You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by xt...@apache.org on 2021/04/19 11:36:42 UTC

[flink] 02/02: [FLINK-22340][runtime] Make ThresholdMeter thread safe.

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

xtsong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git

commit 6c58832852239cf7c04c72ac337dc12dbee0c178
Author: Xintong Song <to...@gmail.com>
AuthorDate: Mon Apr 19 14:28:11 2021 +0800

    [FLINK-22340][runtime] Make ThresholdMeter thread safe.
    
    This closes #15662
---
 .../flink/runtime/metrics/ThresholdMeter.java      | 21 +++++++---
 .../flink/runtime/metrics/ThresholdMeterTest.java  | 46 ++++++++++++++++++++++
 2 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/metrics/ThresholdMeter.java b/flink-runtime/src/main/java/org/apache/flink/runtime/metrics/ThresholdMeter.java
index 96f3da6..fbba4df 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/metrics/ThresholdMeter.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/metrics/ThresholdMeter.java
@@ -23,17 +23,28 @@ import org.apache.flink.metrics.Meter;
 import org.apache.flink.util.clock.Clock;
 import org.apache.flink.util.clock.SystemClock;
 
+import javax.annotation.concurrent.GuardedBy;
+
 import java.time.Duration;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
-/** A timestamp queue based threshold meter. */
+/**
+ * A timestamp queue based threshold meter.
+ *
+ * <p>Note: This class is thread safe, at the price of synchronization overhead. Do not use this in
+ * performance sensitive scenarios, e.g., per-record updated metrics.
+ */
 public class ThresholdMeter implements Meter {
     private static final double MILLISECONDS_PER_SECOND = 1000.0;
     private final Clock clock;
     private final double maxEventsPerInterval;
     private final Duration interval;
+
+    @GuardedBy("this")
     private final Queue<Long> eventTimestamps;
+
+    @GuardedBy("this")
     private long eventCount = 0;
 
     public ThresholdMeter(double maxEventsPerInterval, Duration interval) {
@@ -52,13 +63,13 @@ public class ThresholdMeter implements Meter {
     }
 
     @Override
-    public void markEvent() {
+    public synchronized void markEvent() {
         eventTimestamps.add(clock.absoluteTimeMillis());
         eventCount++;
     }
 
     @Override
-    public void markEvent(long n) {
+    public synchronized void markEvent(long n) {
         long timestamp = clock.absoluteTimeMillis();
         for (int i = 0; i < n; i++) {
             eventTimestamps.add(timestamp);
@@ -72,7 +83,7 @@ public class ThresholdMeter implements Meter {
     }
 
     @Override
-    public long getCount() {
+    public synchronized long getCount() {
         return eventCount;
     }
 
@@ -86,7 +97,7 @@ public class ThresholdMeter implements Meter {
         }
     }
 
-    private int getEventCountsRecentInterval() {
+    private synchronized int getEventCountsRecentInterval() {
         Long currentTimeStamp = clock.absoluteTimeMillis();
         while (!eventTimestamps.isEmpty()
                 && currentTimeStamp - eventTimestamps.peek() > interval.toMillis()) {
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/metrics/ThresholdMeterTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/metrics/ThresholdMeterTest.java
index 1058093..2a38541 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/metrics/ThresholdMeterTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/metrics/ThresholdMeterTest.java
@@ -26,10 +26,13 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
@@ -121,6 +124,49 @@ public class ThresholdMeterTest extends TestLogger {
         thresholdMeter.checkAgainstThreshold();
     }
 
+    @Test
+    public void testConcurrentAccess() throws Exception {
+        final ThresholdMeter thresholdMeter = new ThresholdMeter(THRESHOLD_LARGE, INTERVAL);
+        final int repeatNum = 100;
+        final int concurrency = 2;
+
+        final List<Thread> threads = new ArrayList<>();
+
+        threads.addAll(
+                getConcurrentThreads(repeat(thresholdMeter::markEvent, repeatNum), concurrency));
+        threads.addAll(
+                getConcurrentThreads(repeat(thresholdMeter::getRate, repeatNum), concurrency));
+        threads.addAll(
+                getConcurrentThreads(
+                        repeat(thresholdMeter::checkAgainstThreshold, repeatNum), concurrency));
+
+        for (Thread thread : threads) {
+            thread.start();
+        }
+
+        for (Thread thread : threads) {
+            thread.join();
+        }
+
+        assertEquals(repeatNum * concurrency, thresholdMeter.getCount());
+    }
+
+    private static Runnable repeat(Runnable task, int repeatNum) {
+        return () -> {
+            for (int i = 0; i < repeatNum; ++i) {
+                task.run();
+            }
+        };
+    }
+
+    private static List<Thread> getConcurrentThreads(Runnable task, int concurrency) {
+        List<Thread> threads = new ArrayList<>();
+        for (int i = 0; i < concurrency; ++i) {
+            threads.add(new Thread(task));
+        }
+        return threads;
+    }
+
     private static ThresholdMeter createLargeThresholdMeter() {
         return new ThresholdMeter(THRESHOLD_LARGE, INTERVAL, clock);
     }