Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
language: java
jdk:
- oraclejdk8
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
</licenses>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
Expand Down Expand Up @@ -65,6 +67,12 @@
<version>18.0</version>
</dependency>

<dependency>
<groupId>org.threeten</groupId>
<artifactId>threeten-extra</artifactId>
<version>1.2</version>
</dependency>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be achieved without adding a new dependency?


<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be replaced by a JDK Clock?

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
Expand All @@ -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);
}
}

28 changes: 28 additions & 0 deletions src/main/java/org/isomorphism/util/TokenBucket.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.isomorphism.util;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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. */
Expand Down
32 changes: 24 additions & 8 deletions src/main/java/org/isomorphism/util/TokenBucketImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}

/**
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/org/isomorphism/util/TokenBuckets.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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());
}
}
Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -119,27 +123,27 @@ 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));
}
}

@Test
public void testDurationAtSecondRefillTime()
{
strategy.refill();
ticker.advance(P, U);
ticker.advance(P);

assertEquals(0, strategy.getDurationUntilNextRefill(TimeUnit.SECONDS));
assertTrue(strategy.getDurationUntilNextRefill().isZero());
}

@Test
public void testDurationInProperUnits()
{
strategy.refill();

assertEquals(10000, strategy.getDurationUntilNextRefill(TimeUnit.MILLISECONDS));
assertEquals(P, strategy.getDurationUntilNextRefill());
}

private static final class MockTicker extends Ticker
Expand All @@ -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();
}
}
}
6 changes: 6 additions & 0 deletions src/test/java/org/isomorphism/util/TokenBucketImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.junit.Test;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -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++;
Expand Down