-
-
Save lwiechec/bd9a33963d39a3853dc2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Class that throttles requests. Ensures that the StartRequest cannot be called | |
* more than a given amount of time in any given interval. | |
* | |
* StartRequest blocks until this requirement is satisfied. | |
* | |
* TryStartRequest returns 0 if the request was cleared, or a non-0 number of | |
* millisecond to sleep before the next attempt. | |
* | |
* Simple usage, 10 requests per second: | |
* | |
* Throttler t = new Throttler(10, 1000); ... ServiceRequest(Throtter t, ...) { | |
* t.StartRequest(); .. do work .. | |
* | |
* @author [email protected] (Sergey Solyanik) | |
* http://1-800-magic.blogspot.com/2008/02/throttling-requests-java.html | |
* | |
*/ | |
public class RequestThrottler { | |
/** | |
* The interval within we're ensuring max number of calls. | |
*/ | |
private final long _interval; | |
/** | |
* The maximum number of calls that can be made within the interval. | |
*/ | |
private final int _maxCalls; | |
/** | |
* Previous calls within the interval. | |
*/ | |
private final long[] _ticks; | |
/** | |
* Available element at the insertion point (back of the queue). | |
*/ | |
private int _tickNext; | |
/** | |
* Element at the removal point (front of the queue). | |
*/ | |
private int _tickLast; | |
/** | |
* the time when the last expired request occured. might be used to auto | |
* disable some services all together | |
*/ | |
private long _lastExpiredMaxWait = 0; | |
/** | |
* Constructor. | |
* | |
* @param maxCalls | |
* Max number of calls that can be made within the interval. | |
* @param interval | |
* The interval. | |
*/ | |
public RequestThrottler(final int maxCalls, final long interval) { | |
if (maxCalls < 1) { | |
throw new IllegalArgumentException("maxCalls must be >=1, was " + maxCalls); | |
} | |
if (interval < 1) { | |
throw new IllegalArgumentException("interval must be >=1, was " + interval); | |
} | |
_interval = interval; | |
_maxCalls = maxCalls + 1; | |
_ticks = new long[_maxCalls]; | |
_tickLast = _tickNext = 0; | |
} | |
/** | |
* Returns the next element in the queue. | |
* | |
* @param index | |
* The element for which to compute the next. | |
* @return | |
*/ | |
private int next(int index) { | |
index += 1; | |
return index < _maxCalls ? index : 0; | |
} | |
/** | |
* Attempts to clear the request. | |
* | |
* @return Returns 0 if successful, or a time hint (ms) in which we should | |
* attempt to clear it again. | |
*/ | |
public synchronized long tryStartRequest() { | |
long result = 0; | |
final long now = System.currentTimeMillis(); | |
while (_tickLast != _tickNext) { | |
if (now - _ticks[_tickLast] < _interval) { | |
break; | |
} | |
_tickLast = next(_tickLast); | |
} | |
final int next = next(_tickNext); | |
if (next != _tickLast) { | |
_ticks[_tickNext] = now; | |
_tickNext = next; | |
} else { | |
result = _interval - (now - _ticks[_tickLast]); | |
} | |
return result; | |
} | |
/** | |
* Clears the request. Blocks until the request can execute. | |
*/ | |
public void startRequest() { | |
startRequest(Integer.MAX_VALUE); | |
} | |
/** | |
* Clears the request. Blocks until the request can execute or until waxWait | |
* would be exceeded. | |
* | |
* @return true if successful or false if request should not execute | |
*/ | |
public boolean startRequest(final int maxWait) { | |
long sleep; | |
long total = 0; | |
while ((sleep = tryStartRequest()) > 0) { | |
if (maxWait > 0 && (total += sleep) > maxWait) { | |
_lastExpiredMaxWait = System.currentTimeMillis(); | |
return false; | |
} else { | |
try { | |
Thread.sleep(sleep); | |
} catch (final InterruptedException ex) { | |
ex.printStackTrace(); | |
} | |
} | |
} | |
return true; | |
} | |
public long getLastExpiredMaxWait() { | |
return _lastExpiredMaxWait; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment