Skip to content

Commit

Permalink
Move Clock out of ServiceOptions, use it in RetryHelper
Browse files Browse the repository at this point in the history
  • Loading branch information
mziccard committed Apr 22, 2016
1 parent e213176 commit 329ab44
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 68 deletions.
59 changes: 59 additions & 0 deletions gcloud-java-core/src/main/java/com/google/cloud/Clock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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.
*/

package com.google.cloud;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
* A class providing access to the current time in milliseconds. This class is mainly used for
* testing and will be replaced by Java8's {@code java.time.Clock}.
*
* <p>Implementations should implement {@code Serializable} wherever possible and must document
* whether or not they do support serialization.
*/
public abstract class Clock {

private static final Clock DEFAULT_TIME_SOURCE = new DefaultClock();

/**
* Returns current time in milliseconds according to this clock.
*/
public abstract long millis();

/**
* Returns the default clock. Default clock uses {@link System#currentTimeMillis()} to get time
* in milliseconds.
*/
public static Clock defaultClock() {
return DEFAULT_TIME_SOURCE;
}

private static class DefaultClock extends Clock implements Serializable {

private static final long serialVersionUID = -5077300394286703864L;

@Override
public long millis() {
return System.currentTimeMillis();
}

private Object readResolve() throws ObjectStreamException {
return DEFAULT_TIME_SOURCE;
}
}
}
20 changes: 9 additions & 11 deletions gcloud-java-core/src/main/java/com/google/cloud/RetryHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@
import static java.lang.StrictMath.min;
import static java.lang.StrictMath.pow;
import static java.lang.StrictMath.random;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Stopwatch;

