Skip to content

Commit

Permalink
#410 decompose core classes in order to its easier re-implementation …
Browse files Browse the repository at this point in the history
…in lua
  • Loading branch information
Vladimir Buhtoyarov committed Oct 15, 2023
1 parent 5283bb7 commit 51efaf6
Show file tree
Hide file tree
Showing 4 changed files with 406 additions and 411 deletions.
228 changes: 227 additions & 1 deletion bucket4j-core/src/main/java/io/github/bucket4j/Bandwidth.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,216 @@ public String getId() {
return id;
}

public static final SerializationHandle<Bandwidth> SERIALIZATION_HANDLE = new SerializationHandle<Bandwidth>() {
public long calculateInitialTokens(long currentTimeNanos) {
if (!useAdaptiveInitialTokens) {
return initialTokens;
}

long timeOfFirstRefillNanos = timeOfFirstRefillMillis * 1_000_000;
if (currentTimeNanos >= timeOfFirstRefillNanos) {
return initialTokens;
}

long guaranteedBase = Math.max(0, capacity - refillTokens);
long nanosBeforeFirstRefill = timeOfFirstRefillNanos - currentTimeNanos;
if (multiplyExactOrReturnMaxValue(nanosBeforeFirstRefill, refillTokens) != Long.MAX_VALUE) {
return Math.min(capacity, guaranteedBase + nanosBeforeFirstRefill * refillTokens / refillPeriodNanos);
} else {
// arithmetic overflow happens.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
return Math.min(capacity, guaranteedBase + (long)((double)nanosBeforeFirstRefill * (double) refillTokens / (double) refillPeriodNanos));
}
}

public long calculateInitialLastRefillTimeNanos(long currentTimeNanos) {
if (!isIntervallyAligned()) {
return currentTimeNanos;
}
return timeOfFirstRefillMillis * 1_000_000 - refillPeriodNanos;
}

public long calculateDelayNanosAfterWillBePossibleToConsume(BucketState64BitsInteger state, int bandwidthIndex, long deficit, long currentTimeNanos) {
if (isRefillIntervally()) {
return calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(state, bandwidthIndex, deficit, currentTimeNanos);
} else {
return calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(state, bandwidthIndex, deficit);
}
}

public long calculateDelayNanosAfterWillBePossibleToConsume(BucketState64BitsInteger state, int bandwidthIndex, long tokens, long currentTimeNanos, boolean checkTokensToConsumeShouldBeLessThenCapacity) {
if (checkTokensToConsumeShouldBeLessThenCapacity && tokens > capacity) {
return Long.MAX_VALUE;
}
long currentSize = state.getCurrentSize(bandwidthIndex);
if (tokens <= currentSize) {
return 0;
}
long deficit = tokens - currentSize;
if (deficit <= 0) {
// math overflow happen
return Long.MAX_VALUE;
}

return calculateDelayNanosAfterWillBePossibleToConsume(state, bandwidthIndex, deficit, currentTimeNanos);
}

public long calculateFullRefillingTime(BucketState64BitsInteger state, int bandwidthIndex, long currentTimeNanos) {
long availableTokens = state.getCurrentSize(bandwidthIndex);
if (availableTokens >= capacity) {
return 0L;
}
long deficit = capacity - availableTokens;

return calculateDelayNanosAfterWillBePossibleToConsume(state, bandwidthIndex, deficit, currentTimeNanos);
}

public void addTokens(BucketState64BitsInteger state, int bandwidthIndex, long tokensToAdd) {
long currentSize = state.getCurrentSize(bandwidthIndex);
long newSize = currentSize + tokensToAdd;
if (newSize >= capacity) {
state.resetBandwidth(bandwidthIndex, capacity);
} else if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just reset bandwidth state
state.resetBandwidth(bandwidthIndex, capacity);
} else {
state.setCurrentSize(bandwidthIndex, newSize);
}
}

public void forceAddTokens(BucketState64BitsInteger state, int bandwidthIndex, long tokensToAdd) {
long currentSize = state.getCurrentSize(bandwidthIndex);
long newSize = currentSize + tokensToAdd;
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just set MAX_VALUE tokens
state.setCurrentSize(bandwidthIndex, Long.MAX_VALUE);
state.setRoundingError(bandwidthIndex, 0);
} else {
state.setCurrentSize(bandwidthIndex, newSize);
}
}

