From 0105c29710baa144a37e5ec9b1f8086bb1db02f9 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 25 Mar 2022 09:32:49 +0100 Subject: [PATCH] Reduce memory consumption of anti bot feature --- .../fastlogin/core/TickingRateLimiter.java | 96 ++++++++++++++++--- ...rTest.java => TickingRateLimiterTest.java} | 2 +- 2 files changed, 84 insertions(+), 14 deletions(-) rename core/src/test/java/com/github/games647/fastlogin/core/{RateLimiterTest.java => TickingRateLimiterTest.java} (99%) diff --git a/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java b/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java index 7df3d0d0..6064229e 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java @@ -27,7 +27,9 @@ package com.github.games647.fastlogin.core; import com.google.common.base.Ticker; -import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.concurrent.TimeUnit; /** * Limit the number of requests with a maximum size. Each requests expire after the specified time making it available @@ -37,20 +39,22 @@ public class TickingRateLimiter implements RateLimiter { private final Ticker ticker; - private final long[] requests; + // amount of milliseconds to expire private final long expireTime; - private int position; + + // total request limit + private final int requestLimit; + + private final Deque records; + private int totalRequests; public TickingRateLimiter(Ticker ticker, int maxLimit, long expireTime) { this.ticker = ticker; - this.requests = new long[maxLimit]; + this.requestLimit = maxLimit; this.expireTime = expireTime; - // fill the array with expired entry, because nanoTime could overflow and include negative numbers - long nowMilli = ticker.read() / 1_000_000; - long initialVal = nowMilli - expireTime; - Arrays.fill(requests, initialVal); + records = new ArrayDeque<>(10); } /** @@ -64,14 +68,80 @@ public class TickingRateLimiter implements RateLimiter { long nowMilli = ticker.read() / 1_000_000; synchronized (this) { // having synchronized will limit the amount of concurrency a lot - long oldest = requests[position]; - if (nowMilli - oldest >= expireTime) { - requests[position] = nowMilli; - position = (position + 1) % requests.length; + TimeRecord oldest = records.peekFirst(); + if (oldest != null && oldest.hasExpired(nowMilli)) { + records.pop(); + totalRequests -= oldest.getRequestCount(); + } + + // total requests reached block any further requests + if (totalRequests >= requestLimit) { + return false; + } + + TimeRecord latest = records.peekLast(); + if (latest == null) { + // empty list - add new record + records.add(new TimeRecord(nowMilli, expireTime)); + totalRequests++; return true; } - return false; + int res = latest.compareTo(nowMilli); + if (res < 0) { + // now is before than the record means time jumps + throw new IllegalStateException("Time jumped back"); + } + + if (res == 0) { + // same minute record + latest.hit(); + totalRequests++; + return true; + } + + // now is one minute newer + records.add(new TimeRecord(nowMilli, expireTime)); + totalRequests++; + return true; + } + } + + private static class TimeRecord implements Comparable { + + private final long firstMinuteRecord; + private final long expireTime; + private int count; + + public TimeRecord(long firstMinuteRecord, long expireTime) { + this.firstMinuteRecord = firstMinuteRecord; + this.expireTime = expireTime; + this.count = 1; + } + + public void hit() { + count++; + } + + public int getRequestCount() { + return count; + } + + public boolean hasExpired(long now) { + return firstMinuteRecord + expireTime <= now; + } + + @Override + public int compareTo(Long other) { + if (other < firstMinuteRecord) { + return -1; + } + + if (other > firstMinuteRecord + TimeUnit.MINUTES.toMillis(1)) { + return +1; + } + + return 0; } } } diff --git a/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java similarity index 99% rename from core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java rename to core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 7cbd6357..2c04b6e3 100644 --- a/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java +++ b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java @@ -33,7 +33,7 @@ import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class RateLimiterTest { +public class TickingRateLimiterTest { private static final long THRESHOLD_MILLI = 10;