From 8a4c7108e48c5186da2346c05eecf6fdb31cb37a Mon Sep 17 00:00:00 2001 From: Guillaume Lederrey Date: Thu, 27 Jul 2017 14:15:09 +0200 Subject: [PATCH 1/2] Use the newer java.time.Duration to represent durations Up to now the code used (or abused) java.util.concurrent.TimeUnit. Duration is a better representation, which encapsulate the amount and the unit in a single class. Method using TimeUnit have been deprecated and delegate to the new methods using Duration. A dependency to threeten-extra has been added to do this convertion. It can be removed once all TimeUnit based methods have been removed. --- pom.xml | 8 ++++ .../util/FixedIntervalRefillStrategy.java | 36 ++++++++++++-- .../org/isomorphism/util/TokenBucket.java | 28 +++++++++++ .../org/isomorphism/util/TokenBucketImpl.java | 32 +++++++++---- .../org/isomorphism/util/TokenBuckets.java | 18 ++++++- .../util/FixedIntervalRefillStrategyTest.java | 48 ++++++++++--------- .../isomorphism/util/TokenBucketImplTest.java | 6 +++ 7 files changed, 140 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index 4555bd2..b8e8e99 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,8 @@ + 1.8 + 1.8 UTF-8 UTF-8 @@ -65,6 +67,12 @@ 18.0 + + org.threeten + threeten-extra + 1.2 + + junit junit diff --git a/src/main/java/org/isomorphism/util/FixedIntervalRefillStrategy.java b/src/main/java/org/isomorphism/util/FixedIntervalRefillStrategy.java index 0639a70..89d1616 100644 --- a/src/main/java/org/isomorphism/util/FixedIntervalRefillStrategy.java +++ b/src/main/java/org/isomorphism/util/FixedIntervalRefillStrategy.java @@ -16,9 +16,16 @@ package org.isomorphism.util; import com.google.common.base.Ticker; +import org.threeten.extra.Temporals; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; +import static java.time.temporal.ChronoUnit.NANOS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.threeten.extra.Temporals.chronoUnit; + /** * A token bucket refill strategy that will provide N tokens for a token bucket to consume every T units of time. * The tokens are refilled in bursts rather than at a fixed rate. This refill strategy will never allow more than @@ -39,14 +46,29 @@ public class FixedIntervalRefillStrategy implements TokenBucketImpl.RefillStrate * @param numTokensPerPeriod The number of tokens to add to the bucket every period. * @param period How often to refill the bucket. * @param unit Unit for period. + * + * @deprecated since 1.8 use {@link FixedIntervalRefillStrategy#FixedIntervalRefillStrategy(Ticker, long, Duration)}. */ + @Deprecated public FixedIntervalRefillStrategy(Ticker ticker, long numTokensPerPeriod, long period, TimeUnit unit) + { + this(ticker, numTokensPerPeriod, Duration.of(period, chronoUnit(unit))); + } + + /** + * Create a FixedIntervalRefillStrategy. + * + * @param ticker A ticker to use to measure time. + * @param numTokensPerPeriod The number of tokens to add to the bucket every period. + * @param period How often to refill the bucket. + */ + public FixedIntervalRefillStrategy(Ticker ticker, long numTokensPerPeriod, Duration period) { this.ticker = ticker; this.numTokensPerPeriod = numTokensPerPeriod; - this.periodDurationInNanos = unit.toNanos(period); - this.lastRefillTime = -periodDurationInNanos; - this.nextRefillTime = -periodDurationInNanos; + this.periodDurationInNanos = period.toNanos(); + this.lastRefillTime = -period.toNanos(); + this.nextRefillTime = -period.toNanos(); } @Override @@ -72,9 +94,15 @@ public synchronized long refill() @Override public long getDurationUntilNextRefill(TimeUnit unit) + { + return unit.convert(getDurationUntilNextRefill().toNanos(), NANOSECONDS); + } + + @Override + public Duration getDurationUntilNextRefill() { long now = ticker.read(); - return unit.convert(Math.max(0, nextRefillTime - now), TimeUnit.NANOSECONDS); + return Duration.of(Math.max(0, nextRefillTime - now), NANOS); } } diff --git a/src/main/java/org/isomorphism/util/TokenBucket.java b/src/main/java/org/isomorphism/util/TokenBucket.java index 232fb95..c18c17f 100644 --- a/src/main/java/org/isomorphism/util/TokenBucket.java +++ b/src/main/java/org/isomorphism/util/TokenBucket.java @@ -15,6 +15,7 @@ */ package org.isomorphism.util; +import java.time.Duration; import java.util.concurrent.TimeUnit; /** @@ -47,9 +48,20 @@ public interface TokenBucket * @see org.isomorphism.util.TokenBucket.RefillStrategy#getDurationUntilNextRefill(java.util.concurrent.TimeUnit) * @param unit The time unit to express the return value in. * @return The amount of time until the next group of tokens can be added to the token bucket. + * + * @deprecated since 1.8, see {@link TokenBucket#getDurationUntilNextRefill()} */ + @Deprecated long getDurationUntilNextRefill(TimeUnit unit) throws UnsupportedOperationException; + /** + * Returns the amount of time until the next group of tokens can be added to the token bucket. + * + * @see org.isomorphism.util.TokenBucket.RefillStrategy#getDurationUntilNextRefill() + * @return The amount of time until the next group of tokens can be added to the token bucket. + */ + Duration getDurationUntilNextRefill() throws UnsupportedOperationException; + /** * Attempt to consume a single token from the bucket. If it was consumed then {@code true} is returned, otherwise * {@code false} is returned. @@ -110,8 +122,24 @@ static interface RefillStrategy * * @param unit The time unit to express the return value in. * @return The amount of time until the next group of tokens can be added to the token bucket. + * + * @deprecated since 1.8, see {@link RefillStrategy#getDurationUntilNextRefill()} */ + @Deprecated long getDurationUntilNextRefill(TimeUnit unit) throws UnsupportedOperationException; + + /** + * Returns the amount of time until the next group of tokens can be added to the token + * bucket. Please note, depending on the {@code SleepStrategy} used by the token bucket, tokens may not actually + * be added until much after the returned duration. If for some reason the implementation of + * {@code RefillStrategy} doesn't support knowing when the next batch of tokens will be added, then an + * {@code UnsupportedOperationException} may be thrown. Lastly, if the duration until the next time tokens will + * be added to the token bucket is less than a single unit of the passed in time unit then this method will + * return 0. + * + * @return The amount of time until the next group of tokens can be added to the token bucket. + */ + Duration getDurationUntilNextRefill(); } /** Encapsulation of a strategy for relinquishing control of the CPU. */ diff --git a/src/main/java/org/isomorphism/util/TokenBucketImpl.java b/src/main/java/org/isomorphism/util/TokenBucketImpl.java index c27cefb..f4707b7 100644 --- a/src/main/java/org/isomorphism/util/TokenBucketImpl.java +++ b/src/main/java/org/isomorphism/util/TokenBucketImpl.java @@ -15,10 +15,12 @@ */ package org.isomorphism.util; +import java.time.Duration; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * A token bucket implementation that is of a leaky bucket in the sense that it has a finite capacity and any added @@ -80,17 +82,31 @@ public synchronized long getNumTokens() } /** - * Returns the amount of time in the specified time unit until the next group of tokens can be added to the token - * bucket. - * - * @see org.isomorphism.util.TokenBucket.RefillStrategy#getDurationUntilNextRefill(java.util.concurrent.TimeUnit) - * @param unit The time unit to express the return value in. - * @return The amount of time until the next group of tokens can be added to the token bucket. - */ + * Returns the amount of time in the specified time unit until the next group of tokens can be added to the token + * bucket. + * + * @see org.isomorphism.util.TokenBucket.RefillStrategy#getDurationUntilNextRefill(java.util.concurrent.TimeUnit) + * @param unit The time unit to express the return value in. + * @return The amount of time until the next group of tokens can be added to the token bucket. + */ @Override + @Deprecated public long getDurationUntilNextRefill(TimeUnit unit) throws UnsupportedOperationException { - return refillStrategy.getDurationUntilNextRefill(unit); + return unit.convert(getDurationUntilNextRefill().toNanos(), NANOSECONDS); + } + + /** + * Returns the amount of time in the specified time unit until the next group of tokens can be added to the token + * bucket. + * + * @see org.isomorphism.util.TokenBucket.RefillStrategy#getDurationUntilNextRefill() + * @return The amount of time until the next group of tokens can be added to the token bucket. + */ + @Override + public Duration getDurationUntilNextRefill() throws UnsupportedOperationException + { + return refillStrategy.getDurationUntilNextRefill(); } /** diff --git a/src/main/java/org/isomorphism/util/TokenBuckets.java b/src/main/java/org/isomorphism/util/TokenBuckets.java index ffedcad..851505d 100644 --- a/src/main/java/org/isomorphism/util/TokenBuckets.java +++ b/src/main/java/org/isomorphism/util/TokenBuckets.java @@ -17,11 +17,14 @@ import com.google.common.base.Ticker; import com.google.common.util.concurrent.Uninterruptibles; +import org.threeten.extra.Temporals; +import java.time.Duration; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static org.threeten.extra.Temporals.chronoUnit; /** Static utility methods pertaining to creating {@link TokenBucketImpl} instances. */ public final class TokenBuckets @@ -58,10 +61,21 @@ public Builder withInitialTokens(long numTokens) return this; } - /** Refill tokens at a fixed interval. */ + /** + * Refill tokens at a fixed interval. + * + * @deprecated since 1.8, see {@link TokenBuckets.Builder#withFixedIntervalRefillStrategy(long, Duration)} + */ + @Deprecated public Builder withFixedIntervalRefillStrategy(long refillTokens, long period, TimeUnit unit) { - return withRefillStrategy(new FixedIntervalRefillStrategy(ticker, refillTokens, period, unit)); + return withFixedIntervalRefillStrategy(refillTokens, Duration.of(period, chronoUnit(unit))); + } + + /** Refill tokens at a fixed interval. */ + public Builder withFixedIntervalRefillStrategy(long refillTokens, Duration period) + { + return withRefillStrategy(new FixedIntervalRefillStrategy(ticker, refillTokens, period)); } /** Use a user defined refill strategy. */ diff --git a/src/test/java/org/isomorphism/util/FixedIntervalRefillStrategyTest.java b/src/test/java/org/isomorphism/util/FixedIntervalRefillStrategyTest.java index 861a986..408cfeb 100644 --- a/src/test/java/org/isomorphism/util/FixedIntervalRefillStrategyTest.java +++ b/src/test/java/org/isomorphism/util/FixedIntervalRefillStrategyTest.java @@ -18,18 +18,22 @@ import com.google.common.base.Ticker; import org.junit.Test; +import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; +import static java.time.temporal.ChronoUnit.SECONDS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class FixedIntervalRefillStrategyTest { private static final long N = 5; // 5 tokens - private static final long P = 10; // every 10 + private static final Duration P = Duration.of(10, SECONDS); // every 10 private static final TimeUnit U = TimeUnit.SECONDS; // seconds private final MockTicker ticker = new MockTicker(); - private final FixedIntervalRefillStrategy strategy = new FixedIntervalRefillStrategy(ticker, N, P, U); + private final FixedIntervalRefillStrategy strategy = new FixedIntervalRefillStrategy(ticker, N, P); @Test public void testFirstRefill() @@ -43,8 +47,8 @@ public void testNoRefillUntilPeriodUp() strategy.refill(); // Another refill shouldn't come for P time units. - for (int i = 0; i < P - 1; i++) { - ticker.advance(1, U); + for (int i = 0; i < P.getSeconds() - 1; i++) { + ticker.advance(Duration.of(1, SECONDS)); assertEquals(0, strategy.refill()); } } @@ -54,13 +58,13 @@ public void testRefillEveryPeriod() { strategy.refill(); - ticker.advance(P, U); + ticker.advance(P); assertEquals(N, strategy.refill()); - ticker.advance(P, U); + ticker.advance(P); assertEquals(N, strategy.refill()); - ticker.advance(P, U); + ticker.advance(P); assertEquals(N, strategy.refill()); } @@ -70,10 +74,10 @@ public void testRefillEveryOtherPeriod() strategy.refill(); // Move time forward two periods, since we're skipping a period next time we should add double the tokens. - ticker.advance(2 * P, U); + ticker.advance(P.multipliedBy(2)); assertEquals(2 * N, strategy.refill()); - ticker.advance(2 * P, U); + ticker.advance(P.multipliedBy(2)); assertEquals(2 * N, strategy.refill()); } @@ -87,23 +91,23 @@ public void testRefillOnNonEvenPeriods() assertEquals(N, strategy.refill()); // t = P+1 - ticker.advance(P + 1, U); + ticker.advance(P.plus(1, SECONDS)); assertEquals(N, strategy.refill()); // t = 2P+1 - ticker.advance(P, U); + ticker.advance(P); assertEquals(N, strategy.refill()); // t = 3P - ticker.advance(P - 1, U); + ticker.advance(P.minus(1, SECONDS)); assertEquals(N, strategy.refill()); // t = 4P-1 - ticker.advance(P - 1, U); + ticker.advance(P.minus(1, SECONDS)); assertEquals(0, strategy.refill()); // t = 4P - ticker.advance(1, U); + ticker.advance(Duration.of(1, SECONDS)); assertEquals(N, strategy.refill()); } @@ -119,9 +123,9 @@ public void testDurationAfterFirstRefill() { strategy.refill(); - for (int i = 0; i < P - 1; i++) { - assertEquals(P - i, strategy.getDurationUntilNextRefill(TimeUnit.SECONDS)); - ticker.advance(1, U); + for (int i = 0; i < P.getSeconds() - 1; i++) { + assertEquals(P.getSeconds() - i, strategy.getDurationUntilNextRefill().getSeconds()); + ticker.advance(Duration.of(1, SECONDS)); } } @@ -129,9 +133,9 @@ public void testDurationAfterFirstRefill() public void testDurationAtSecondRefillTime() { strategy.refill(); - ticker.advance(P, U); + ticker.advance(P); - assertEquals(0, strategy.getDurationUntilNextRefill(TimeUnit.SECONDS)); + assertTrue(strategy.getDurationUntilNextRefill().isZero()); } @Test @@ -139,7 +143,7 @@ public void testDurationInProperUnits() { strategy.refill(); - assertEquals(10000, strategy.getDurationUntilNextRefill(TimeUnit.MILLISECONDS)); + assertEquals(P, strategy.getDurationUntilNextRefill()); } private static final class MockTicker extends Ticker @@ -152,9 +156,9 @@ public long read() return now; } - public void advance(long delta, TimeUnit unit) + public void advance(Duration delta) { - now += unit.toNanos(delta); + now += delta.toNanos(); } } } diff --git a/src/test/java/org/isomorphism/util/TokenBucketImplTest.java b/src/test/java/org/isomorphism/util/TokenBucketImplTest.java index e339c61..661b446 100644 --- a/src/test/java/org/isomorphism/util/TokenBucketImplTest.java +++ b/src/test/java/org/isomorphism/util/TokenBucketImplTest.java @@ -17,6 +17,7 @@ import org.junit.Test; +import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; @@ -234,6 +235,11 @@ public long getDurationUntilNextRefill(TimeUnit unit) throws UnsupportedOperatio throw new UnsupportedOperationException(); } + @Override + public Duration getDurationUntilNextRefill() { + throw new UnsupportedOperationException(); + } + public void addToken() { numTokensToAdd++; From d3bfc5d1fcf66a57450ee6752ce377897496a9f4 Mon Sep 17 00:00:00 2001 From: Guillaume Lederrey Date: Thu, 27 Jul 2017 14:44:54 +0200 Subject: [PATCH 2/2] switch travis to Java 8 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index dff5f3a..9bcf999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,3 @@ language: java +jdk: + - oraclejdk8