import java.io.InterruptedIOException;
import java.nio.channels.ClosedByInterruptException;
Expand All @@ -45,7 +43,7 @@ public class RetryHelper<V> {

private static final Logger log = Logger.getLogger(RetryHelper.class.getName());

private final Stopwatch stopwatch;
private final Clock clock;
private final Callable<V> callable;
private final RetryParams params;
private final ExceptionHandler exceptionHandler;
Expand Down Expand Up @@ -153,10 +151,10 @@ static Context getContext() {

@VisibleForTesting
RetryHelper(Callable<V> callable, RetryParams params, ExceptionHandler exceptionHandler,
Stopwatch stopwatch) {
Clock clock) {
this.callable = checkNotNull(callable);
this.params = checkNotNull(params);
this.stopwatch = checkNotNull(stopwatch);
this.clock = checkNotNull(clock);
this.exceptionHandler = checkNotNull(exceptionHandler);
exceptionHandler.verifyCaller(callable);
}
Expand All @@ -165,15 +163,15 @@ static Context getContext() {
public String toString() {
ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
toStringHelper.add("params", params);
toStringHelper.add("stopwatch", stopwatch);
toStringHelper.add("clock", clock);
toStringHelper.add("attemptNumber", attemptNumber);
toStringHelper.add("callable", callable);
toStringHelper.add("exceptionHandler", exceptionHandler);
return toStringHelper.toString();
}

private V doRetry() throws RetryHelperException {
stopwatch.start();
long start = clock.millis();
while (true) {
attemptNumber++;
Exception exception;
Expand All @@ -196,7 +194,7 @@ private V doRetry() throws RetryHelperException {
}
if (attemptNumber >= params.retryMaxAttempts()
|| attemptNumber >= params.retryMinAttempts()
&& stopwatch.elapsed(MILLISECONDS) >= params.totalRetryPeriodMillis()) {
&& clock.millis() - start >= params.totalRetryPeriodMillis()) {
throw new RetriesExhaustedException(this + ": Too many failures, giving up", exception);
}
long sleepDurationMillis = getSleepDuration(params, attemptNumber);
Expand Down Expand Up @@ -234,13 +232,13 @@ public static <V> V runWithRetries(Callable<V> callable) throws RetryHelperExcep

public static <V> V runWithRetries(Callable<V> callable, RetryParams params,
ExceptionHandler exceptionHandler) throws RetryHelperException {
return runWithRetries(callable, params, exceptionHandler, Stopwatch.createUnstarted());
return runWithRetries(callable, params, exceptionHandler, Clock.defaultClock());
}

@VisibleForTesting
static <V> V runWithRetries(Callable<V> callable, RetryParams params,
ExceptionHandler exceptionHandler, Stopwatch stopwatch) throws RetryHelperException {
RetryHelper<V> retryHelper = new RetryHelper<>(callable, params, exceptionHandler, stopwatch);
ExceptionHandler exceptionHandler, Clock clock) throws RetryHelperException {
RetryHelper<V> retryHelper = new RetryHelper<>(callable, params, exceptionHandler, clock);
Context previousContext = getContext();
setContext(new Context(retryHelper));
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
Expand Down Expand Up @@ -125,45 +124,6 @@ public HttpTransport create() {
}
}

/**
* A class providing access to the current time in milliseconds. This class is mainly used for
* testing and will be replaced by Java8's {@code java.time.Clock}.
*
* <p>Implementations should implement {@code Serializable} wherever possible and must document
* whether or not they do support serialization.
*/
public abstract static class Clock {

private static final ServiceOptions.Clock DEFAULT_TIME_SOURCE = new DefaultClock();

/**
* Returns current time in milliseconds according to this clock.
*/
public abstract long millis();

/**
* Returns the default clock. Default clock uses {@link System#currentTimeMillis()} to get time
* in milliseconds.
*/
public static ServiceOptions.Clock defaultClock() {
return DEFAULT_TIME_SOURCE;
}

private static class DefaultClock extends ServiceOptions.Clock implements Serializable {

private static final long serialVersionUID = -5077300394286703864L;

@Override
public long millis() {
return System.currentTimeMillis();
}

private Object readResolve() throws ObjectStreamException {
return DEFAULT_TIME_SOURCE;
}
}
}

/**
* Builder for {@code ServiceOptions}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

import com.google.cloud.RetryHelper.NonRetriableException;
import com.google.cloud.RetryHelper.RetriesExhaustedException;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;

import org.junit.Test;

Expand Down Expand Up @@ -157,24 +155,24 @@ public void testTriesNoMoreThanMaxTimes() {
}
}

private static class FakeTicker extends Ticker {
private final AtomicLong nanos = new AtomicLong();
private static class FakeClock extends Clock {

// Advances the ticker value by {@code time} in {@code timeUnit}.
private final AtomicLong millis = new AtomicLong();

// Advances the clock value by {@code time} in {@code timeUnit}.
void advance(long time, TimeUnit timeUnit) {
nanos.addAndGet(timeUnit.toNanos(time));
millis.addAndGet(timeUnit.toMillis(time));
}

@Override
public long read() {
return nanos.get();
public long millis() {
return millis.get();
}
}

@Test
public void testTriesNoMoreLongerThanTotalRetryPeriod() {
final FakeTicker ticker = new FakeTicker();
Stopwatch stopwatch = Stopwatch.createUnstarted(ticker);
final FakeClock fakeClock = new FakeClock();
// The 8th attempt (after min and before max) will trigger a 1 second (virtual) delay exceeding
// total retry period which is set just under 1 second. Test occurs faster than realtime.
RetryParams params = RetryParams.builder().initialRetryDelayMillis(0)
Expand All @@ -190,11 +188,11 @@ public void testTriesNoMoreLongerThanTotalRetryPeriod() {
@Override public void run() {
timesCalled.incrementAndGet();
if (timesCalled.get() == sleepOnAttempt) {
ticker.advance(1000, TimeUnit.MILLISECONDS);
fakeClock.advance(1000, TimeUnit.MILLISECONDS);
}
throw new RuntimeException();
}
}), params, handler, stopwatch);
}), params, handler, fakeClock);
fail();
} catch (RetriesExhaustedException expected) {
// verify timesCalled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.google.cloud.ServiceOptions.Clock;
import com.google.cloud.ServiceOptions.DefaultHttpTransportFactory;
import com.google.cloud.ServiceOptions.HttpTransportFactory;
import com.google.cloud.spi.ServiceRpcFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
import com.google.api.services.dns.model.Change;
import com.google.api.services.dns.model.ManagedZone;
import com.google.api.services.dns.model.ResourceRecordSet;
import com.google.cloud.Clock;
import com.google.cloud.Page;
import com.google.cloud.RetryParams;
import com.google.cloud.ServiceOptions;
import com.google.cloud.dns.spi.DnsRpc;
import com.google.cloud.dns.spi.DnsRpcFactory;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -100,7 +100,7 @@ public class DnsImplTest {

// Other
private static final Map<DnsRpc.Option, ?> EMPTY_RPC_OPTIONS = ImmutableMap.of();
private static final ServiceOptions.Clock TIME_SOURCE = new ServiceOptions.Clock() {
private static final Clock TIME_SOURCE = new Clock() {
@Override
public long millis() {
return 42000L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@

import com.google.api.services.storage.model.StorageObject;
import com.google.cloud.AuthCredentials.ServiceAccountAuthCredentials;
import com.google.cloud.Clock;
import com.google.cloud.Page;
import com.google.cloud.ReadChannel;
import com.google.cloud.RetryParams;
import com.google.cloud.ServiceOptions;
import com.google.cloud.WriteChannel;
import com.google.cloud.storage.Storage.CopyRequest;
import com.google.cloud.storage.spi.StorageRpc;
Expand Down Expand Up @@ -225,7 +225,7 @@ public class StorageImplTest {
+ "EkPPhszldvQTY486uPxyD/D7HdfnGW/Nbw5JUhfvecAdudDEhNAQ3PNabyDMI+TpiHy4NTWOrgdcWrzj6VXcdc"
+ "+uuABnPwRCdcyJ1xl2kOrPksRnp1auNGMLOe4IpEBjGY7baX9UG8+A45MbG0aHmkR59Op/aR9XowIDAQAB";

private static final ServiceOptions.Clock TIME_SOURCE = new ServiceOptions.Clock() {
private static final Clock TIME_SOURCE = new Clock() {
@Override
public long millis() {
return 42000L;
Expand Down

0 comments on commit 329ab44

Please sign in to comment.