Skip to content

Commit

Permalink
#39 implementation for fixed interval refill
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-bukhtoyarov committed Aug 9, 2017
1 parent ad0097e commit 04f84fc
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 68 deletions.
2 changes: 1 addition & 1 deletion bucket4j-benchmarks/pom.xml
Expand Up @@ -24,7 +24,7 @@
<parent>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../bucket4j-parent</relativePath>
</parent>
<artifactId>bucket4j-benchmarks</artifactId>
Expand Down
10 changes: 9 additions & 1 deletion bucket4j-core/pom.xml
Expand Up @@ -23,7 +23,7 @@
<parent>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../bucket4j-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -53,4 +53,12 @@
</plugins>
</build>

<dependencies>
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>

</project>
92 changes: 53 additions & 39 deletions bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java
Expand Up @@ -18,13 +18,12 @@
package io.github.bucket4j;

import java.io.Serializable;
import java.time.Instant;
import java.util.Arrays;

public class BucketState implements Serializable {

private static final int LAST_REFILL_TIME_OFFSET = 0;

final long[] stateData;
private final long[] stateData;

BucketState(long[] stateData) {
this.stateData = stateData;
Expand All @@ -34,17 +33,39 @@ public BucketState(BucketConfiguration configuration) {
Bandwidth[] bandwidths = configuration.getBandwidths();
long[] bandwidthsInitialTokens = configuration.getBandwidthsInitialTokens();

this.stateData = new long[1 + bandwidths.length * 2];
this.stateData = new long[bandwidths.length * 2];
long currentTimeNanos = configuration.getTimeMeter().currentTimeNanos();
for(int i = 0; i < bandwidths.length; i++) {
Bandwidth bandwidth = bandwidths[i];
long initialTokens = bandwidthsInitialTokens[i];
if (initialTokens == BucketConfiguration.INITIAL_TOKENS_UNSPECIFIED) {
initialTokens = bandwidth.capacity;
}
long initialTokens = calculateInitialTokens(bandwidthsInitialTokens[i], bandwidth);
setCurrentSize(i, initialTokens);

long lastRefillNanos = calculateInitialRefillNanos(bandwidth, currentTimeNanos);
setLastRefillTimeNanos(i, lastRefillNanos);
}
}

private long calculateInitialRefillNanos(Bandwidth bandwidth, long currentTimeNanos) {
Refill refill = bandwidth.refill;
if (!refill.isIntervally()) {
return currentTimeNanos;
}
Instant firstRefillTime = refill.getFirstRefillTime();
if (firstRefillTime == null) {
return currentTimeNanos;
}

// TODO
//long timeOfDesiredFirstRefill = firstRefillTime.toEpochMilli() *
return 0;
}

private long calculateInitialTokens(long bandwidthsInitialToken, Bandwidth bandwidth) {
long initialTokens = bandwidthsInitialToken;
if (initialTokens == BucketConfiguration.INITIAL_TOKENS_UNSPECIFIED) {
initialTokens = bandwidth.capacity;
}
setLastRefillTimeNanos(currentTimeNanos);
return initialTokens;
}

public BucketState copy() {
Expand All @@ -59,14 +80,6 @@ public static BucketState createInitialState(BucketConfiguration configuration)
return new BucketState(configuration);
}

public long getAvailableTokens(Bandwidth[] bandwidths) {
long availableTokens = getCurrentSize(0);
for (int i = 1; i < bandwidths.length; i++) {
availableTokens = Math.min(availableTokens, getCurrentSize(i));
}
return availableTokens;
}

public void consume(Bandwidth[] bandwidths, long toConsume) {
for (int i = 0; i < bandwidths.length; i++) {
consume(i, toConsume);
Expand All @@ -86,15 +99,13 @@ public long delayNanosAfterWillBePossibleToConsume(Bandwidth[] bandwidths, long
return delayAfterWillBePossibleToConsume;
}

public void refillAllBandwidth(Bandwidth[] limits, long currentTimeNanos) {
long lastRefillTimeNanos = getLastRefillTimeNanos();
if (currentTimeNanos <= lastRefillTimeNanos) {
return;
}
for (int i = 0; i < limits.length; i++) {
refill(i, limits[i], lastRefillTimeNanos, currentTimeNanos);
public long refillAllBandwidth(Bandwidth[] bandwidths, long currentTimeNanos) {
long totalAvailableTokens = refill(0, bandwidths[0], currentTimeNanos);
for (int i = 1; i < bandwidths.length; i++) {
long availableTokens = refill(i, bandwidths[i], currentTimeNanos);
totalAvailableTokens = Math.min(totalAvailableTokens, availableTokens);
}
setLastRefillTimeNanos(currentTimeNanos);
return totalAvailableTokens;
}

public void addTokens(Bandwidth[] limits, long tokensToAdd) {
Expand All @@ -114,11 +125,14 @@ private void addTokens(int bandwidthIndex, Bandwidth bandwidth, long tokensToAdd
}
}

private void consume(int bandwidth, long tokens) {
stateData[1 + bandwidth * 2] -= tokens;
}
private long refill(int bandwidthIndex, Bandwidth bandwidth, long currentTimeNanos) {
long previousRefillNanos;

long lastRefillTimeNanos = getLastRefillTimeNanos();
if (currentTimeNanos <= lastRefillTimeNanos) {
return;
}

private void refill(int bandwidthIndex, Bandwidth bandwidth, long previousRefillNanos, long currentTimeNanos) {
final long capacity = bandwidth.capacity;
long currentSize = getCurrentSize(bandwidthIndex);

Expand Down Expand Up @@ -162,28 +176,28 @@ private long delayNanosAfterWillBePossibleToConsume(int bandwidthIndex, Bandwidt
return periodNanos * deficit / bandwidth.refill.getTokens();
}

long getCurrentSize(int bandwidth) {
return stateData[1 + bandwidth * 2];
private long getCurrentSize(int bandwidth) {
return stateData[bandwidth * 2];
}

long getRoundingError(int bandwidth) {
return stateData[2 + bandwidth * 2];
private void setCurrentSize(int bandwidth, long currentSize) {
stateData[bandwidth * 2] = currentSize;
}

private void setCurrentSize(int bandwidth, long currentSize) {
stateData[1 + bandwidth * 2] = currentSize;
private void consume(int bandwidth, long tokens) {
stateData[bandwidth * 2] -= tokens;
}

private void setRoundingError(int bandwidth, long roundingError) {
stateData[2 + bandwidth * 2] = roundingError;
}

private long getLastRefillTimeNanos() {
return stateData[LAST_REFILL_TIME_OFFSET];
private long getLastRefillTimeNanos(int bandwidth) {
return stateData[bandwidth * 2 + 1];
}

private void setLastRefillTimeNanos(long nanos) {
stateData[LAST_REFILL_TIME_OFFSET] = nanos;
private void setLastRefillTimeNanos(int bandwidth, long lastRefillNanos) {
stateData[bandwidth * 2 + 1] = lastRefillNanos;
}

@Override
Expand Down
5 changes: 5 additions & 0 deletions bucket4j-core/src/main/java/io/github/bucket4j/Refill.java
Expand Up @@ -90,6 +90,10 @@ public static Refill fixedInterval(long tokens, Duration period, Instant timeOfF
return new Refill(tokens, period, true, timeOfFirstRefill);
}

public boolean isIntervally() {
return intervally;
}

public long getPeriodNanos() {
return periodNanos;
}
Expand All @@ -113,6 +117,7 @@ public String toString() {
", tokens=" + tokens +
", intervally=" + intervally +
", firstRefillTime=" + firstRefillTime +
", nanosPerToken=" + nanosPerToken +
'}';
}

Expand Down
Expand Up @@ -36,8 +36,8 @@ public Long execute(GridBucketState gridState) {
BucketState state = gridState.getBucketState();
long currentTimeNanos = configuration.getTimeMeter().currentTimeNanos();
Bandwidth[] bandwidths = configuration.getBandwidths();
state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);

long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
long toConsume = Math.min(limit, availableToConsume);
if (toConsume <= 0) {
return 0l;
Expand Down
Expand Up @@ -14,8 +14,7 @@ public Long execute(GridBucketState gridState) {
Bandwidth[] bandwidths = configuration.getBandwidths();
long currentTimeNanos = configuration.getTimeMeter().currentTimeNanos();

state.refillAllBandwidth(bandwidths, currentTimeNanos);
return state.getAvailableTokens(bandwidths);
return state.refillAllBandwidth(bandwidths, currentTimeNanos);
}

@Override
Expand Down
Expand Up @@ -22,8 +22,7 @@ public ConsumptionProbe execute(GridBucketState gridState) {
long currentTimeNanos = configuration.getTimeMeter().currentTimeNanos();
Bandwidth[] bandwidths = configuration.getBandwidths();

state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);
long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume <= availableToConsume) {
state.consume(bandwidths, tokensToConsume);
bucketStateModified = true;
Expand Down
Expand Up @@ -37,8 +37,7 @@ public Boolean execute(GridBucketState gridState) {
long currentTimeNanos = configuration.getTimeMeter().currentTimeNanos();
Bandwidth[] bandwidths = configuration.getBandwidths();

state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);
long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume <= availableToConsume) {
state.consume(bandwidths, tokensToConsume);
bucketStateModified = true;
Expand Down
Expand Up @@ -44,8 +44,7 @@ protected long consumeAsMuchAsPossibleImpl(long limit) {
long currentTimeNanos = timeMeter.currentTimeNanos();

while (true) {
newState.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = newState.getAvailableTokens(bandwidths);
long availableToConsume = newState.refillAllBandwidth(bandwidths, currentTimeNanos);
long toConsume = Math.min(limit, availableToConsume);
if (toConsume == 0) {
return 0;
Expand All @@ -67,8 +66,7 @@ protected boolean tryConsumeImpl(long tokensToConsume) {
long currentTimeNanos = timeMeter.currentTimeNanos();

while (true) {
newState.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = newState.getAvailableTokens(bandwidths);
long availableToConsume = newState.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume > availableToConsume) {
return false;
}
Expand All @@ -89,8 +87,7 @@ protected ConsumptionProbe tryConsumeAndReturnRemainingTokensImpl(long tokensToC
long currentTimeNanos = timeMeter.currentTimeNanos();

while (true) {
newState.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = newState.getAvailableTokens(bandwidths);
long availableToConsume = newState.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume > availableToConsume) {
long nanosToWaitForRefill = newState.delayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume);
return ConsumptionProbe.rejected(availableToConsume, nanosToWaitForRefill);
Expand Down Expand Up @@ -168,8 +165,7 @@ protected void addTokensImpl(long tokensToAdd) {
public long getAvailableTokens() {
long currentTimeNanos = timeMeter.currentTimeNanos();
BucketState snapshot = stateReference.get().copy();
snapshot.refillAllBandwidth(bandwidths, currentTimeNanos);
return snapshot.getAvailableTokens(bandwidths);
return snapshot.refillAllBandwidth(bandwidths, currentTimeNanos);
}

@Override
Expand Down
Expand Up @@ -48,8 +48,7 @@ protected long consumeAsMuchAsPossibleImpl(long limit) {
long currentTimeNanos = timeMeter.currentTimeNanos();
lock.lock();
try {
state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);
long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
long toConsume = Math.min(limit, availableToConsume);
if (toConsume == 0) {
return 0;
Expand All @@ -66,8 +65,7 @@ protected boolean tryConsumeImpl(long tokensToConsume) {
long currentTimeNanos = timeMeter.currentTimeNanos();
lock.lock();
try {
state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);
long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume > availableToConsume) {
return false;
}
Expand All @@ -83,8 +81,7 @@ protected ConsumptionProbe tryConsumeAndReturnRemainingTokensImpl(long tokensToC
long currentTimeNanos = timeMeter.currentTimeNanos();
lock.lock();
try {
state.refillAllBandwidth(bandwidths, currentTimeNanos);
long availableToConsume = state.getAvailableTokens(bandwidths);
long availableToConsume = state.refillAllBandwidth(bandwidths, currentTimeNanos);
if (tokensToConsume > availableToConsume) {
long nanosToWaitForRefill = state.delayNanosAfterWillBePossibleToConsume(bandwidths, tokensToConsume);
return ConsumptionProbe.rejected(availableToConsume, nanosToWaitForRefill);
Expand Down Expand Up @@ -143,8 +140,7 @@ public long getAvailableTokens() {
long currentTimeNanos = timeMeter.currentTimeNanos();
lock.lock();
try {
state.refillAllBandwidth(bandwidths, currentTimeNanos);
return state.getAvailableTokens(bandwidths);
return state.refillAllBandwidth(bandwidths, currentTimeNanos);
} finally {
lock.unlock();
}
Expand Down
2 changes: 1 addition & 1 deletion bucket4j-jcache/pom.xml
Expand Up @@ -23,7 +23,7 @@
<parent>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../bucket4j-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
Expand Down
2 changes: 1 addition & 1 deletion bucket4j-parent/pom.xml
Expand Up @@ -21,7 +21,7 @@

<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<name>bucket4-parent</name>
Expand Down
2 changes: 1 addition & 1 deletion release-reactor/pom.xml
Expand Up @@ -24,7 +24,7 @@

<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>release-reactor</artifactId>
<version>2.1.0</version>
<version>3.0.0-SNAPSHOT</version>
<packaging>pom</packaging>

<properties>
Expand Down

0 comments on commit 04f84fc

Please sign in to comment.