This class provides bandwidth management using First-In-First-Out queues for both inbound and outbound traffic. It implements token bucket algorithm to control data rates and prevent burst traffic patterns.
Core Features:
- FIFO request queuing for fair bandwidth allocation
- Token bucket rate limiting
- Separate inbound and outbound management
- Configurable bandwidth limits and refill rates
- Thread-safe operations with atomic counters
- Request satisfaction and partial fulfillment handling
Concurrency Strategy:
- Java 5: Used synchronized ArrayList with head/tail access
- Java 6+: Uses LinkedBlockingDeque for lock-free operations
- Request polling from queue head for efficiency
- Partial request satisfaction with push-back mechanism
Algorithm:
- Token refill at fixed intervals
- Request processing when tokens available
- Burst prevention through token depletion
- Priority-based request handling
Nested Class Summary
Modifier and TypeClassDescriptionstatic interfaceListener for bandwidth request completion events.private static classA request that always succeeds without capacity.static interfaceA bandwidth request, either inbound or outbound.private static classOne bandwidth capacity request.Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final AtomicLongCounter handing out request ids.private final AtomicIntegerHow many bytes we can consume for inbound transmission immediately.private final AtomicIntegerHow many bytes we can consume for outbound transmission immediately.private final RouterContextThe router context.private booleanShortcut of whether our inbound rate is unlimited - UNUSED, always false for now.private longWhen the rate stats were last updated.private longTotal received when the stats were last updated.private longTotal sent when the stats were last updated.private final LogLogger for the bandwidth limiter.private intHow large _availableInbound can get - aka our inbound rate during a burst.private intHow large _unavailableInbound can get.private intHow large _availableOutbound can get - aka our outbound rate during a burst.private intHow large _unavailableInbound can get.private static final FIFOBandwidthLimiter.NoopRequestThe shared request that consumes no capacity.private booleanShortcut of whether our outbound rate is unlimited - UNUSED, always false for now.private final List<FIFOBandwidthLimiter.SimpleRequest> Inbound requests waiting for capacity.private final List<FIFOBandwidthLimiter.SimpleRequest> Outbound requests waiting for capacity.private floatCached receive rate, in bytes per second.private floatCached receive rate over 15 seconds.private final FIFOBandwidthRefillerJob that refills the capacity buckets.private final ThreadThread running the refiller.private floatCached send rate, in bytes per second.private floatCached send rate over 15 seconds.private final AtomicLongLifetime counter of bytes received.private final AtomicLongLifetime counter of bytes sent.private final AtomicIntegerHow many bytes we can queue up for bursting.private final AtomicIntegerHow many bytes we can queue up for bursting.Constructor Summary
ConstructorsConstructorDescriptionFIFOBandwidthLimiter(RouterContext context) Creates a limiter with empty queues and starts the background refiller.Method Summary
Modifier and TypeMethodDescriptionprivate voidclear()Clear all pending requests and reset counters.intOut bandwidth.intIn bandwidth.intThe max inbound burst, in bytes.intThe configured maximum, not the current rate.intThe configured maximum, not the current rate.private StringBuilderThe inbound bandwidth status.intIn Bytes per second.intThe max outbound burst, in bytes.intThe configured maximum, not the current rate.intThe configured maximum, not the current rate.private StringBuilderThe outbound bandwidth status.floatSmoothed one-second receive rate.floatSmoothed 15-second receive rate.floatSmoothed one-second send rate.floatSmoothed 15-second send rate.(package private) StringBuilderThe current status string.longTotal bytes allocated for inbound messages since start.longTotal bytes allocated for outbound messages since start.private longCalled from debug logging only.private longCalled from debug logging only.private final voidWe have limits, so iterate through the requests, allocating as much bandwidth as we can to those who have used what we have given them and are waiting for more (giving priority to the first ones who requested it).private final voidThere are no limits, so just give every inbound request whatever they want.private final voidWe have limits, so iterate through the requests, allocating as much bandwidth as we can to those who have used what we have given them and are waiting for more (giving priority to the first ones who requested it).private final voidThere are no limits, so just give every outbound request whatever they want.longnow()Current time in milliseconds from the System clock.booleanreceivedParticipatingMessage(int size, float factor) Check if we should accept an inbound participating message.(package private) final voidrefillBandwidthQueues(List<FIFOBandwidthLimiter.Request> buf, long bytesInbound, long bytesOutbound, long maxBurstIn, long maxBurstOut) More bytes are available - add them to the queue and satisfy any requests we can.voidClear and reinitialize the refiller and queues.requestInbound(int bytesIn, String purpose) Request some bytes.requestInbound(FIFOBandwidthLimiter.Request candidate, int bytesIn, String purpose) Request some bytes, reusing the given request if possible.private voidrequestInbound(FIFOBandwidthLimiter.SimpleRequest req, int bytesIn, String purpose) The transports don't use this any more, so make it private and a SimpleRequest instead of a Request So there's no more casting.requestOutbound(int bytesOut, int priority, String purpose) Request some bytes.requestOutbound(FIFOBandwidthLimiter.Request candidate, int bytesOut, int priority, String purpose) Request some bytes, reusing the given request if possible.private voidrequestOutbound(FIFOBandwidthLimiter.SimpleRequest req, int bytesOut, String purpose) Queues an outbound request for capacity.private booleanresetIfReusable(FIFOBandwidthLimiter.SimpleRequest sr, int bytesIn, int priority) Reset a previously allocated SimpleRequest for reuse if it is complete (fully allocated) and not aborted.private booleanresetIfReusableOutbound(FIFOBandwidthLimiter.SimpleRequest sr, int bytesOut, int priority) Reset a previously allocated SimpleRequest for reuse if it is complete (fully allocated) and not aborted.private final voidsatisfyInboundRequests(List<FIFOBandwidthLimiter.Request> satisfied) Satisfy the pending inbound requests with currently available bandwidth.private final voidsatisfyOutboundRequests(List<FIFOBandwidthLimiter.Request> satisfied) Satisfy the pending outbound requests with currently available bandwidth.private final voidGo through the queue, satisfying as many requests as possible (notifying each one satisfied that the request has been granted).booleansentParticipatingMessage(int size, float factor) We intend to send traffic for a participating tunnel with the given size and adjustment factor.(package private) voidsetInboundBurstBytes(int bytes) Inbound burst maximum, in bytes.(package private) voidsetInboundBurstKBps(int kbytesPerSecond) Inbound burst rate in KBps.(package private) voidsetOutboundBurstBytes(int bytes) Outbound burst maximum, in bytes.(package private) voidsetOutboundBurstKBps(int kbytesPerSecond) Outbound burst rate in KBps.private booleanshortcutSatisfyInboundRequest(int requested) Lockless total satisfaction, at some minor risk of exceeding the limits and driving the available counter below zero.private booleanshortcutSatisfyOutboundRequest(int requested) Lockless total satisfaction, at some minor risk of exceeding the limits and driving the available counter below zero.voidshutdown()Shut down the bandwidth limiter.private voidRecomputes the cached rate statistics.
Field Details
_log
Logger for the bandwidth limiter._context
The router context._pendingInboundRequests
Inbound requests waiting for capacity._pendingOutboundRequests
Outbound requests waiting for capacity._availableInbound
How many bytes we can consume for inbound transmission immediately._availableOutbound
How many bytes we can consume for outbound transmission immediately._maxInboundBurst
private volatile int _maxInboundBurstHow large _unavailableInbound can get._maxOutboundBurst
private volatile int _maxOutboundBurstHow large _unavailableInbound can get._maxInbound
private volatile int _maxInboundHow large _availableInbound can get - aka our inbound rate during a burst._maxOutbound
private volatile int _maxOutboundHow large _availableOutbound can get - aka our outbound rate during a burst._outboundUnlimited
private volatile boolean _outboundUnlimitedShortcut of whether our outbound rate is unlimited - UNUSED, always false for now._inboundUnlimited
private volatile boolean _inboundUnlimitedShortcut of whether our inbound rate is unlimited - UNUSED, always false for now._totalAllocatedInboundBytes
Lifetime counter of bytes received._totalAllocatedOutboundBytes
Lifetime counter of bytes sent.__requestId
Counter handing out request ids._refiller
Job that refills the capacity buckets._refillerThread
Thread running the refiller._lastTotalSent
private volatile long _lastTotalSentTotal sent when the stats were last updated._lastTotalReceived
private volatile long _lastTotalReceivedTotal received when the stats were last updated._lastStatsUpdated
private volatile long _lastStatsUpdatedWhen the rate stats were last updated._sendBps
private volatile float _sendBpsCached send rate, in bytes per second._recvBps
private volatile float _recvBpsCached receive rate, in bytes per second._sendBps15s
private volatile float _sendBps15sCached send rate over 15 seconds._recvBps15s
private volatile float _recvBps15sCached receive rate over 15 seconds._noop
The shared request that consumes no capacity.
Constructor Details
FIFOBandwidthLimiter
Creates a limiter with empty queues and starts the background refiller.- Parameters:
context- the router context, used for the clock, logging, statistics and the refiller
Method Details
now
public long now()Current time in milliseconds from the System clock.- Returns:
- milliseconds since the epoch, from
System.currentTimeMillis()
getTotalAllocatedInboundBytes
public long getTotalAllocatedInboundBytes()Total bytes allocated for inbound messages since start.- Returns:
- the total allocated inbound bytes
getTotalAllocatedOutboundBytes
public long getTotalAllocatedOutboundBytes()Total bytes allocated for outbound messages since start.- Returns:
- the total allocated outbound bytes
getSendBps
public float getSendBps()Smoothed one-second send rate.- Returns:
- smoothed one second rate
getReceiveBps
public float getReceiveBps()Smoothed one-second receive rate.- Returns:
- smoothed one second rate
getSendBps15s
public float getSendBps15s()Smoothed 15-second send rate.- Returns:
- smoothed 15 second rate
getReceiveBps15s
public float getReceiveBps15s()Smoothed 15-second receive rate.- Returns:
- smoothed 15 second rate
getOutboundKBytesPerSecond
public int getOutboundKBytesPerSecond()The configured maximum, not the current rate. In binary K, i.e. rate / 1024.- Returns:
- the outbound k bytes per second
getInboundKBytesPerSecond
public int getInboundKBytesPerSecond()The configured maximum, not the current rate. In binary K, i.e. rate / 1024.- Returns:
- the inbound k bytes per second
getOutboundBurstKBytesPerSecond
public int getOutboundBurstKBytesPerSecond()The configured maximum, not the current rate. In binary K, i.e. rate / 1024.- Returns:
- the outbound burst k bytes per second
getInboundBurstKBytesPerSecond
public int getInboundBurstKBytesPerSecond()The configured maximum, not the current rate. In binary K, i.e. rate / 1024.- Returns:
- the inbound burst k bytes per second
reinitialize
public void reinitialize()Clear and reinitialize the refiller and queues.shutdown
public void shutdown()Shut down the bandwidth limiter.clear
private void clear()Clear all pending requests and reset counters.sentParticipatingMessage
public boolean sentParticipatingMessage(int size, float factor) We intend to send traffic for a participating tunnel with the given size and adjustment factor. Returns true if the message can be sent within the current share bandwidth limits, or false if it should be dropped.- Parameters:
size- the size of the participating message in bytesfactor- multiplier of size for the drop calculation, 1 for no adjustment- Returns:
- true for accepted, false for drop
receivedParticipatingMessage
public boolean receivedParticipatingMessage(int size, float factor) Check if we should accept an inbound participating message.- Parameters:
size- the size of the participating message in bytesfactor- multiplier of size for the drop calculation, 1 for no adjustment- Returns:
- true for accepted, false for drop
getCurrentParticipatingBandwidth
public int getCurrentParticipatingBandwidth()Out bandwidth. Actual bandwidth, not smoothed, not bucketed.- Returns:
- Bps in recent period (a few seconds)
getCurrentParticipatingBandwidthIn
public int getCurrentParticipatingBandwidthIn()In bandwidth. Actual bandwidth, not smoothed, not bucketed.- Returns:
- Bps in recent period (a few seconds)
requestInbound
Request some bytes. Does not block.- Parameters:
bytesIn- the number of bytes requestedpurpose- the purpose of the request, for logging- Returns:
- the request to use; never null
requestInbound
public FIFOBandwidthLimiter.Request requestInbound(FIFOBandwidthLimiter.Request candidate, int bytesIn, String purpose) Request some bytes, reusing the given request if possible. Does not block.If the candidate is a SimpleRequest that is complete (fully allocated and not aborted), it is reset and re-queued, avoiding a new allocation. If the candidate is the shared no-op request and the shortcut still applies, it is returned as-is.
The reuse check, reset, and re-queue are serialized on the pending inbound queue so the SimpleRequest is never doubled up by concurrent allocators or concurrent reuse of the same candidate.
- Parameters:
candidate- the request from a previous allocation for this consumer, or null for a new requestbytesIn- the number of bytes requestedpurpose- the purpose of the request, for logging- Returns:
- the request to use; never null
resetIfReusable
Reset a previously allocated SimpleRequest for reuse if it is complete (fully allocated) and not aborted. The check and the reset are performed together under the pending inbound queue lock, so the request cannot be observed by an allocator, reset twice, or re-queued by two threads.This deliberately acquires the pending queue lock before the request lock, matching the ordering used by the satisfy/refill paths. Acquiring them in the opposite order (request lock then queue lock) can deadlock against a refill pass, so callers of this method must never hold a SimpleRequest lock while taking the inbound queue lock.
- Parameters:
sr- the SimpleRequest to consider for reuse; never the shared no-opbytesIn- the number of bytes for the reused requestpriority- 0 for now- Returns:
- true if the request was reset and should be re-queued
requestInbound
The transports don't use this any more, so make it private and a SimpleRequest instead of a Request So there's no more casting.- Parameters:
req- the request to enqueue, satisfied when bytes are availablebytesIn- the number of inbound bytes wantedpurpose- label naming the requester, currently unused
requestOutbound
Request some bytes. Does not block.- Parameters:
bytesOut- the number of bytes requestedpriority- 0 for nowpurpose- the purpose of the request, for logging- Returns:
- the request to use; never null
requestOutbound
public FIFOBandwidthLimiter.Request requestOutbound(FIFOBandwidthLimiter.Request candidate, int bytesOut, int priority, String purpose) Request some bytes, reusing the given request if possible. Does not block.If the candidate is a SimpleRequest that is complete (fully allocated and not aborted), it is reset and re-queued, avoiding a new allocation. If the candidate is the shared no-op request and the shortcut still applies, it is returned as-is.
- Parameters:
candidate- the request from a previous allocation for this consumer, or null for a new requestbytesOut- the number of bytes requestedpriority- 0 for nowpurpose- the purpose of the request, for logging- Returns:
- the request to use; never null
resetIfReusableOutbound
private boolean resetIfReusableOutbound(FIFOBandwidthLimiter.SimpleRequest sr, int bytesOut, int priority) Reset a previously allocated SimpleRequest for reuse if it is complete (fully allocated) and not aborted. The check and the reset are performed together under the pending outbound queue lock, so the request cannot be observed by an allocator, reset twice, or re-queued by two threads.This deliberately acquires the pending queue lock before the request lock, matching the ordering used by the satisfy/refill paths. Acquiring them in the opposite order (request lock then queue lock) can deadlock against a refill pass, so callers of this method must never hold a SimpleRequest lock while taking the outbound queue lock.
- Parameters:
sr- the SimpleRequest to consider for reuse; never the shared no-opbytesOut- the number of bytes for the reused requestpriority- 0 for now- Returns:
- true if the request was reset and should be re-queued
requestOutbound
Queues an outbound request for capacity.- Parameters:
req- the request to queuebytesOut- bytes of capacity wantedpurpose- the request purpose for the log
setInboundBurstKBps
void setInboundBurstKBps(int kbytesPerSecond) Inbound burst rate in KBps.- Parameters:
kbytesPerSecond- the new inbound burst rate in KBps, stored internally in bytes
setOutboundBurstKBps
void setOutboundBurstKBps(int kbytesPerSecond) Outbound burst rate in KBps.- Parameters:
kbytesPerSecond- the new outbound burst rate in KBps, stored internally in bytes
getInboundBurstBytes
public int getInboundBurstBytes()The max inbound burst, in bytes.- Returns:
- the inbound burst bytes
getOutboundBurstBytes
public int getOutboundBurstBytes()The max outbound burst, in bytes.- Returns:
- the outbound burst bytes
setInboundBurstBytes
void setInboundBurstBytes(int bytes) Inbound burst maximum, in bytes.- Parameters:
bytes- the new inbound burst maximum, in bytes per second
setOutboundBurstBytes
void setOutboundBurstBytes(int bytes) Outbound burst maximum, in bytes.- Parameters:
bytes- the new outbound burst maximum, in bytes per second
getStatus
StringBuilder getStatus()The current status string.- Returns:
- the available, max, burst and burst-max rates for both directions
getInboundStatus
getOutboundStatus
refillBandwidthQueues
final void refillBandwidthQueues(List<FIFOBandwidthLimiter.Request> buf, long bytesInbound, long bytesOutbound, long maxBurstIn, long maxBurstOut) More bytes are available - add them to the queue and satisfy any requests we can.- Parameters:
buf- contains satisfied outbound requests, really just to avoid object thrash, not really usedbytesInbound- allowance to add to the inbound queue, in bytes, for the elapsed periodbytesOutbound- allowance to add to the outbound queue, in bytes, for the elapsed periodmaxBurstIn- allow up to this many bytes in from the burst section for this time period (may be negative)maxBurstOut- allow up to this many bytes in from the burst section for this time period (may be negative)
updateStats
private void updateStats()Recomputes the cached rate statistics.satisfyRequests
Go through the queue, satisfying as many requests as possible (notifying each one satisfied that the request has been granted).- Parameters:
buffer- Out parameter, returned with the satisfied outbound requests only
satisfyInboundRequests
Satisfy the pending inbound requests with currently available bandwidth.- Parameters:
satisfied- Out parameter, returned with the satisfied requests added
locked_getLongestInboundWait
private long locked_getLongestInboundWait()Called from debug logging only.- Returns:
- how long the longest-waiting inbound request has waited in ms, 0 if none pending
locked_getLongestOutboundWait
private long locked_getLongestOutboundWait()Called from debug logging only.- Returns:
- how long the longest-waiting outbound request has waited in ms, 0 if none pending
locked_satisfyInboundUnlimited
There are no limits, so just give every inbound request whatever they want.- Parameters:
satisfied- out param, list of requests that were completely satisfied
locked_satisfyInboundAvailable
We have limits, so iterate through the requests, allocating as much bandwidth as we can to those who have used what we have given them and are waiting for more (giving priority to the first ones who requested it).- Parameters:
satisfied- out param, list of requests that were completely satisfied
satisfyOutboundRequests
Satisfy the pending outbound requests with currently available bandwidth.- Parameters:
satisfied- Out parameter, returned with the satisfied requests added
locked_satisfyOutboundUnlimited
There are no limits, so just give every outbound request whatever they want.- Parameters:
satisfied- out param, list of requests that were completely satisfied
locked_satisfyOutboundAvailable
We have limits, so iterate through the requests, allocating as much bandwidth as we can to those who have used what we have given them and are waiting for more (giving priority to the first ones who requested it).- Parameters:
satisfied- out param, list of requests that were completely satisfied
shortcutSatisfyInboundRequest
private boolean shortcutSatisfyInboundRequest(int requested) Lockless total satisfaction, at some minor risk of exceeding the limits and driving the available counter below zero.- Parameters:
requested- number of bytes- Returns:
- true if the request was satisfied outright, false if it was queued
shortcutSatisfyOutboundRequest
private boolean shortcutSatisfyOutboundRequest(int requested) Lockless total satisfaction, at some minor risk of exceeding the limits and driving the available counter below zero.- Parameters:
requested- number of bytes- Returns:
- true if the request was satisfied outright, false if it was queued