failsafe-lib / failsafe

Fault tolerance and resilience patterns for the JVM
https://failsafe.dev
Apache License 2.0
4.17k stars 296 forks source link

RateLimiter in vert.x #326

Closed jinwik closed 2 years ago

jinwik commented 2 years ago

Is possible to add a new method to RateLimiter, like long reservePermission() (like resilience4j) , which returns waiTimeInNanos, so we can use vertx.setTimer to wait, instead of Thread.sleep (which will bock the eventloop), thx.

jhalterman commented 2 years ago

@jinwik I'd be open to that, but a better solution might be to support non-blocking rate limiting in general (#318), which I'm actually working on right now. I think this would accomplish what you want, since it would use whatever Scheduler you configure (like a vertx scheduler) to perform the rate limit waiting.

jhalterman commented 2 years ago

@jinwik #318 is implemented in master now if you want to give it a try. I'll likely cut a release in the next day or two.

jinwik commented 2 years ago

@jhalterman Great, I'll give it a try, thx

jhalterman commented 2 years ago

3.2 is released which includes #318, so async waiting on rate limiters should be working now. Feel free to leave a comment and re-open if things aren't working as expected.

jinwik commented 2 years ago

we can use a custom scheduler to avoid blocking vertx eventloop, but I need to get the actual delay time and record it in the http response header. It is easier if acquirePermitWaitNanos is public, e.g:

// HTTP request
RoutingContext routingContext = ...
long ms = toMs(rateLimiterSingleton.acquirePermitWaitNanos());
if(ms >= 1) {
    vertx.setTimer(ms, l -> {
          routingContext.response().putHeader("delay", ms);
          routingContext.response().end(body);
    });
} 
//...
jhalterman commented 2 years ago

@jinwik Can you give me a bit of a broader example how you're using Vert.x with failsafe? I was assuming you were using a custom Failsafe scheduler, something like this:

https://github.com/failsafe-lib/failsafe/blob/master/src/test/java/dev/failsafe/examples/VertxExample.java#L44-L66

If you implement a Scheduler similar to that, then Failsafe will pass runnables to the scheduler when it needs to wait for something, like a RateLimiter.

jinwik commented 2 years ago

@jhalterman https://gist.github.com/jinwik/555315e24bdf187d9a93b6366b600f8b

jhalterman commented 2 years ago

Thanks @jinwik. It looks like it makes sense to just expose the wait time. I'll push a change for that shortly which should be in the next release.

I'm curious though, is the custom "delay" header something special for your setup, or do you think something like that is common with other usages of a RateLimiter with vertx?

jinwik commented 2 years ago

Thanks, @jhalterman, the custom "delay" header is my special case.

jhalterman commented 2 years ago

Support for RateLimiter.reservePermit(), tryReservePermit(Duration), and similar methods, has been released in 3.2.1:

https://failsafe.dev/javadoc/dev/failsafe/RateLimiter.html

jinwik commented 2 years ago

Thanks a lot @jhalterman

jhalterman commented 2 years ago

No problem. I forgot to mention, since you want to wait on a permit externally rather than going through a Failsafe execution (get, getAsync, etc) you'll need to use the RateLimiter in a standalone way. Ex:

RateLimiter<Object> rateLimiter = RateLimiter.smoothBuilder(1, Duration.ofSeconds(1));
Duration permitWaitTime = rateLimiter.tryReservePermit(Duration.ofSeconds(10));
if (permitWaitTime.toNanos() > -1)
  vertx.setTimer....

If you want to also record execution results and check for completion, you can use a standalone execution object. Let me know if you run into any trouble with your use case.