Skip to content

Commit

Permalink
#51 Merge branch '2.0' into 2.1
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
#	bucket4j-benchmarks/pom.xml
#	bucket4j-core/pom.xml
#	bucket4j-jcache/pom.xml
#	bucket4j-jcache/src/main/java/io/github/bucket4j/grid/jcache/JCacheCommand.java
#	bucket4j-parent/pom.xml
#	pom.xml
#	release-reactor/pom.xml
  • Loading branch information
vladimir-bukhtoyarov committed Sep 23, 2017
2 parents 8392843 + cb99ead commit 8b933d5
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 58 deletions.
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,30 @@ Documentation for previous versions:

## Get Bucket4j library

#### By direct link
[Download compiled jar, sources, javadocs](https://github.com/vladimir-bukhtoyarov/bucket4j/releases/tag/2.1.0)

#### You can build Bucket4j from sources
```bash
git clone https://github.com/vladimir-bukhtoyarov/bucket4j.git
cd bucket4j
mvn clean install
```

#### You can add Bucket4j to your project as maven dependency
The Bucket4j is distributed through both [JCenter](https://bintray.com/bintray/jcenter) and [Maven Central](http://search.maven.org/),
use any of them:
```xml
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>2.1.0</version>
<version>2.1.1</version>
</dependency>
```
To use JCache extension you also need to add following dependency:
```xml
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-jcache</artifactId>
<version>2.1.0</version>
<version>2.1.1</version>
</dependency>
```
#### You can build Bucket4j from sources
```bash
git clone https://github.com/vladimir-bukhtoyarov/bucket4j.git
cd bucket4j
mvn clean install
```

## Have a question?
Feel free to ask in the [gitter chat](https://gitter.im/vladimir-bukhtoyarov/bucket4j)
Expand Down
2 changes: 1 addition & 1 deletion bucket4j-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<parent>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>2.1.1</version>
<relativePath>../bucket4j-parent</relativePath>
</parent>
<artifactId>bucket4j-benchmarks</artifactId>
Expand Down
9 changes: 1 addition & 8 deletions bucket4j-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,12 @@
<parent>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-parent</artifactId>
<version>2.1.0</version>
<version>2.1.1</version>
<relativePath>../bucket4j-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bucket4j-core</artifactId>

<distributionManagement>
<repository>
<id>vladimir-bukhtoyarov-bintray-repo</id>
<url>https://api.bintray.com/maven/vladimir-bukhtoyarov/maven/bucket4j-core/</url>
</repository>
</distributionManagement>

<build>
<plugins>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ public static IllegalArgumentException restrictionsNotSpecified() {
return new IllegalArgumentException(msg);
}

public static IllegalArgumentException tooHighRefillRate(long periodNanos, long tokens) {
double actualRate = (double) tokens / (double) periodNanos;
String pattern = "{0} token/nanosecond is not permitted refill rate" +
", because highest supported rate is 1 token/nanosecond";
String msg = MessageFormat.format(pattern, actualRate);
return new IllegalArgumentException(msg);
}

// ------------------- end of construction time exceptions --------------------------------

// ------------------- usage time exceptions ---------------------------------------------
Expand Down
105 changes: 82 additions & 23 deletions bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ private void addTokens(int bandwidthIndex, Bandwidth bandwidth, long tokensToAdd
long currentSize = getCurrentSize(bandwidthIndex);
long newSize = currentSize + tokensToAdd;
if (newSize >= bandwidth.capacity) {
setCurrentSize(bandwidthIndex, bandwidth.capacity);
setRoundingError(bandwidthIndex, 0L);
resetBandwidth(bandwidthIndex, bandwidth.capacity);
} else if (newSize < currentSize) {
// arithmetic overflow happens. This mean that bucket reached Long.MAX_VALUE tokens.
// just reset bandwidth state
resetBandwidth(bandwidthIndex, bandwidth.capacity);
} else {
setCurrentSize(bandwidthIndex, newSize);
}
Expand All @@ -120,46 +123,85 @@ private void consume(int bandwidth, long tokens) {

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

if (currentSize >= capacity) {
setCurrentSize(bandwidthIndex, capacity);
setRoundingError(bandwidthIndex, 0L);
return;
}
final long refillPeriodNanos = bandwidth.refill.getPeriodNanos();
final long refillTokens = bandwidth.refill.getTokens();
final long currentSize = getCurrentSize(bandwidthIndex);

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

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

long refillPeriod = bandwidth.refill.getPeriodNanos();
long refillTokens = bandwidth.refill.getTokens();
long roundingError = getRoundingError(bandwidthIndex);
long divided = refillTokens * durationSinceLastRefillNanos + roundingError;
long calculatedRefill = divided / refillPeriod;
if (calculatedRefill == 0) {
setRoundingError(bandwidthIndex, divided);
return;
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;
}
}

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

roundingError = divided % refillPeriod;
setCurrentSize(bandwidthIndex, newSize);
setRoundingError(bandwidthIndex, roundingError);
}

private void resetBandwidth(int bandwidthIndex, long capacity) {
setCurrentSize(bandwidthIndex, capacity);
setRoundingError(bandwidthIndex, 0);
}

private long delayNanosAfterWillBePossibleToConsume(int bandwidthIndex, Bandwidth bandwidth, long tokens) {
long currentSize = getCurrentSize(bandwidthIndex);
if (tokens <= currentSize) {
return 0;
}
long deficit = tokens - currentSize;
long periodNanos = bandwidth.refill.getPeriodNanos();
return periodNanos * deficit / bandwidth.refill.getTokens();
long refillPeriodNanos = bandwidth.refill.getPeriodNanos();
long refillPeriodTokens = bandwidth.refill.getTokens();

long divided = multiplyExactOrReturnMaxValue(refillPeriodNanos, deficit);
if (divided == Long.MAX_VALUE) {
// arithmetic overflow happens.
// there is no sense to stay in integer arithmetic when having deal with so big numbers
return (long)((double) deficit / (double)refillPeriodTokens * (double)refillPeriodNanos);
} else {
return divided / refillPeriodTokens;
}
}

long getCurrentSize(int bandwidth) {
Expand Down Expand Up @@ -193,4 +235,21 @@ public String toString() {
'}';
}

// just a copy of JDK method Math#multiplyExact,
// but instead of throwing exception it returns Long.MAX_VALUE in case of overflow
private 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;
}

}
3 changes: 3 additions & 0 deletions bucket4j-core/src/main/java/io/github/bucket4j/Refill.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ private Refill(long tokens, Duration period) {
if (periodNanos <= 0) {
throw BucketExceptions.nonPositivePeriod(periodNanos);
}
if (tokens > periodNanos) {
throw BucketExceptions.tooHighRefillRate(periodNanos, tokens);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ class DetectionOfIllegalApiUsageSpecification extends Specification {
ex.message == restrictionsNotSpecified().message
}

def "Should detect the high rate of refill"() {
when:
Bucket4j.builder().addLimit(Bandwidth.simple(2, Duration.ofNanos(1)))
then:
IllegalArgumentException ex = thrown()
ex.message == tooHighRefillRate(1, 2).message
}

def "Should check that tokens to consume should be positive"() {
setup:
def bucket = Bucket4j.builder().addLimit(
Expand Down
Loading

0 comments on commit 8b933d5

Please sign in to comment.