You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/10/08 15:35:03 UTC

(hugegraph) 13/15: fix(server): defer the first rate limit reset

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

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

commit ea4994a96747f17d767bf3ab97740880194d7b5a
Author: imbajin <ji...@apache.org>
AuthorDate: Thu Oct 8 20:31:40 2026 +0800

    fix(server): defer the first rate limit reset
    
    - start the first reset after the configured interval
    - preserve the initial request window
    - cover fixed-window timing boundaries
---
 .../util/FixedTimerWindowRateLimiter.java          |  8 +++-
 .../hugegraph/unit/util/RateLimiterTest.java       | 52 ++++++++++++++++++++++
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/FixedTimerWindowRateLimiter.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/FixedTimerWindowRateLimiter.java
index 0163656d5..7b08204bb 100644
--- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/FixedTimerWindowRateLimiter.java
+++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/FixedTimerWindowRateLimiter.java
@@ -34,7 +34,11 @@ public class FixedTimerWindowRateLimiter implements RateLimiter {
     private final int limit;
 
     public FixedTimerWindowRateLimiter(int limitPerSecond) {
-        this.timer = new Timer("RateAuditLog", true);
+        this(limitPerSecond, new Timer("RateAuditLog", true));
+    }
+
+    FixedTimerWindowRateLimiter(int limitPerSecond, Timer timer) {
+        this.timer = timer;
         this.count = new LongAdder();
         this.limit = limitPerSecond;
         // Count will be reset if hit limit (run once per 1000ms)
@@ -45,7 +49,7 @@ public class FixedTimerWindowRateLimiter implements RateLimiter {
                     count.reset();
                 }
             }
-        }, 0L, RESET_PERIOD);
+        }, RESET_PERIOD, RESET_PERIOD);
     }
 
     @Override
diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/RateLimiterTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/RateLimiterTest.java
index 111b63942..4ace25217 100644
--- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/RateLimiterTest.java
+++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/RateLimiterTest.java
@@ -17,6 +17,9 @@
 
 package org.apache.hugegraph.unit.util;
 
+import java.lang.reflect.Constructor;
+import java.util.Timer;
+import java.util.TimerTask;
 import java.util.concurrent.atomic.LongAdder;
 
 import org.apache.hugegraph.testutil.Assert;
@@ -74,6 +77,55 @@ public abstract class RateLimiterTest {
             return new FixedTimerWindowRateLimiter(rate);
         }
 
+        @Test
+        public void testInitialTimerWindow() throws Exception {
+            try (RecordingTimer timer = new RecordingTimer()) {
+                Constructor<FixedTimerWindowRateLimiter> constructor =
+                        FixedTimerWindowRateLimiter.class.getDeclaredConstructor(int.class, Timer.class);
+                constructor.setAccessible(true);
+                RateLimiter limiter = constructor.newInstance(400, timer);
+
+                // The initial reset must wait for a complete window, even if the timer thread starts late.
+                Assert.assertEquals(RateLimiter.RESET_PERIOD, timer.delay);
+                Assert.assertEquals(RateLimiter.RESET_PERIOD, timer.period);
+                for (int i = 0; i < 400; i++) {
+                    Assert.assertTrue(limiter.tryAcquire());
+                }
+                LongAdder count = Whitebox.getInternalState(limiter, "count");
+                Assert.assertEquals(400, count.intValue());
+                Assert.assertFalse(limiter.tryAcquire());
+
+                timer.task.run();
+                Assert.assertEquals(0, count.intValue());
+                Assert.assertTrue(limiter.tryAcquire());
+                timer.task.run();
+                Assert.assertEquals(1, count.intValue());
+            }
+        }
+
+        private static class RecordingTimer extends Timer implements AutoCloseable {
+
+            private TimerTask task;
+            private long delay;
+            private long period;
+
+            private RecordingTimer() {
+                super("RateLimiterTest", true);
+            }
+
+            @Override
+            public void schedule(TimerTask task, long delay, long period) {
+                this.task = task;
+                this.delay = delay;
+                this.period = period;
+            }
+
+            @Override
+            public void close() {
+                this.cancel();
+            }
+        }
+
         @Test
         public void testTryAcquireAfterPeriod() throws Exception {
             int rateLimit = 400;