diff --git a/README.md b/README.md index 0e1727d5..0269a4bd 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ In additional to basic features described above, ```Bucket4j``` provides ability In addition to local in-memory buckets, the Bucket4j supports clustered usage scenario on top of following back-ends: | Back-end | Async supported | Optimized serialization | Thin-client support | Documentation link | | :--- | :---: | :---: | :---: | :---: | -| ```JCache API (JSR 107)``` | No | No | No | [bucket4j-jcache](https://bucket4j.com/7.3.0/toc.html#bucket4j-jcache) | -| ```Hazelcast``` | Yes | Yes | Planned | [bucket4j-hazelcast](https://bucket4j.com/7.3.0/toc.html#bucket4j-hazelcast) | -| ```Apache Ignite``` | Yes | n/a | Yes | [bucket4j-ignite](https://bucket4j.com/7.3.0/toc.html#bucket4j-ignite) | -| ```Inifinispan``` | Yes | Yes | No | [bucket4j-infinispan](https://bucket4j.com/7.3.0/toc.html#bucket4j-infinispan) | -| ```Oracle Coherence``` | Yes | Yes | No | [bucket4j-coherence](https://bucket4j.com/7.3.0/toc.html#bucket4j-coherence) | +| ```JCache API (JSR 107)``` | No | No | No | [bucket4j-jcache](https://bucket4j.com/7.4.0/toc.html#bucket4j-jcache) | +| ```Hazelcast``` | Yes | Yes | Planned | [bucket4j-hazelcast](https://bucket4j.com/7.4.0/toc.html#bucket4j-hazelcast) | +| ```Apache Ignite``` | Yes | n/a | Yes | [bucket4j-ignite](https://bucket4j.com/7.4.0/toc.html#bucket4j-ignite) | +| ```Inifinispan``` | Yes | Yes | No | [bucket4j-infinispan](https://bucket4j.com/7.4.0/toc.html#bucket4j-infinispan) | +| ```Oracle Coherence``` | Yes | Yes | No | [bucket4j-coherence](https://bucket4j.com/7.4.0/toc.html#bucket4j-coherence) | ### Non-JVM back-ends Bucket4j authors strongly recommends to use JVM based back-ends when possible, @@ -38,8 +38,8 @@ In addition to local in-memory buckets, the Bucket4j supports clustered usage sc | Back-end | Async supported | Documentation link | | :--- | :---: | :---: | | ```Redis``` | Yes | [bucket4j-redis](https://github.com/vladimir-bukhtoyarov/bucket4j/blob/master/bucket4j-redis/src/main/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManager.java) | -| ```MySQL``` | No | [bucket4j-mysql](https://bucket4j.com/7.3.0/toc.html#mysql-integration) | -| ```PostgreSQL``` | No | [bucket4j-postgresql](https://bucket4j.com/7.3.0/toc.html#postgresql-integration) | +| ```MySQL``` | No | [bucket4j-mysql](https://bucket4j.com/7.4.0/toc.html#mysql-integration) | +| ```PostgreSQL``` | No | [bucket4j-postgresql](https://bucket4j.com/7.4.0/toc.html#postgresql-integration) | | ```DynamoDb``` | No | [bucket4j-dynamodb](https://github.com/vladimir-bukhtoyarov/bucket4j/blob/master/bucket4j-dynamodb-sdk-v1/src/main/java/io/github/bucket4j/dynamodb/v1/LongDynamoDBProxyManager.java) | ### Local caches support @@ -49,8 +49,8 @@ Sometimes you are having deal with bucket per key scenarios but distributed sync | ```Caffeine``` | [bucket4j-caffeine](https://github.com/vladimir-bukhtoyarov/bucket4j/blob/7.3/bucket4j-caffeine/src/main/java/io/github/bucket4j/caffeine/CaffeineProxyManager.java) | ## [Documentation](https://bucket4j.com) -* [Official reference](https://bucket4j.com/7.3.0/toc.html) -* [Quick start examples](https://bucket4j.com/7.3.0/toc.html#quick-start-examples) +* [Official reference](https://bucket4j.com/7.4.0/toc.html) +* [Quick start examples](https://bucket4j.com/7.4.0/toc.html#quick-start-examples) * [Third-party articles](https://bucket4j.com/#third-party-articles) ## Get Bucket4j library @@ -60,7 +60,7 @@ The Bucket4j is distributed through [Maven Central](http://search.maven.org/): com.github.vladimir-bukhtoyarov bucket4j-core - 7.3.0 + 7.4.0 ``` #### You can build Bucket4j from sources diff --git a/asciidoc/pom.xml b/asciidoc/pom.xml index 347e556d..eeebff9a 100644 --- a/asciidoc/pom.xml +++ b/asciidoc/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent asciidoc diff --git a/asciidoc/src/main/docs/asciidoc/basic/api-reference.adoc b/asciidoc/src/main/docs/asciidoc/basic/api-reference.adoc index f33431cc..f2efc8e6 100644 --- a/asciidoc/src/main/docs/asciidoc/basic/api-reference.adoc +++ b/asciidoc/src/main/docs/asciidoc/basic/api-reference.adoc @@ -146,6 +146,15 @@ boolean tryConsume(long numTokens); void forceAddTokens(long tokensToAdd); ---- +===== reset +[source, java] +---- + /** + * Reset all tokens up to maximum capacity. + */ + void reset(); +---- + ===== getAvailableTokens [source, java] ---- diff --git a/asciidoc/src/main/docs/asciidoc/index.adoc b/asciidoc/src/main/docs/asciidoc/index.adoc index a8fd7d6f..b9f0a5c4 100644 --- a/asciidoc/src/main/docs/asciidoc/index.adoc +++ b/asciidoc/src/main/docs/asciidoc/index.adoc @@ -3,7 +3,7 @@ image::./images/white-logo.png[] endif::[] == Current version {revnumber} documentation -`04 March 2022` Support for Caffeine. +`09 April 2022` Method for reset bucket. * http://bucket4j.com/{revnumber}/release-notes.html[{revnumber} Release notes] * http://bucket4j.com/{revnumber}/toc.html[{revnumber} Reference] @@ -43,6 +43,12 @@ image:images/Maxim_Bartkov.jpg[80,80] + == Documentation for previous versions === 2022 +==== 7.4.0 +`04 March 2022` Support for Caffeine. + +* http://bucket4j.com/7.4.0/release-notes.html[7.4.0 Release notes] +* http://bucket4j.com/7.4.0/toc.html[7.4.0 Reference] + ==== 7.2.0 `11 Feb 2022` Support for MySQL and PostgreSQL. diff --git a/asciidoc/src/main/docs/asciidoc/release-notes.adoc b/asciidoc/src/main/docs/asciidoc/release-notes.adoc index a8ec77eb..a6853e2d 100644 --- a/asciidoc/src/main/docs/asciidoc/release-notes.adoc +++ b/asciidoc/src/main/docs/asciidoc/release-notes.adoc @@ -2,4 +2,8 @@ Release `{revnumber}` .The issues in release scope are following: -* https://github.com/vladimir-bukhtoyarov/bucket4j/issues/238[#238 Support for Caffeine] +* https://github.com/vladimir-bukhtoyarov/bucket4j/issues/116[#116 Method for reset bucket] + +.The bugfixes in release scope are following: +* https://github.com/vladimir-bukhtoyarov/bucket4j/issues/246[#246 Connection leak in MySQL/PostgreSQL proxy managers] +* https://github.com/vladimir-bukhtoyarov/bucket4j/issues/244[#244 RedisCommands.SETPXNX not exists] diff --git a/bucket4j-benchmarks/pom.xml b/bucket4j-benchmarks/pom.xml index ba6637f5..bf3d1dbb 100644 --- a/bucket4j-benchmarks/pom.xml +++ b/bucket4j-benchmarks/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent bucket4j-benchmarks diff --git a/bucket4j-caffeine/pom.xml b/bucket4j-caffeine/pom.xml index 5fd19455..b0430c24 100644 --- a/bucket4j-caffeine/pom.xml +++ b/bucket4j-caffeine/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent bucket4j-caffeine diff --git a/bucket4j-coherence/pom.xml b/bucket4j-coherence/pom.xml index 33841a8f..5d2062a0 100644 --- a/bucket4j-coherence/pom.xml +++ b/bucket4j-coherence/pom.xml @@ -9,7 +9,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent diff --git a/bucket4j-core/pom.xml b/bucket4j-core/pom.xml index 62b7d4e7..1d535374 100644 --- a/bucket4j-core/pom.xml +++ b/bucket4j-core/pom.xml @@ -7,7 +7,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent 4.0.0 diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/AbstractBucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/AbstractBucket.java index 02467931..52c4378c 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/AbstractBucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/AbstractBucket.java @@ -63,6 +63,8 @@ public abstract class AbstractBucket implements Bucket, BlockingBucket, Scheduli protected abstract VerboseResult forceAddTokensVerboseImpl(long tokensToAdd); + protected abstract VerboseResult resetVerboseImpl(); + protected abstract VerboseResult replaceConfigurationVerboseImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy); protected abstract VerboseResult consumeIgnoringRateLimitsVerboseImpl(long tokensToConsume); @@ -157,6 +159,11 @@ public VerboseResult addTokens(long tokensToAdd) { return addTokensVerboseImpl(tokensToAdd); } + @Override + public VerboseResult reset() { + return resetVerboseImpl(); + } + @Override public VerboseResult forceAddTokens(long tokensToAdd) { checkTokensToAdd(tokensToAdd); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/Bucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/Bucket.java index 1e236321..6d5af1d3 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/Bucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/Bucket.java @@ -176,6 +176,11 @@ static LocalBucketBuilder builder() { */ void forceAddTokens(long tokensToAdd); + /** + * Reset all tokens up to maximum capacity. + */ + void reset(); + /** * Returns amount of available tokens in this bucket. diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java b/bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java index 0dd75ebe..2beb40a7 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/BucketState.java @@ -49,6 +49,8 @@ public interface BucketState { void addTokens(long tokensToAdd); + void reset(); + void forceAddTokens(long tokensToAdd); long getCurrentSize(int bandwidth); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/BucketState64BitsInteger.java b/bucket4j-core/src/main/java/io/github/bucket4j/BucketState64BitsInteger.java index 84030e9c..c39e409d 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/BucketState64BitsInteger.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/BucketState64BitsInteger.java @@ -325,6 +325,14 @@ public void addTokens(long tokensToAdd) { } } + @Override + public void reset() { + Bandwidth[] bandwidths = configuration.getBandwidths(); + for (int i = 0; i < bandwidths.length; i++) { + resetBandwidth(i, bandwidths[i].capacity); + } + } + @Override public void forceAddTokens(long tokensToAdd) { Bandwidth[] bandwidths = configuration.getBandwidths(); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/BucketStateIEEE754.java b/bucket4j-core/src/main/java/io/github/bucket4j/BucketStateIEEE754.java index 517b1ca1..fc8e285d 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/BucketStateIEEE754.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/BucketStateIEEE754.java @@ -179,6 +179,14 @@ public void addTokens(long tokensToAdd) { } } + @Override + public void reset() { + Bandwidth[] bandwidths = configuration.getBandwidths(); + for (int i = 0; i < bandwidths.length; i++) { + tokens[i] = bandwidths[i].getCapacity(); + } + } + @Override public void refillAllBandwidth(long currentTimeNanos) { Bandwidth[] limits = configuration.getBandwidths(); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/VerboseBucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/VerboseBucket.java index 8a022f61..ccc5f386 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/VerboseBucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/VerboseBucket.java @@ -65,6 +65,11 @@ public interface VerboseBucket { */ VerboseResult addTokens(long tokensToAdd); + /** + * Does the same that {@link Bucket#reset()} + */ + VerboseResult reset(); + /** * Does the same that {@link Bucket#forceAddTokens(long)} */ diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxy.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxy.java index 37a776c9..16528866 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxy.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxy.java @@ -275,6 +275,11 @@ public interface AsyncBucketProxy { */ CompletableFuture forceAddTokens(long tokensToAdd); + /** + * Reset all tokens up to maximum capacity. + */ + CompletableFuture reset(); + /** * Has the same semantic with {@link Bucket#replaceConfiguration(BucketConfiguration, TokensInheritanceStrategy)} */ diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxyAdapter.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxyAdapter.java index fbb03001..0e182231 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxyAdapter.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncBucketProxyAdapter.java @@ -73,6 +73,11 @@ public CompletableFuture> forceAddTokens(long tokensToAdd return completedFuture(() -> target.asVerbose().forceAddTokens(tokensToAdd)); } + @Override + public CompletableFuture> reset() { + return completedFuture(() -> target.asVerbose().reset()); + } + @Override public CompletableFuture> replaceConfiguration(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { LimitChecker.checkConfiguration(newConfiguration); @@ -156,6 +161,14 @@ public CompletableFuture forceAddTokens(long tokensToAdd) { }); } + @Override + public CompletableFuture reset() { + return completedFuture(() -> { + target.reset(); + return null; + }); + } + @Override public CompletableFuture replaceConfiguration(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { LimitChecker.checkConfiguration(newConfiguration); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncVerboseBucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncVerboseBucket.java index bf2b2d6b..ec5df579 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncVerboseBucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/AsyncVerboseBucket.java @@ -69,6 +69,11 @@ public interface AsyncVerboseBucket { */ CompletableFuture> forceAddTokens(long tokensToAdd); + /** + * Does the same that {@link Bucket#reset()} + */ + CompletableFuture> reset(); + /** * Does the same that {@link Bucket#replaceConfiguration(BucketConfiguration, TokensInheritanceStrategy)} */ diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultAsyncBucketProxy.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultAsyncBucketProxy.java index 983de21b..b25dccdc 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultAsyncBucketProxy.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultAsyncBucketProxy.java @@ -171,6 +171,12 @@ public CompletableFuture> forceAddTokens(long tokensToAdd return execute(verboseCommand).thenApply(RemoteVerboseResult::asLocal); } + @Override + public CompletableFuture> reset() { + VerboseCommand verboseCommand = new VerboseCommand<>(new ResetCommand()); + return execute(verboseCommand).thenApply(RemoteVerboseResult::asLocal); + } + @Override public CompletableFuture> replaceConfiguration(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { checkConfiguration(newConfiguration); @@ -345,6 +351,12 @@ public CompletableFuture forceAddTokens(long tokensToAdd) { return future.thenApply(nothing -> null); } + @Override + public CompletableFuture reset() { + CompletableFuture future = execute(new ResetCommand()); + return future.thenApply(nothing -> null); + } + @Override public CompletableFuture getAvailableTokens() { return execute(new GetAvailableTokensCommand()); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultBucketProxy.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultBucketProxy.java index f45abc35..551a6ec6 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultBucketProxy.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/proxy/DefaultBucketProxy.java @@ -118,6 +118,12 @@ protected long consumeIgnoringRateLimitsImpl(long tokensToConsume) { return execute(command); } + @Override + public void reset() { + ResetCommand command = new ResetCommand(); + execute(command); + } + @Override public long getAvailableTokens() { return execute(new GetAvailableTokensCommand()); @@ -165,6 +171,12 @@ protected VerboseResult forceAddTokensVerboseImpl(long tokensToAdd) { return execute(command.asVerbose()).asLocal(); } + @Override + protected VerboseResult resetVerboseImpl() { + ResetCommand command = new ResetCommand(); + return execute(command.asVerbose()).asLocal(); + } + @Override protected VerboseResult replaceConfigurationVerboseImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { ReplaceConfigurationCommand replaceConfigCommand = new ReplaceConfigurationCommand(newConfiguration, tokensInheritanceStrategy); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/RemoteBucketState.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/RemoteBucketState.java index 03b8f39f..bdce18ba 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/RemoteBucketState.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/RemoteBucketState.java @@ -109,6 +109,10 @@ public void forceAddTokens(long tokensToAdd) { state.forceAddTokens(tokensToAdd); } + public void reset() { + state.reset(); + } + public BucketState copyBucketState() { return state.copy(); } diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/commands/ResetCommand.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/commands/ResetCommand.java new file mode 100644 index 00000000..1112f9f8 --- /dev/null +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/remote/commands/ResetCommand.java @@ -0,0 +1,109 @@ +/*- + * ========================LICENSE_START================================= + * Bucket4j + * %% + * Copyright (C) 2015 - 2020 Vladimir Bukhtoyarov + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * =========================LICENSE_END================================== + */ + +package io.github.bucket4j.distributed.remote.commands; + +import io.github.bucket4j.Nothing; +import io.github.bucket4j.distributed.remote.CommandResult; +import io.github.bucket4j.distributed.remote.MutableBucketEntry; +import io.github.bucket4j.distributed.remote.RemoteBucketState; +import io.github.bucket4j.distributed.remote.RemoteCommand; +import io.github.bucket4j.distributed.serialization.DeserializationAdapter; +import io.github.bucket4j.distributed.serialization.SerializationAdapter; +import io.github.bucket4j.distributed.serialization.SerializationHandle; +import io.github.bucket4j.distributed.versioning.Version; +import io.github.bucket4j.distributed.versioning.Versions; +import io.github.bucket4j.util.ComparableByContent; + +import java.io.IOException; + +import static io.github.bucket4j.distributed.versioning.Versions.v_7_0_0; + + +public class ResetCommand implements RemoteCommand, ComparableByContent { + + public static final SerializationHandle SERIALIZATION_HANDLE = new SerializationHandle() { + @Override + public ResetCommand deserialize(DeserializationAdapter adapter, S input, Version backwardCompatibilityVersion) throws IOException { + int formatNumber = adapter.readInt(input); + Versions.check(formatNumber, v_7_0_0, v_7_0_0); + return new ResetCommand(); + } + + @Override + public void serialize(SerializationAdapter adapter, O output, ResetCommand command, Version backwardCompatibilityVersion) throws IOException { + adapter.writeInt(output, v_7_0_0.getNumber()); + } + + @Override + public int getTypeId() { + return 39; + } + + @Override + public Class getSerializedType() { + return ResetCommand.class; + } + + }; + + public ResetCommand() { + + } + + @Override + public CommandResult execute(MutableBucketEntry mutableEntry, long currentTimeNanos) { + if (!mutableEntry.exists()) { + return CommandResult.bucketNotFound(); + } + + RemoteBucketState state = mutableEntry.get(); + state.refillAllBandwidth(currentTimeNanos); + state.reset(); + mutableEntry.set(state); + return CommandResult.empty(); + } + + @Override + public SerializationHandle getSerializationHandle() { + return SERIALIZATION_HANDLE; + } + + @Override + public boolean equalsByContent(ResetCommand other) { + return true; + } + + @Override + public boolean isImmediateSyncRequired(long unsynchronizedTokens, long nanosSinceLastSync) { + return true; + } + + @Override + public long estimateTokensToConsume() { + return 0; + } + + @Override + public long getConsumedTokens(Nothing result) { + return 0; + } + +} diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/serialization/SerializationHandle.java b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/serialization/SerializationHandle.java index 99fc055e..fcaf1a08 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/distributed/serialization/SerializationHandle.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/distributed/serialization/SerializationHandle.java @@ -64,9 +64,8 @@ public interface SerializationHandle { VerboseCommand.SERIALIZATION_HANDLE, // 35 SyncCommand.SERIALIZATION_HANDLE, // 36 Request.SERIALIZATION_HANDLE, // 37 - ForceAddTokensCommand.SERIALIZATION_HANDLE // 38 - - + ForceAddTokensCommand.SERIALIZATION_HANDLE, // 38 + ResetCommand.SERIALIZATION_HANDLE // 39 )); T deserialize(DeserializationAdapter adapter, I input, Version backwardCompatibilityVersion) throws IOException; diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/local/LockFreeBucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/local/LockFreeBucket.java index c23210f3..c8917bec 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/local/LockFreeBucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/local/LockFreeBucket.java @@ -199,6 +199,24 @@ protected void forceAddTokensImpl(long tokensToAdd) { } } + @Override + public void reset() { + BucketState previousState = stateRef.get(); + BucketState newState = previousState.copy(); + long currentTimeNanos = timeMeter.currentTimeNanos(); + + while (true) { + newState.refillAllBandwidth(currentTimeNanos); + newState.reset(); + if (stateRef.compareAndSet(previousState, newState)) { + return; + } else { + previousState = stateRef.get(); + newState.copyStateFrom(previousState); + } + } + } + @Override protected void replaceConfigurationImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { BucketState previousState = stateRef.get(); @@ -374,6 +392,24 @@ protected VerboseResult forceAddTokensVerboseImpl(long tokensToAdd) { } } + @Override + protected VerboseResult resetVerboseImpl() { + BucketState previousState = stateRef.get(); + BucketState newState = previousState.copy(); + long currentTimeNanos = timeMeter.currentTimeNanos(); + + while (true) { + newState.refillAllBandwidth(currentTimeNanos); + newState.reset(); + if (stateRef.compareAndSet(previousState, newState)) { + return new VerboseResult<>(currentTimeNanos, Nothing.INSTANCE, newState.copy()); + } else { + previousState = stateRef.get(); + newState.copyStateFrom(previousState); + } + } + } + @Override protected VerboseResult replaceConfigurationVerboseImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { BucketState previousState = stateRef.get(); diff --git a/bucket4j-core/src/main/java/io/github/bucket4j/local/SynchronizedBucket.java b/bucket4j-core/src/main/java/io/github/bucket4j/local/SynchronizedBucket.java index 989748b9..ea9976d3 100644 --- a/bucket4j-core/src/main/java/io/github/bucket4j/local/SynchronizedBucket.java +++ b/bucket4j-core/src/main/java/io/github/bucket4j/local/SynchronizedBucket.java @@ -283,6 +283,19 @@ protected VerboseResult forceAddTokensVerboseImpl(long tokensToAdd) { } } + @Override + protected VerboseResult resetVerboseImpl() { + long currentTimeNanos = timeMeter.currentTimeNanos(); + lock.lock(); + try { + state.refillAllBandwidth(currentTimeNanos); + state.reset(); + return new VerboseResult<>(currentTimeNanos, Nothing.INSTANCE, state.copy()); + } finally { + lock.unlock(); + } + } + @Override protected VerboseResult replaceConfigurationVerboseImpl(BucketConfiguration newConfiguration, TokensInheritanceStrategy tokensInheritanceStrategy) { long currentTimeNanos = timeMeter.currentTimeNanos(); @@ -339,6 +352,18 @@ protected void forceAddTokensImpl(long tokensToAdd) { } } + @Override + public void reset() { + long currentTimeNanos = timeMeter.currentTimeNanos(); + lock.lock(); + try { + state.refillAllBandwidth(currentTimeNanos); + state.reset(); + } finally { + lock.unlock(); + } + } + @Override public long getAvailableTokens() { long currentTimeNanos = timeMeter.currentTimeNanos(); diff --git a/bucket4j-core/src/test/java/io/github/bucket4j/api_specifications/regular/ResetTokensSpecification.groovy b/bucket4j-core/src/test/java/io/github/bucket4j/api_specifications/regular/ResetTokensSpecification.groovy new file mode 100644 index 00000000..11650106 --- /dev/null +++ b/bucket4j-core/src/test/java/io/github/bucket4j/api_specifications/regular/ResetTokensSpecification.groovy @@ -0,0 +1,50 @@ +package io.github.bucket4j.api_specifications.regular + +import io.github.bucket4j.Bandwidth +import io.github.bucket4j.Bucket +import io.github.bucket4j.BucketConfiguration +import io.github.bucket4j.distributed.AsyncBucketProxy +import io.github.bucket4j.mock.BucketType +import io.github.bucket4j.mock.TimeMeterMock +import spock.lang.Specification + +import java.time.Duration + +class ResetTokensSpecification extends Specification { + + def "resetBucket spec"() { + expect: + BucketConfiguration configuration = BucketConfiguration.builder() + .addLimit(Bandwidth.simple(100, Duration.ofNanos(100)) + .withInitialTokens(10)) + .build() + for (BucketType type : BucketType.values()) { + for (boolean sync : [true, false]) { + for (boolean verbose : [true, false]) { + println("type=$type sync=$sync verbose=$verbose") + TimeMeterMock timeMeter = new TimeMeterMock(0) + if (sync) { + Bucket bucket = type.createBucket(configuration, timeMeter) + bucket.getAvailableTokens() + if (!verbose) { + bucket.reset() + } else { + bucket.asVerbose().reset() + } + assert bucket.getAvailableTokens() == 100 + } else { + AsyncBucketProxy bucket = type.createAsyncBucket(configuration, timeMeter) + bucket.getAvailableTokens() + if (!verbose) { + bucket.reset().get() + } else { + bucket.asVerbose().reset().get() + } + assert bucket.getAvailableTokens().get() == 100 + } + } + } + } + } + +} diff --git a/bucket4j-core/src/test/java/io/github/bucket4j/distributed/serialization/AbstractSerializationTest.java b/bucket4j-core/src/test/java/io/github/bucket4j/distributed/serialization/AbstractSerializationTest.java index 80130067..3d8ba894 100644 --- a/bucket4j-core/src/test/java/io/github/bucket4j/distributed/serialization/AbstractSerializationTest.java +++ b/bucket4j-core/src/test/java/io/github/bucket4j/distributed/serialization/AbstractSerializationTest.java @@ -285,6 +285,7 @@ public void serializationOfCommands() throws IOException { testSerialization(new VerboseCommand<>(new GetAvailableTokensCommand())); testSerialization(new VerboseCommand<>(new ReplaceConfigurationCommand(configuration, TokensInheritanceStrategy.AS_IS))); testSerialization(new SyncCommand(20, 10000000)); + testSerialization(new ResetCommand()); testSerialization(new Request(new GetAvailableTokensCommand(), Versions.getLatest(), null)); testSerialization(new Request(new GetAvailableTokensCommand(), Versions.getLatest(), 0L)); diff --git a/bucket4j-dynamodb-sdk-v1/pom.xml b/bucket4j-dynamodb-sdk-v1/pom.xml index c7ccf4fd..afbbba04 100644 --- a/bucket4j-dynamodb-sdk-v1/pom.xml +++ b/bucket4j-dynamodb-sdk-v1/pom.xml @@ -7,7 +7,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent diff --git a/bucket4j-examples/pom.xml b/bucket4j-examples/pom.xml index f96341ed..53b964dd 100644 --- a/bucket4j-examples/pom.xml +++ b/bucket4j-examples/pom.xml @@ -23,7 +23,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent 4.0.0 diff --git a/bucket4j-hazelcast-all/bucket4j-hazelcast-3/pom.xml b/bucket4j-hazelcast-all/bucket4j-hazelcast-3/pom.xml index 56cb86f8..e34c9cc8 100644 --- a/bucket4j-hazelcast-all/bucket4j-hazelcast-3/pom.xml +++ b/bucket4j-hazelcast-all/bucket4j-hazelcast-3/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-hazelcast-all - 7.3.0 + 7.4.0 ../../bucket4j-hazelcast-all bucket4j-hazelcast-3 diff --git a/bucket4j-hazelcast-all/bucket4j-hazelcast/pom.xml b/bucket4j-hazelcast-all/bucket4j-hazelcast/pom.xml index 094c313c..fb08d7b3 100644 --- a/bucket4j-hazelcast-all/bucket4j-hazelcast/pom.xml +++ b/bucket4j-hazelcast-all/bucket4j-hazelcast/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-hazelcast-all - 7.3.0 + 7.4.0 ../../bucket4j-hazelcast-all bucket4j-hazelcast diff --git a/bucket4j-hazelcast-all/pom.xml b/bucket4j-hazelcast-all/pom.xml index 8d1aef09..3312565c 100644 --- a/bucket4j-hazelcast-all/pom.xml +++ b/bucket4j-hazelcast-all/pom.xml @@ -5,7 +5,7 @@ bucket4j-parent com.github.vladimir-bukhtoyarov - 7.3.0 + 7.4.0 ../bucket4j-parent pom diff --git a/bucket4j-ignite/pom.xml b/bucket4j-ignite/pom.xml index d8479a75..0835eaa4 100644 --- a/bucket4j-ignite/pom.xml +++ b/bucket4j-ignite/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent bucket4j-ignite diff --git a/bucket4j-infinispan-all/bucket4j-infinispan-8/pom.xml b/bucket4j-infinispan-all/bucket4j-infinispan-8/pom.xml index a4140f7a..be702e32 100644 --- a/bucket4j-infinispan-all/bucket4j-infinispan-8/pom.xml +++ b/bucket4j-infinispan-all/bucket4j-infinispan-8/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-infinispan-all - 7.3.0 + 7.4.0 ../../bucket4j-infinispan-all diff --git a/bucket4j-infinispan-all/bucket4j-infinispan/pom.xml b/bucket4j-infinispan-all/bucket4j-infinispan/pom.xml index 60b65f23..0d8c941e 100644 --- a/bucket4j-infinispan-all/bucket4j-infinispan/pom.xml +++ b/bucket4j-infinispan-all/bucket4j-infinispan/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-infinispan-all - 7.3.0 + 7.4.0 ../../bucket4j-infinispan-all bucket4j-infinispan diff --git a/bucket4j-infinispan-all/pom.xml b/bucket4j-infinispan-all/pom.xml index d17ba291..ffff32a2 100644 --- a/bucket4j-infinispan-all/pom.xml +++ b/bucket4j-infinispan-all/pom.xml @@ -6,7 +6,7 @@ bucket4j-parent com.github.vladimir-bukhtoyarov - 7.3.0 + 7.4.0 ../bucket4j-parent 4.0.0 diff --git a/bucket4j-jcache/pom.xml b/bucket4j-jcache/pom.xml index 71498f7a..cd18ef15 100644 --- a/bucket4j-jcache/pom.xml +++ b/bucket4j-jcache/pom.xml @@ -7,7 +7,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent 4.0.0 diff --git a/bucket4j-mysql/pom.xml b/bucket4j-mysql/pom.xml index 19d20c97..3ff4b93a 100644 --- a/bucket4j-mysql/pom.xml +++ b/bucket4j-mysql/pom.xml @@ -7,7 +7,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent diff --git a/bucket4j-mysql/src/main/java/io/github/bucket4j/mysql/MySQLSelectForUpdateBasedProxyManager.java b/bucket4j-mysql/src/main/java/io/github/bucket4j/mysql/MySQLSelectForUpdateBasedProxyManager.java index f87200c3..d1739a92 100644 --- a/bucket4j-mysql/src/main/java/io/github/bucket4j/mysql/MySQLSelectForUpdateBasedProxyManager.java +++ b/bucket4j-mysql/src/main/java/io/github/bucket4j/mysql/MySQLSelectForUpdateBasedProxyManager.java @@ -161,8 +161,7 @@ public boolean tryInsertEmptyData() { @Override public void removeProxy(Long key) { - try { - Connection connection = dataSource.getConnection(); + try (Connection connection = dataSource.getConnection()) { try(PreparedStatement removeStatement = connection.prepareStatement(removeSqlQuery)) { removeStatement.setLong(1, key); removeStatement.executeUpdate(); diff --git a/bucket4j-parent/pom.xml b/bucket4j-parent/pom.xml index a944e5c9..cce8df53 100644 --- a/bucket4j-parent/pom.xml +++ b/bucket4j-parent/pom.xml @@ -7,7 +7,7 @@ 4.0.0 com.github.vladimir-bukhtoyarov - 7.3.0 + 7.4.0 bucket4j-parent pom bucket4j-parent @@ -65,7 +65,7 @@ 1.15.1 5.0.2.RELEASE 2.9.0 - 3.11.5 + 3.16.8 2.9.3 @@ -81,10 +81,21 @@ Vladimir Bukhtoyarov jsecoder@mail.ru https://github.com/vladimir-bukhtoyarov - DINS - http://www.dins.ru/ + edna + https://edna.ru/ - Lead software developer + Software developer + + + + MaxBartkov + Maxim Bartkov + maxgalayoutop@gmail.com + https://github.com/MaxBartkov + Jaja finance + https://jaja.co.uk + + Java Technical Leader diff --git a/bucket4j-postgresql/pom.xml b/bucket4j-postgresql/pom.xml index 76b22e4a..e839b262 100644 --- a/bucket4j-postgresql/pom.xml +++ b/bucket4j-postgresql/pom.xml @@ -7,7 +7,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent diff --git a/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLSelectForUpdateBasedProxyManager.java b/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLSelectForUpdateBasedProxyManager.java index a282c2c7..66cf8138 100644 --- a/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLSelectForUpdateBasedProxyManager.java +++ b/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLSelectForUpdateBasedProxyManager.java @@ -167,8 +167,7 @@ public void release() { @Override public void removeProxy(Long key) { - try { - Connection connection = dataSource.getConnection(); + try (Connection connection = dataSource.getConnection()) { try(PreparedStatement removeStatement = connection.prepareStatement(removeSqlQuery)) { removeStatement.setLong(1, key); removeStatement.executeUpdate(); diff --git a/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLadvisoryLockBasedProxyManager.java b/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLadvisoryLockBasedProxyManager.java index 616541c2..7e722bce 100644 --- a/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLadvisoryLockBasedProxyManager.java +++ b/bucket4j-postgresql/src/main/java/io/github/bucket4j/postgresql/PostgreSQLadvisoryLockBasedProxyManager.java @@ -177,8 +177,7 @@ public void unlock() { @Override public void removeProxy(Long key) { - try { - Connection connection = dataSource.getConnection(); + try (Connection connection = dataSource.getConnection()) { try(PreparedStatement removeStatement = connection.prepareStatement(removeSqlQuery)) { removeStatement.setLong(1, key); removeStatement.executeUpdate(); diff --git a/bucket4j-redis/pom.xml b/bucket4j-redis/pom.xml index edbb15c0..48aadae7 100644 --- a/bucket4j-redis/pom.xml +++ b/bucket4j-redis/pom.xml @@ -8,7 +8,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent bucket4j-redis diff --git a/bucket4j-redis/src/main/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManager.java b/bucket4j-redis/src/main/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManager.java index d99069fd..21b71cf6 100644 --- a/bucket4j-redis/src/main/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManager.java +++ b/bucket4j-redis/src/main/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManager.java @@ -27,7 +27,9 @@ import io.netty.buffer.ByteBuf; import org.redisson.api.RFuture; import org.redisson.client.codec.ByteArrayCodec; +import org.redisson.client.protocol.RedisCommand; import org.redisson.client.protocol.RedisCommands; +import org.redisson.client.protocol.convertor.BooleanNotNullReplayConvertor; import org.redisson.command.CommandExecutor; import java.io.IOException; @@ -40,6 +42,8 @@ public class RedissonBasedProxyManager extends AbstractCompareAndSwapBasedProxyManager { + public static RedisCommand SETPXNX_WORK_ARROUND = new RedisCommand("SET", new BooleanNotNullReplayConvertor()); + private final CommandExecutor commandExecutor; private final long ttlMillis; @@ -66,7 +70,7 @@ public Optional getStateData() { public boolean compareAndSwap(byte[] originalData, byte[] newData) { if (originalData == null) { // Redisson prohibits the usage null as values, so "replace" must not be used in such cases - RFuture redissonFuture = commandExecutor.writeAsync(key, ByteArrayCodec.INSTANCE, RedisCommands.SETPXNX, key, encodeByteArray(newData), "PX", ttlMillis, "NX"); + RFuture redissonFuture = commandExecutor.writeAsync(key, ByteArrayCodec.INSTANCE, SETPXNX_WORK_ARROUND, key, encodeByteArray(newData), "PX", ttlMillis, "NX"); return commandExecutor.get(redissonFuture); } else { String script = @@ -99,7 +103,7 @@ public CompletableFuture> getStateData() { @Override public CompletableFuture compareAndSwap(byte[] originalData, byte[] newData) { if (originalData == null) { - RFuture redissonFuture = commandExecutor.writeAsync(key, ByteArrayCodec.INSTANCE, RedisCommands.SETPXNX, key, encodeByteArray(newData), "PX", ttlMillis, "NX"); + RFuture redissonFuture = commandExecutor.writeAsync(key, ByteArrayCodec.INSTANCE, SETPXNX_WORK_ARROUND, key, encodeByteArray(newData), "PX", ttlMillis, "NX"); return convertFuture(redissonFuture); } else { String script = diff --git a/bucket4j-redis/src/test/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManagerTest.java b/bucket4j-redis/src/test/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManagerTest.java index 74e63798..e8fe2988 100644 --- a/bucket4j-redis/src/test/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManagerTest.java +++ b/bucket4j-redis/src/test/java/io/github/bucket4j/redis/redisson/cas/RedissonBasedProxyManagerTest.java @@ -5,6 +5,8 @@ import io.github.bucket4j.tck.AbstractDistributedBucketTest; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.redisson.command.CommandExecutor; +import org.redisson.command.CommandSyncService; import org.redisson.config.Config; import org.redisson.config.ConfigSupport; import org.redisson.connection.ConnectionManager; @@ -17,11 +19,13 @@ public class RedissonBasedProxyManagerTest extends AbstractDistributedBucketTest private static GenericContainer container; private static ConnectionManager connectionManager; + private static CommandExecutor commandExecutor; @BeforeClass public static void setup() { container = startRedisContainer(); connectionManager = createRedissonClient(container); + commandExecutor = createRedissonExecutor(connectionManager); } @AfterClass @@ -46,6 +50,10 @@ private static ConnectionManager createRedissonClient(GenericContainer container return connectionManager; } + private static CommandExecutor createRedissonExecutor(ConnectionManager connectionManager) { + return new CommandSyncService(connectionManager, null); + } + private static GenericContainer startRedisContainer() { GenericContainer genericContainer = new GenericContainer("redis:4.0.11") .withExposedPorts(6379); @@ -55,7 +63,7 @@ private static GenericContainer startRedisContainer() { @Override protected ProxyManager getProxyManager() { - return new RedissonBasedProxyManager(connectionManager.getCommandExecutor(), ClientSideConfig.getDefault(), Duration.ofMinutes(10)); + return new RedissonBasedProxyManager(commandExecutor, ClientSideConfig.getDefault(), Duration.ofMinutes(10)); } @Override diff --git a/experimental/bucket4j-lua/pom.xml b/experimental/bucket4j-lua/pom.xml index 2fbd48ce..32c7e7de 100644 --- a/experimental/bucket4j-lua/pom.xml +++ b/experimental/bucket4j-lua/pom.xml @@ -24,7 +24,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../../bucket4j-parent bucket4j-lua diff --git a/experimental/pom.xml b/experimental/pom.xml index 33434106..18130f0a 100644 --- a/experimental/pom.xml +++ b/experimental/pom.xml @@ -6,7 +6,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent 4.0.0 diff --git a/lincheck-tests/pom.xml b/lincheck-tests/pom.xml index a7390009..67a70b92 100644 --- a/lincheck-tests/pom.xml +++ b/lincheck-tests/pom.xml @@ -6,7 +6,7 @@ com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ../bucket4j-parent diff --git a/pom.xml b/pom.xml index 0aca00db..228b4e17 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ com.github.vladimir-bukhtoyarov bucket4j - 7.3.0 + 7.4.0 pom com.github.vladimir-bukhtoyarov bucket4j-parent - 7.3.0 + 7.4.0 ./bucket4j-parent