Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.settings
.classpath
.project
bin/

# Package Files #
*.jar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface ReactiveRequestRateLimiter {
Mono<Boolean> overLimitWhenIncrementedReactive(String key);

Mono<Boolean> overLimitWhenIncrementedReactive(String key, int weight);

Mono<Boolean> incrementRegardlessReactive(String key, int weight);

Mono<Boolean> geLimitWhenIncrementedReactive(String key);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ public interface RequestRateLimiter {
* @return {@code true} if the key is over the limit, otherwise {@code false}
*/
boolean overLimitWhenIncremented(String key);

/**
* Regardless of being over limit, we still want to track the count, this is for a client that is going to act
* regardless of a limit, but, still needs to be counted towareds said limit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

towareds -> towards

* @param key
* @param weight
* @return {@code true} if the key is over the limit, otherwise {@code false}
*/
boolean incrementRegardless(String key, int weight);

/**
* Determine if the given key, after incrementing by the given weight, has exceeded the configured rate limit.
Expand Down Expand Up @@ -55,4 +64,5 @@ public interface RequestRateLimiter {
* @return {@code true} if the key existed, otherwise {@code false} .
*/
boolean resetLimit(String key);

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public boolean overLimitWhenIncremented(String key) {
// TODO support muli keys
@Override
public boolean overLimitWhenIncremented(String key, int weight) {
return eqOrGeLimit(key, weight, true);
return eqOrGeLimit(key, weight, true, false);
}

@Override
Expand All @@ -60,20 +60,19 @@ public boolean geLimitWhenIncremented(String key) {

@Override
public boolean geLimitWhenIncremented(String key, int weight) {
return eqOrGeLimit(key, weight, false);
return eqOrGeLimit(key, weight, false, false);
}

// @Override
// public boolean isOverLimit(String key) {
// return overLimitWhenIncremented(key, 0);
// }
//
// @Override
// public boolean isGeLimit(String key) {
// return geLimitWhenIncremented(key, 0);
// }




@Override
public boolean incrementRegardless(String key, int weight) {
return eqOrGeLimit(key, weight, false, true);
}



@Override
public boolean resetLimit(String key) {
IMap<Object, Object> map = hz.getMap(key);
if (map == null || map.isEmpty()) {
Expand All @@ -92,7 +91,7 @@ private IMap<String, Long> getMap(String key, int longestDuration) {
return hz.getMap(key);
}

private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater) {
private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater, boolean increment_anyway) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

increment_anyway -> incrementAnyway

For me alwaysIncrement sounds better.


requireNonNull(key, "key cannot be null");
requireNonNull(rules, "rules cannot be null");
Expand Down Expand Up @@ -152,7 +151,10 @@ private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater) {
// check our limits
long count = coalesce(cur, 0L) + weight;
if (count > rule.getLimit()) {
return true; // over limit, don't record request
geLimit = true;
if (!increment_anyway) {
return true; // over limit, don't record request
}
} else if (!strictlyGreater && count == rule.getLimit()) {
geLimit = true; // at limit, do record request
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,41 @@ void shouldEventuallyCleanUpExpiredKeys() throws Exception {
});

IMap<Object, Object> map = hz.getMap(key);

while (map.size() != 0) {
Thread.sleep(10);
}
assertThat(map.size()).isZero();
}


@Test
void shouldCheckIncrementAnyway() throws Exception {

ImmutableSet<RequestLimitRule> rules = ImmutableSet.of(RequestLimitRule.of(20, TimeUnit.SECONDS, 5));
RequestRateLimiter requestRateLimiter = getRateLimiter(rules, timeBandit);

String key = "ip:127.0.0.5";

timeBandit.addUnixTimeMilliSeconds(100L);
// Counter should be 0 now
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isFalse();
// Counter should be 1 now and 'false', meaning not over limit

timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.incrementRegardless(key, 10)).isTrue();
// Counter should be 11 now and 'true', meaning over limit

timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isTrue();
// Counter should be 11 now and 'true', meaning over limit
// If the increment regardless worked, then the value would have been 2
// which would not be under the limit.




Comment on lines +92 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Way too many new lines :)

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,24 @@ public boolean overLimitWhenIncremented(String key) {

@Override
public boolean overLimitWhenIncremented(String key, int weight) {
return eqOrGeLimit(key, weight, true);
return eqOrGeLimit(key, weight, true, false);
}

@Override
public boolean incrementRegardless(String key, int weight) {
return eqOrGeLimit(key, weight, true, true);
}

@Override
public boolean geLimitWhenIncremented(String key) {
return geLimitWhenIncremented(key, 1);
}

@Override
public boolean geLimitWhenIncremented(String key, int weight) {
return eqOrGeLimit(key, weight, false);
return eqOrGeLimit(key, weight, false, false);
}


// @Override
// public boolean isOverLimit(String key) {
Expand Down Expand Up @@ -100,7 +106,9 @@ private ConcurrentMap<String, Long> getMap(String key, int longestDuration) {
});
}

private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater) {
// Chris Fauerbach, adding a way to override the increment even if over limit
// @chrisfauerbach , https://fauie.com , github.com/chrisfauerbach

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these comments present?

private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater, boolean increment_regardless) {

requireNonNull(key, "key cannot be null");
requireNonNull(rules, "rules cannot be null");
Expand Down Expand Up @@ -157,7 +165,11 @@ private boolean eqOrGeLimit(String key, int weight, boolean strictlyGreater) {
// check our limits
long count = coalesce(cur, 0L) + weight;
if (count > rule.getLimit()) {
return true; // over limit, don't record request
if (increment_regardless) {
geLimit = true;
}else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}else { -> } else {

return true; // over limit, don't record request
}
} else if (!strictlyGreater && count == rule.getLimit()) {
geLimit = true; // at limit, do record request
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,33 @@ void shouldEventuallyCleanUpExpiredKeys() throws Exception {
assertThat(expiryingKeyMap.size()).isZero();
}


@Test
void shouldCheckIncrementAnyway() throws Exception {

ImmutableSet<RequestLimitRule> rules = ImmutableSet.of(RequestLimitRule.of(20, TimeUnit.SECONDS, 5));
RequestRateLimiter requestRateLimiter = getRateLimiter(rules, timeBandit);

String key = "ip:127.0.0.5";

timeBandit.addUnixTimeMilliSeconds(100L);
// Counter should be 0 now
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isFalse();
// Counter should be 1 now and 'false', meaning not over limit

timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.incrementRegardless(key, 10)).isTrue();
// Counter should be 11 now and 'true', meaning over limit

timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isTrue();
// Counter should be 11 now and 'true', meaning over limit
// If the increment regardless worked, then the value would have been 2
// which would not be under the limit.




}

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public boolean overLimitWhenIncremented(String key) {

@Override
public boolean overLimitWhenIncremented(String key, int weight) {
return throwOnTimeout(eqOrGeLimitReactive(key, weight, true));
return throwOnTimeout(eqOrGeLimitReactive(key, weight, true, false));
}

@Override
Expand All @@ -92,7 +92,12 @@ public boolean geLimitWhenIncremented(String key) {

@Override
public boolean geLimitWhenIncremented(String key, int weight) {
return throwOnTimeout(eqOrGeLimitReactive(key, weight, false));
return throwOnTimeout(eqOrGeLimitReactive(key, weight, false, false));
}

@Override
public boolean incrementRegardless(String key, int weight) {
return throwOnTimeout(eqOrGeLimitReactive(key, weight, false, true));
}

// @Override
Expand All @@ -117,7 +122,7 @@ public Mono<Boolean> overLimitWhenIncrementedReactive(String key) {

@Override
public Mono<Boolean> overLimitWhenIncrementedReactive(String key, int weight) {
return eqOrGeLimitReactive(key, weight, true);
return eqOrGeLimitReactive(key, weight, true, false);
}

@Override
Expand All @@ -127,7 +132,12 @@ public Mono<Boolean> geLimitWhenIncrementedReactive(String key) {

@Override
public Mono<Boolean> geLimitWhenIncrementedReactive(String key, int weight) {
return eqOrGeLimitReactive(key, weight, false);
return eqOrGeLimitReactive(key, weight, false, false);
}

@Override
public Mono<Boolean> incrementRegardlessReactive(String key, int weight) {
return eqOrGeLimitReactive(key, weight, true, true);
}

@Override
Expand All @@ -136,18 +146,19 @@ public Mono<Boolean> resetLimitReactive(String key) {
}

private CompletionStage<Boolean> eqOrGeLimitAsync(String key, int weight, boolean strictlyGreater) {
return eqOrGeLimitReactive(key, weight, strictlyGreater).toFuture();
return eqOrGeLimitReactive(key, weight, strictlyGreater, false).toFuture();
}

private Mono<Boolean> eqOrGeLimitReactive(String key, int weight, boolean strictlyGreater) {


private Mono<Boolean> eqOrGeLimitReactive(String key, int weight, boolean strictlyGreater, boolean incremement_regardless) {
requireNonNull(key);

// TODO script load can be reactive
// TODO handle scenario where script is not loaded, flush scripts and test scenario
String sha = scriptLoader.scriptSha();

return timeSupplier.getReactive().flatMapMany(time ->
connection.reactive().evalsha(sha, VALUE, new String[]{key}, rulesJson, Long.toString(time), Integer.toString(weight), toStringOneZero(strictlyGreater)))
connection.reactive().evalsha(sha, VALUE, new String[]{key}, rulesJson, Long.toString(time), Integer.toString(weight), toStringOneZero(strictlyGreater), toStringOneZero(incremement_regardless)))
.next()
.map("1"::equals)
.doOnSuccess(over -> {
Expand Down
12 changes: 11 additions & 1 deletion ratelimitj-redis/src/main/resources/sliding-window-ratelimit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ local limits = cjson.decode(ARGV[1])
local now = tonumber(ARGV[2])
local weight = tonumber(ARGV[3] or '1')
local strictly_greater = tonumber(ARGV[4] or '1') == 1

--Chris Fauerbach, adding a way to override the increment even if over limit
--@chrisfauerbach , https://fauie.com , github.com/chrisfauerbach
local incr_regardless = tonumber(ARGV[5] or '1') == 1

local longest_duration = limits[1][1] or 0
local saved_keys = {}
local ge_limit = '0'
Expand Down Expand Up @@ -55,7 +60,12 @@ for i, limit in ipairs(limits) do
-- check our limits
local count = tonumber(cur or '0') + weight
if count > limit[2] then
return '1' -- over limit, don't record request
-- Checking the overlimit, but allowing the increment
if incr_regardless then
ge_limit= '1'
else
return '1' -- over limit, don't record request
end
elseif count == limit[2] and not strictly_greater then
ge_limit = '1' -- at limit, do record request
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@
import es.moki.ratelimitj.core.limiter.request.RequestRateLimiter;
import es.moki.ratelimitj.core.time.TimeSupplier;
import es.moki.ratelimitj.test.limiter.request.AbstractSyncRequestRateLimiterTest;
import es.moki.ratelimitj.test.time.TimeBanditSupplier;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import com.google.common.collect.ImmutableSet;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class RedisSlidingWindowSyncRequestRateLimiterTest extends AbstractSyncRequestRateLimiterTest {

private static RedisClient client;
private static StatefulRedisConnection<String, String> connect;
private static Logger LOG = LoggerFactory.getLogger(RedisSlidingWindowSyncRequestRateLimiterTest.class);

@BeforeAll
static void beforeAll() {
Expand All @@ -42,4 +52,33 @@ void afterEach() {
protected RequestRateLimiter getRateLimiter(Set<RequestLimitRule> rules, TimeSupplier timeSupplier) {
return new RedisSlidingWindowRequestRateLimiter(connect, rules, timeSupplier);
}


@Test
void shouldCheckIncrementAnyway() throws Exception {

ImmutableSet<RequestLimitRule> rules = ImmutableSet.of(RequestLimitRule.of(20, TimeUnit.SECONDS, 5));
TimeBanditSupplier timeBandit = new TimeBanditSupplier();

RequestRateLimiter requestRateLimiter = getRateLimiter(rules, timeBandit);

String key = "ip:127.0.0.5";

timeBandit.addUnixTimeMilliSeconds(100L);
// Counter should be 0 now
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isFalse();
// Counter should be 1 now and 'false', meaning not over limit
LOG.debug("About to print add 10 to go over.");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO the log debug messages are not needed, and they could be Java comments if they bring any value

timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.incrementRegardless(key, 10)).isTrue();
// Counter should be 11 now and 'true', meaning over limit

LOG.debug("About to print add 1 that should be over..");
timeBandit.addUnixTimeMilliSeconds(100L);
assertThat(requestRateLimiter.overLimitWhenIncremented(key, 1)).isTrue();
// Counter should be 11 now and 'true', meaning over limit
// If the increment regardless worked, then the value would have been 2
// which would not be under the limit.
}

}