
com.univocity.api.io.RateLimiter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of univocity-common-api Show documentation
Show all versions of univocity-common-api Show documentation
univocity public API - Common classes and utilites
The newest version!
/*
* Copyright (c) 2013 Univocity Software Pty Ltd. All rights reserved.
* This file is subject to the terms and conditions defined in file
* 'LICENSE.txt', which is part of this source code package.
*/
package com.univocity.api.io;
import com.univocity.api.common.*;
import org.slf4j.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
/**
* A very simple method call rate limiter, based on a time interval. Users can call {@link #waitAndGo()}
* or {@link #waitAndGo(long)} to block the current thread before performing an operation that is potentially costly
* on other resources (such as HTTP requests to remote servers).
*/
public class RateLimiter {
private static final Logger log = LoggerFactory.getLogger(RateLimiter.class);
private long lastCallTime;
private long waitTimeAdjustment;
private long interval;
private final Object callLock = new Object();
private final AtomicInteger callWaitingCount = new AtomicInteger();
private final String name;
/**
* Creates a new rate limiter configured to allow for a given interval of time
* to elapse before allowing a process to continue.
*
* @param interval the interval to wait between each call to {@link #waitAndGo()}
*/
public RateLimiter(long interval) {
this(null, interval);
}
/**
* Creates a new rate limiter configured to allow for a given interval of time
* to elapse before allowing a process to continue.
*
* @param name a name for the rate limiter, used for logging
* @param interval the interval to wait between each call to {@link #waitAndGo()}
*/
public RateLimiter(String name, long interval) {
Args.positiveOrZero(interval, "Interval");
this.interval = interval;
this.name = Args.isBlank(name) ? "" : name.trim();
}
/**
* Returns the name associated with this rate limiter, used for logging.
* @return the name of this rate limiter
*/
public String getName() {
return name;
}
/**
* Returns the current rate interval
* @return the current interval
*/
public final long getInterval() {
return interval;
}
/**
* Modifies the rate interval
* @param interval the new interval
*/
public void setInterval(long interval) {
Args.positiveOrZero(interval, "Interval");
this.interval = interval;
if (interval == 0L) {
synchronized (callLock) {
callLock.notifyAll();
}
}
}
/**
* Returns the number of threads blocked and waiting for their turn to execute
* @return the number of threads blocked
*/
public final long getWaitingCount() {
return callWaitingCount.get();
}
/**
* Blocks the current process if the previous call to this method happened at a time less than the configured
* interval. The waiting time will be the time difference between the configured interval and the time elapsed
* since the previous call to this method.
*
* @param timeout the maximum length of time the process is allowed to wait. A {@code TimeoutException} will
* be thrown if the process got locked up for too long.
*
*
* @return the number of threads still blocked and waiting.
*
* @throws TimeoutException if the wait time exceeds the given timeout. Will only be thrown after the process
* gets unblocked - at some time potentially much longer than the given timeout.
*/
public final long waitAndGo(long timeout) throws TimeoutException {
return waitAndGo("operation", timeout);
}
/**
* Blocks the current process if the previous call to this method happened at a time less than the configured
* interval. The waiting time will be the time difference between the configured interval and the time elapsed
* since the previous call to this method.
*
* @param action description of the action to be performed after the wait time is over. Used for logging.
*
* @param timeout the maximum length of time the process is allowed to wait. A {@code TimeoutException} will
* be thrown if the process got locked up for too long.
*
*
* @return the number of threads still blocked and waiting.
*
* @throws TimeoutException if the wait time exceeds the given timeout. Will only be thrown after the process
* gets unblocked - at some time potentially much longer than the given timeout.
*/
public final long waitAndGo(String action, long timeout) throws TimeoutException {
long start = 0L;
if (timeout > 0) {
start = System.currentTimeMillis();
}
callWaitingCount.incrementAndGet();
synchronized (callLock) {
long time = System.currentTimeMillis();
if (timeout > 0 && time - start > timeout) {
throw new TimeoutException(name + " rate limiter: " + action + " timed out after " + (time - start) + "ms");
}
doWait(time, action);
}
return callWaitingCount.decrementAndGet();
}
/**
* Blocks the current process if the previous call to this method happened at a time less than the configured
* interval. The waiting time will be the time difference between the configured interval and the time elapsed
* since the previous call to this method.
*
* @return the number of threads still blocked and waiting.
*/
public final long waitAndGo() {
return waitAndGo("proceeding");
}
/**
* Blocks the current process if the previous call to this method happened at a time less than the configured
* interval. The waiting time will be the time difference between the configured interval and the time elapsed
* since the previous call to this method.
*
* @param action description of the action to be performed after the wait time is over. Used for logging.
*
* @return the number of threads still blocked and waiting.
*/
public final long waitAndGo(String action) {
callWaitingCount.incrementAndGet();
synchronized (callLock) {
doWait(System.currentTimeMillis(), action);
}
return callWaitingCount.decrementAndGet();
}
private void doWait(long currentTime, String action) {
long waitTime = lastCallTime == 0 ? 0 : (interval + waitTimeAdjustment) - (currentTime - lastCallTime);
waitTimeAdjustment = 0L;
if (waitTime > 0L) {
try {
log.debug("{} rate limiter active: waiting {} ms before {}", name, waitTime, action);
Thread.sleep(waitTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
lastCallTime = System.currentTimeMillis();
}
/**
* Decreases the wait time of the next process that will be put to wait.
* The wait time returns back to the interval provided in the constructor of this
* class after the next process is blocked
*
* @param timeToDecrease length of time to decrease from the configured interval.
*/
public final void decreaseWaitTime(long timeToDecrease) {
Args.positive(timeToDecrease, "Time to decrease");
waitTimeAdjustment = -timeToDecrease;
}
/**
* Increases the wait time of the next process that will be put to wait.
* The wait time returns back to the interval provided in the constructor of this
* class after the next process is blocked
*
* @param timeToIncrease length of time add to the configured interval.
*/
public final void increaseWaitTime(long timeToIncrease) {
Args.positive(timeToIncrease, "Time to increase");
waitTimeAdjustment = timeToIncrease;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy