dev.failsafe.RateLimiter Maven / Gradle / Ivy
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package dev.failsafe;
import java.time.Duration;
/**
* A rate limiter allows you to control the rate of executions as a way of preventing system overload.
*
* There are two types of rate limiting: smooth and bursty. Smooth rate limiting will evenly spread
* out execution requests over-time, effectively smoothing out uneven execution request rates. Bursty rate
* limiting allows potential bursts of executions to occur, up to a configured max per time period.
* Rate limiting is based on permits, which can be requested in order to perform rate limited execution.
* Permits are automatically refreshed over time based on the rate limiter's configuration.
*
* This class provides methods that block while waiting for permits to become available, and also methods that return
* immediately. The blocking methods include:
*
* - {@link #acquirePermit()}
* - {@link #acquirePermits(int)}
* - {@link #acquirePermit(Duration)}
* - {@link #acquirePermits(int, Duration)}
* - {@link #tryAcquirePermit(Duration)}
* - {@link #tryAcquirePermits(int, Duration)}
*
*
*
* The methods that return immediately include:
*
* - {@link #tryAcquirePermit()}
* - {@link #tryAcquirePermits(int)}
* - {@link #reservePermit()}
* - {@link #reservePermits(int)}
* - {@link #tryReservePermit(Duration)}
* - {@link #tryReservePermits(int, Duration)}
*
*
*
* This class also provides methods that throw {@link RateLimitExceededException} when permits cannot be acquired, and
* also methods that return a boolean. The {@code acquire} methods all throw {@link RateLimitExceededException} when
* permits cannot be acquired, and the {@code tryAcquire} methods return a boolean.
*
*
* The {@code reserve} methods attempt to reserve permits and return an expected wait time before the permit can be
* used. This helps integrate with scenarios where you need to wait externally
*
*
* This class is threadsafe.
*
*
* @param result type
* @author Jonathan Halterman
* @see RateLimiterConfig
* @see RateLimiterBuilder
* @see RateLimitExceededException
*/
public interface RateLimiter extends Policy {
/**
* Returns a smooth {@link RateLimiterBuilder} for the {@code maxExecutions} and {@code period}, which control how
* frequently an execution is permitted. The individual execution rate is computed as {@code period / maxExecutions}.
* For example, with {@code maxExecutions} of {@code 100} and a {@code period} of {@code 1000 millis}, individual
* executions will be permitted at a max rate of one every 10 millis.
* By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max
* wait time} of {@code 0}.
*
* Executions are performed with no delay until they exceed the max rate, after which executions are either rejected
* or will block and wait until the {@link RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is exceeded.
*
* @param maxExecutions The max number of permitted executions per {@code period}
* @param period The period after which permitted executions are reset to the {@code maxExecutions}
*/
static RateLimiterBuilder smoothBuilder(long maxExecutions, Duration period) {
return new RateLimiterBuilder<>(period.dividedBy(maxExecutions));
}
/**
* Returns a smooth {@link RateLimiterBuilder} for the {@code maxRate}, which controls how frequently an execution is
* permitted. For example, a {@code maxRate} of {@code Duration.ofMillis(10)} would allow up to one execution every 10
* milliseconds.
* By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max
* wait time} of {@code 0}.
*
* Executions are performed with no delay until they exceed the {@code maxRate}, after which executions are either
* rejected or will block and wait until the {@link RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is
* exceeded.
*
* @param maxRate at which individual executions should be permitted
*/
static RateLimiterBuilder smoothBuilder(Duration maxRate) {
return new RateLimiterBuilder<>(maxRate);
}
/**
* Returns a bursty {@link RateLimiterBuilder} for the {@code maxExecutions} per {@code period}. For example, a {@code
* maxExecutions} value of {@code 100} with a {@code period} of {@code Duration.ofSeconds(1)} would allow up to 100
* executions every 1 second.
* By default, the returned {@link RateLimiterBuilder} will have a {@link RateLimiterBuilder#withMaxWaitTime max
* wait time} of {@code 0}.
*
* Executions are performed with no delay up until the {@code maxExecutions} are reached for the current {@code
* period}, after which executions are either rejected or will block and wait until the {@link
* RateLimiterBuilder#withMaxWaitTime(Duration) max wait time} is exceeded.
*
* @param maxExecutions The max number of permitted executions per {@code period}
* @param period The period after which permitted executions are reset to the {@code maxExecutions}
*/
static RateLimiterBuilder burstyBuilder(long maxExecutions, Duration period) {
return new RateLimiterBuilder<>(maxExecutions, period);
}
/**
* Creates a new RateLimiterBuilder that will be based on the {@code config}.
*/
static RateLimiterBuilder builder(RateLimiterConfig config) {
return new RateLimiterBuilder<>(config);
}
/**
* Returns the {@link RateLimiterConfig} that the RateLimiter was built with.
*/
@Override
RateLimiterConfig getConfig();
/**
* Attempts to acquire a permit to perform an execution against the rate limiter, waiting until one is available or
* the thread is interrupted.
*
* @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit
* @see #tryAcquirePermit()
* @see #reservePermit()
*/
default void acquirePermit() throws InterruptedException {
acquirePermits(1);
}
/**
* Attempts to acquire the requested {@code permits} to perform executions against the rate limiter, waiting until
* they are available or the thread is interrupted.
*
* @throws IllegalArgumentException if {@code permits} is < 1
* @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits}
* @see #tryAcquirePermits(int)
* @see #reservePermits(int)
*/
void acquirePermits(int permits) throws InterruptedException;
/**
* Attempts to acquire a permit to perform an execution against the rate limiter, waiting up to the {@code
* maxWaitTime} until one is available, else throwing {@link RateLimitExceededException} if a permit will not be
* available in time.
*
* @throws NullPointerException if {@code maxWaitTime} is null
* @throws RateLimitExceededException if the rate limiter cannot acquire a permit within the {@code maxWaitTime}
* @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit
* @see #tryAcquirePermit(Duration)
*/
default void acquirePermit(Duration maxWaitTime) throws InterruptedException {
acquirePermits(1, maxWaitTime);
}
/**
* Attempts to acquire the requested {@code permits} to perform executions against the rate limiter, waiting up to the
* {@code maxWaitTime} until they are available, else throwing {@link RateLimitExceededException} if the permits will
* not be available in time.
*
* @throws IllegalArgumentException if {@code permits} is < 1
* @throws NullPointerException if {@code maxWaitTime} is null
* @throws RateLimitExceededException if the rate limiter cannot acquire a permit within the {@code maxWaitTime}
* @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits}
* @see #tryAcquirePermits(int, Duration)
*/
default void acquirePermits(int permits, Duration maxWaitTime) throws InterruptedException {
if (!tryAcquirePermits(permits, maxWaitTime))
throw new RateLimitExceededException(this);
}
/**
* Returns whether the rate limiter is smooth.
*
* @see #smoothBuilder(long, Duration)
* @see #smoothBuilder(Duration)
*/
default boolean isSmooth() {
return getConfig().getMaxRate() != null;
}
/**
* Returns whether the rate limiter is bursty.
*
* @see #burstyBuilder(long, Duration)
*/
default boolean isBursty() {
return getConfig().getPeriod() != null;
}
/**
* Reserves a permit to perform an execution against the rate limiter, and returns the time that the caller is
* expected to wait before acting on the permit. Returns {@code 0} if the permit is immediately available and no
* waiting is needed.
*
* @see #acquirePermit()
* @see #tryAcquirePermit()
*/
default Duration reservePermit() {
return reservePermits(1);
}
/**
* Reserves the {@code permits} to perform executions against the rate limiter, and returns the time that the caller
* is expected to wait before acting on the permits. Returns {@code 0} if the permits are immediately available and no
* waiting is needed.
*
* @throws IllegalArgumentException if {@code permits} is < 1
* @see #acquirePermits(int)
* @see #tryAcquirePermits(int)
*/
Duration reservePermits(int permits);
/**
* Tries to reserve a permit to perform an execution against the rate limiter, and returns the time that the caller is
* expected to wait before acting on the permit, as long as it's less than the {@code maxWaitTime}.
*
* - Returns the expected wait time for the permit if it was successfully reserved.
* - Returns {@code 0} if the permit was successfully reserved and no waiting is needed.
* - Returns {@code -1 nanoseconds} if the permit was not reserved because the wait time would be greater than the {@code maxWaitTime}.
*
*
* @throws NullPointerException if {@code maxWaitTime} is null
* @see #acquirePermit(Duration)
* @see #tryAcquirePermit(Duration)
*/
default Duration tryReservePermit(Duration maxWaitTime) {
return tryReservePermits(1, maxWaitTime);
}
/**
* Tries to reserve the {@code permits} to perform executions against the rate limiter, and returns the time that the
* caller is expected to wait before acting on the permits, as long as it's less than the {@code maxWaitTime}.
*
* - Returns the expected wait time for the permits if they were successfully reserved.
* - Returns {@code 0} if the permits were successfully reserved and no waiting is needed.
* - Returns {@code -1 nanoseconds} if the permits were not reserved because the wait time would be greater than the {@code maxWaitTime}.
*
*
* @throws IllegalArgumentException if {@code permits} is < 1
* @throws NullPointerException if {@code maxWaitTime} is null
* @see #acquirePermit(Duration)
* @see #tryAcquirePermit(Duration)
*/
Duration tryReservePermits(int permits, Duration maxWaitTime);
/**
* Tries to acquire a permit to perform an execution against the rate limiter, returning immediately without waiting.
*
* @return whether the requested {@code permits} are successfully acquired or not
* @see #acquirePermit()
* @see #reservePermits(int)
*/
default boolean tryAcquirePermit() {
return tryAcquirePermits(1);
}
/**
* Tries to acquire the requested {@code permits} to perform executions against the rate limiter, returning
* immediately without waiting.
*
* @return whether the requested {@code permits} are successfully acquired or not
* @throws IllegalArgumentException if {@code permits} is < 1
* @see #acquirePermits(int)
*/
boolean tryAcquirePermits(int permits);
/**
* Tries to acquire a permit to perform an execution against the rate limiter, waiting up to the {@code maxWaitTime}
* until they are available.
*
* @return whether a permit is successfully acquired
* @throws NullPointerException if {@code maxWaitTime} is null
* @throws InterruptedException if the current thread is interrupted while waiting to acquire a permit
* @see #acquirePermit(Duration)
*/
default boolean tryAcquirePermit(Duration maxWaitTime) throws InterruptedException {
return tryAcquirePermits(1, maxWaitTime);
}
/**
* Tries to acquire the requested {@code permits} to perform executions against the rate limiter, waiting up to the
* {@code maxWaitTime} until they are available.
*
* @return whether the requested {@code permits} are successfully acquired or not
* @throws IllegalArgumentException if {@code permits} is < 1
* @throws NullPointerException if {@code maxWaitTime} is null
* @throws InterruptedException if the current thread is interrupted while waiting to acquire the {@code permits}
* @see #acquirePermits(int, Duration)
*/
boolean tryAcquirePermits(int permits, Duration maxWaitTime) throws InterruptedException;
}