This is an automated email from the ASF dual-hosted git repository.

yuqi1129 pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 09a8658d39 [Cherry-pick to branch-1.3] [#10169] fix(core): Make 
drop-event log throttling atomic (#12711) (#12742)
09a8658d39 is described below

commit 09a8658d39650d55aff6c408a297708eae45abef
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Aug 31 20:27:57 2026 +0800

    [Cherry-pick to branch-1.3] [#10169] fix(core): Make drop-event log 
throttling atomic (#12711) (#12742)
    
    **Cherry-pick Information:**
    - Original commit: 416ef9bece606eecf843b9c112803c04f1d88893
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: MrSibe <[email protected]>
---
 .../gravitino/listener/AsyncQueueListener.java     |  37 +++---
 .../gravitino/listener/TestAsyncQueueListener.java | 127 +++++++++++++++++++++
 2 files changed, 148 insertions(+), 16 deletions(-)

diff --git 
a/core/src/main/java/org/apache/gravitino/listener/AsyncQueueListener.java 
b/core/src/main/java/org/apache/gravitino/listener/AsyncQueueListener.java
index 064a9a63c9..fd5387b40b 100644
--- a/core/src/main/java/org/apache/gravitino/listener/AsyncQueueListener.java
+++ b/core/src/main/java/org/apache/gravitino/listener/AsyncQueueListener.java
@@ -52,7 +52,8 @@ public class AsyncQueueListener implements 
EventListenerPlugin {
   private final int dispatcherJoinSeconds;
   private final AtomicBoolean stopped = new AtomicBoolean(false);
   private final AtomicLong dropEventCounters = new AtomicLong(0);
-  private final AtomicLong lastDropEventCounters = new AtomicLong(0);
+  private final Object dropEventLogLock = new Object();
+  private long lastDropEventCounters;
   private Instant lastRecordDropEventTime = Instant.EPOCH;
   private final String asyncQueueListenerName;
   private final int highWatermarkThreshold;
@@ -144,23 +145,27 @@ public class AsyncQueueListener implements 
EventListenerPlugin {
   }
 
   private void logDropEventsIfNecessary() {
-    long currentDropEvents = dropEventCounters.incrementAndGet();
-    long lastDropEvents = lastDropEventCounters.get();
-    // dropEvents may less than zero in such conditions:
-    // 1. Thread A increment dropEventCounters
-    // 2. Thread B increment dropEventCounters and update lastDropEventCounters
-    // 3. Thread A get lastDropEventCounters
-    long dropEvents = currentDropEvents - lastDropEvents;
-    if (dropEvents > 0 && 
Instant.now().isAfter(lastRecordDropEventTime.plusSeconds(60))) {
-      if (lastDropEventCounters.compareAndSet(lastDropEvents, 
currentDropEvents)) {
-        LOG.warn(
-            "{} drop {} events since {}",
-            asyncQueueListenerName,
-            dropEvents,
-            lastRecordDropEventTime);
-        lastRecordDropEventTime = Instant.now();
+    long dropEventsToLog = 0;
+    Instant previousRecordTime = null;
+    synchronized (dropEventLogLock) {
+      long currentDropEvents = dropEventCounters.incrementAndGet();
+      long lastDropEvents = lastDropEventCounters;
+      long dropEvents = currentDropEvents - lastDropEvents;
+      Instant now = Instant.now();
+      if (dropEvents > 0 && 
now.isAfter(lastRecordDropEventTime.plusSeconds(60))) {
+        lastDropEventCounters = currentDropEvents;
+        dropEventsToLog = dropEvents;
+        previousRecordTime = lastRecordDropEventTime;
+        lastRecordDropEventTime = now;
       }
     }
+    if (dropEventsToLog > 0) {
+      LOG.warn(
+          "{} drop {} events since {}",
+          asyncQueueListenerName,
+          dropEventsToLog,
+          previousRecordTime);
+    }
   }
 
   private void enqueueEvent(BaseEvent baseEvent) {
diff --git 
a/core/src/test/java/org/apache/gravitino/listener/TestAsyncQueueListener.java 
b/core/src/test/java/org/apache/gravitino/listener/TestAsyncQueueListener.java
new file mode 100644
index 0000000000..a5d578a7c0
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/listener/TestAsyncQueueListener.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.listener;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.gravitino.listener.api.event.Event;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.AbstractConfiguration;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestAsyncQueueListener {
+  private BlockingAppender blockingAppender;
+  private LoggerContext loggerContext;
+
+  @BeforeEach
+  void setUp() {
+    loggerContext =
+        (LoggerContext) 
LogManager.getContext(AsyncQueueListener.class.getClassLoader(), false);
+    Configuration configuration = loggerContext.getConfiguration();
+
+    blockingAppender = new BlockingAppender("asyncQueueListenerCapture");
+    blockingAppender.start();
+    configuration.addAppender(blockingAppender);
+
+    LoggerConfig loggerConfig =
+        new LoggerConfig(AsyncQueueListener.class.getName(), Level.WARN, 
false);
+    loggerConfig.addAppender(blockingAppender, Level.WARN, null);
+    configuration.addLogger(AsyncQueueListener.class.getName(), loggerConfig);
+    loggerContext.updateLoggers();
+  }
+
+  @AfterEach
+  void tearDown() {
+    blockingAppender.releaseFirstLog.countDown();
+    AbstractConfiguration configuration = (AbstractConfiguration) 
loggerContext.getConfiguration();
+    configuration.removeLogger(AsyncQueueListener.class.getName());
+    blockingAppender.stop();
+    configuration.removeAppender(blockingAppender.getName());
+    loggerContext.updateLoggers();
+  }
+
+  @Test
+  void testDropEventLogThrottlingIsAtomic() throws Exception {
+    AsyncQueueListener listener = new AsyncQueueListener(List.of(), "test", 1, 
1);
+    Event event = mock(Event.class);
+    listener.onPostEvent(event);
+
+    ExecutorService executor = Executors.newFixedThreadPool(2);
+    try {
+      Future<?> firstDrop = executor.submit(() -> listener.onPostEvent(event));
+      assertTrue(blockingAppender.firstLogEntered.await(30, TimeUnit.SECONDS));
+
+      Future<?> secondDrop = executor.submit(() -> 
listener.onPostEvent(event));
+      secondDrop.get(30, TimeUnit.SECONDS);
+
+      blockingAppender.releaseFirstLog.countDown();
+      firstDrop.get(30, TimeUnit.SECONDS);
+
+      assertEquals(1, blockingAppender.logCount.get());
+    } finally {
+      blockingAppender.releaseFirstLog.countDown();
+      executor.shutdownNow();
+      assertTrue(executor.awaitTermination(30, TimeUnit.SECONDS));
+    }
+  }
+
+  private static class BlockingAppender extends AbstractAppender {
+    private final AtomicInteger logCount = new AtomicInteger();
+    private final CountDownLatch firstLogEntered = new CountDownLatch(1);
+    private final CountDownLatch releaseFirstLog = new CountDownLatch(1);
+
+    BlockingAppender(String name) {
+      super(name, null, PatternLayout.createDefaultLayout(), true, null);
+    }
+
+    @Override
+    public void append(LogEvent event) {
+      if (logCount.incrementAndGet() != 1) {
+        return;
+      }
+
+      firstLogEntered.countDown();
+      try {
+        assertTrue(releaseFirstLog.await(30, TimeUnit.SECONDS));
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw new AssertionError("Interrupted while waiting to release the 
first log", e);
+      }
+    }
+  }
+}

Reply via email to