public void refill(BucketState64BitsInteger state, int bandwidthIndex, long currentTimeNanos) {
long previousRefillNanos = state.getLastRefillTimeNanos(bandwidthIndex);
if (currentTimeNanos <= previousRefillNanos) {
return;
}

if (isRefillIntervally()) {
long incompleteIntervalCorrection = (currentTimeNanos - previousRefillNanos) % refillPeriodNanos;
currentTimeNanos -= incompleteIntervalCorrection;
}
if (currentTimeNanos <= previousRefillNanos) {
return;
} else {
state.setLastRefillTimeNanos(bandwidthIndex, currentTimeNanos);
}


final long currentSize = state.getCurrentSize(bandwidthIndex);

if (currentSize >= capacity) {
// can come here if forceAddTokens has been used
return;
}

long durationSinceLastRefillNanos = currentTimeNanos - previousRefillNanos;
long newSize = currentSize;

if (durationSinceLastRefillNanos > refillPeriodNanos) {
long elapsedPeriods = durationSinceLastRefillNanos / refillPeriodNanos;
long calculatedRefill = elapsedPeriods * refillTokens;
newSize += calculatedRefill;
if (newSize > capacity) {
state.resetBandwidth(bandwidthIndex, capacity);
return;
}
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that tokens reached Long.MAX_VALUE tokens.
// just reset bandwidth state
state.resetBandwidth(bandwidthIndex, capacity);
return;
}
durationSinceLastRefillNanos %= refillPeriodNanos;
}


long roundingError = state.getRoundingError(bandwidthIndex);
long dividedWithoutError = multiplyExactOrReturnMaxValue(refillTokens, durationSinceLastRefillNanos);
long divided = dividedWithoutError + roundingError;
if (divided < 0 || dividedWithoutError == Long.MAX_VALUE) {
// arithmetic overflow happens.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
long calculatedRefill = (long) ((double) durationSinceLastRefillNanos / (double) refillPeriodNanos * (double) refillTokens);
newSize += calculatedRefill;
roundingError = 0;
} else {
long calculatedRefill = divided / refillPeriodNanos;
if (calculatedRefill == 0) {
roundingError = divided;
} else {
newSize += calculatedRefill;
roundingError = divided % refillPeriodNanos;
}
}

if (newSize >= capacity) {
state.resetBandwidth(bandwidthIndex, capacity);
return;
}
if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just reset bandwidth state
state.resetBandwidth(bandwidthIndex, capacity);
return;
}
state.setCurrentSize(bandwidthIndex, newSize);
state.setRoundingError(bandwidthIndex, roundingError);
}

private long calculateDelayNanosAfterWillBePossibleToConsumeForIntervalBandwidth(BucketState64BitsInteger state, int index, long deficit, long currentTimeNanos) {
long previousRefillNanos = state.getLastRefillTimeNanos(index);

long timeOfNextRefillNanos = previousRefillNanos + refillPeriodNanos;
long waitForNextRefillNanos = timeOfNextRefillNanos - currentTimeNanos;
if (deficit <= refillTokens) {
return waitForNextRefillNanos;
}

deficit -= refillTokens;
if (deficit < refillTokens) {
return waitForNextRefillNanos + refillPeriodNanos;
}

long deficitPeriods = deficit / refillTokens + (deficit % refillTokens == 0L? 0 : 1);
long deficitNanos = multiplyExactOrReturnMaxValue(deficitPeriods, refillPeriodNanos);
if (deficitNanos == Long.MAX_VALUE) {
// math overflow happen
return Long.MAX_VALUE;
}
deficitNanos += waitForNextRefillNanos;
if (deficitNanos < 0) {
// math overflow happen
return Long.MAX_VALUE;
}
return deficitNanos;
}

private long calculateDelayNanosAfterWillBePossibleToConsumeForGreedyBandwidth(BucketState64BitsInteger state, int index, long deficit) {
long divided = multiplyExactOrReturnMaxValue(refillPeriodNanos, deficit);
if (divided == Long.MAX_VALUE) {
// math overflow happen.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
return (long)((double) deficit / (double) refillTokens * (double)refillPeriodNanos);
} else {
long correctionForPartiallyRefilledToken = state.getRoundingError(index);
divided -= correctionForPartiallyRefilledToken;
return divided / refillTokens;
}
}

public static final SerializationHandle<Bandwidth> SERIALIZATION_HANDLE = new SerializationHandle<>() {
@Override
public <S> Bandwidth deserialize(DeserializationAdapter<S> adapter, S input) throws IOException {
int formatNumber = adapter.readInt(input);
Expand Down Expand Up @@ -316,4 +525,21 @@ public boolean equalsByContent(Bandwidth other) {
useAdaptiveInitialTokens == other.useAdaptiveInitialTokens;
}

// just a copy of JDK method Math#multiplyExact,
// but instead of throwing exception it returns Long.MAX_VALUE in case of overflow
public static long multiplyExactOrReturnMaxValue(long x, long y) {
long r = x * y;
long ax = Math.abs(x);
long ay = Math.abs(y);
if (((ax | ay) >>> 31 != 0)) {
// Some bits greater than 2^31 that might cause overflow
// Check the result using the divide operator
// and check for the special case of Long.MIN_VALUE * -1
if (((y != 0) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1)) {
return Long.MAX_VALUE;
}
}
return r;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ public Bandwidth[] getBandwidths() {
return bandwidths;
}

public int countOfBandwidthsWithNullIdentifiers() {
int count = 0;
for (int i = 0; i < bandwidths.length; i++) {
if (bandwidths[i].getId() == null) {
count++;
}
}
return count;
}

@Override
public boolean equals(Object o) {
if (this == o) { return true; }
Expand Down

0 comments on commit 51efaf6

Please sign in to comment.