diff --git a/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java b/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java index 5751b1dda6..166cf62646 100644 --- a/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java +++ b/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java @@ -18,7 +18,7 @@ */ package org.jclouds.rest.suppliers; -import static org.jclouds.util.Suppliers2.memoizeWithExpirationOnAbsoluteInterval; +import static org.jclouds.util.Suppliers2.memoizeWithExpirationAfterWrite; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -54,7 +54,7 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier imp public MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier( AtomicReference authException, long seconds, Supplier delegate) { - this.delegate = memoizeWithExpirationOnAbsoluteInterval(new RetryOnTimeOutExceptionSupplier( + this.delegate = memoizeWithExpirationAfterWrite(new RetryOnTimeOutExceptionSupplier( new SetAndThrowAuthorizationExceptionSupplier(delegate, authException)), seconds, TimeUnit.SECONDS); this.seconds = seconds; } diff --git a/core/src/main/java/org/jclouds/util/Suppliers2.java b/core/src/main/java/org/jclouds/util/Suppliers2.java index 44cbb5f271..8456f00040 100644 --- a/core/src/main/java/org/jclouds/util/Suppliers2.java +++ b/core/src/main/java/org/jclouds/util/Suppliers2.java @@ -26,12 +26,14 @@ import java.io.Serializable; import java.util.Map; import java.util.concurrent.TimeUnit; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Objects; -import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ForwardingObject; import com.google.common.collect.Iterables; import com.google.common.io.OutputSupplier; @@ -57,15 +59,15 @@ public class Suppliers2 { } public static Function> ofInstanceFunction() { - return new Function>(){ + return new Function>() { @Override public Supplier apply(X arg0) { return Suppliers.ofInstance(arg0); } - + @Override - public String toString(){ + public String toString() { return "Suppliers.ofInstance()"; } }; @@ -85,70 +87,43 @@ public class Suppliers2 { } /** - * See Supplier.memoizeWithExpiration. + * same as {@link Supplier.memoizeWithExpiration} except that the expiration ticker starts after + * write vs after call to {@code get}. * - * Difference between this impl and v11.0 is that we fix - * http://code.google.com/p/guava-libraries/issues/detail?id=857. + * @see Supplier.memoizeWithExpiration */ - public static Supplier memoizeWithExpirationOnAbsoluteInterval(Supplier delegate, long duration, - TimeUnit unit) { - return new ExpiringMemoizingSupplier(delegate, duration, unit); + public static Supplier memoizeWithExpirationAfterWrite(Supplier delegate, long duration, TimeUnit unit) { + return new ExpireAfterWriteSupplier(delegate, duration, unit); } - @VisibleForTesting - static class ExpiringMemoizingSupplier implements Supplier, Serializable { - final Supplier delegate; - final long durationNanos; - transient volatile T value; - // The special value 0 means "not yet initialized". - transient volatile long expirationNanos; + static class ExpireAfterWriteSupplier extends ForwardingObject implements Supplier, Serializable { + private final Supplier delegate; + private final long duration; + private final TimeUnit unit; + private final LoadingCache cache; - ExpiringMemoizingSupplier(Supplier delegate, long duration, TimeUnit unit) { - this.delegate = Preconditions.checkNotNull(delegate); - this.durationNanos = unit.toNanos(duration); - Preconditions.checkArgument(duration > 0); + public ExpireAfterWriteSupplier(Supplier delegate, long duration, TimeUnit unit) { + this.delegate = delegate; + this.duration = duration; + this.unit = unit; + cache = CacheBuilder.newBuilder().expireAfterWrite(duration, unit).build(CacheLoader.from(delegate)); + } + + @Override + protected Supplier delegate() { + return delegate; } @Override public T get() { - // Another variant of Double Checked Locking. - // - // We use two volatile reads. We could reduce this to one by - // putting our fields into a holder class, but (at least on x86) - // the extra memory consumption and indirection are more - // expensive than the extra volatile reads. - long nanos = expirationNanos; - long now = System.nanoTime(); - if (nanos == 0 || now - nanos >= 0) { - synchronized (this) { - if (nanos == expirationNanos) { // recheck for lost race - - // Set value to null prior to retrieving new val, so old and new are not held in - // memory simultaneously - value = null; - - T t = delegate.get(); - value = t; - - // Update now so that, if call was expensive, we keep value for the full duration - now = System.nanoTime(); - - nanos = now + durationNanos; - // In the very unlikely event that nanos is 0, set it to 1; - // no one will notice 1 ns of tardiness. - expirationNanos = (nanos == 0) ? 1 : nanos; - return t; - } - } - } - return value; + return cache.getUnchecked("FOO"); } private static final long serialVersionUID = 0; @Override public int hashCode() { - return Objects.hashCode(delegate, durationNanos); + return Objects.hashCode(delegate, duration, unit); } @Override @@ -159,13 +134,14 @@ public class Suppliers2 { return false; if (getClass() != obj.getClass()) return false; - ExpiringMemoizingSupplier that = ExpiringMemoizingSupplier.class.cast(obj); - return Objects.equal(delegate, that.delegate) && Objects.equal(durationNanos, that.durationNanos); + ExpireAfterWriteSupplier that = ExpireAfterWriteSupplier.class.cast(obj); + return Objects.equal(delegate, that.delegate) && Objects.equal(duration, that.duration); } @Override public String toString() { - return Objects.toStringHelper(this).add("delegate", delegate).add("durationNanos", durationNanos).toString(); + return Objects.toStringHelper(this).add("delegate", delegate).add("duration", duration).add("unit", unit) + .toString(); } } diff --git a/core/src/test/java/org/jclouds/util/Suppliers2Test.java b/core/src/test/java/org/jclouds/util/Suppliers2Test.java index 7e85f90b5a..d82bf9c33d 100644 --- a/core/src/test/java/org/jclouds/util/Suppliers2Test.java +++ b/core/src/test/java/org/jclouds/util/Suppliers2Test.java @@ -74,7 +74,7 @@ public class Suppliers2Test { } }; - Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationOnAbsoluteInterval( + Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationAfterWrite( slowSupplier, EXPIRATION_TIME, TimeUnit.MILLISECONDS); assertEquals(memoizedSupplier.get(), (Integer)10); @@ -100,7 +100,7 @@ public class Suppliers2Test { public void testMemoizeWithExpiration() throws InterruptedException { CountingSupplier countingSupplier = new CountingSupplier(); - Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationOnAbsoluteInterval( + Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationAfterWrite( countingSupplier, 75, TimeUnit.MILLISECONDS); checkExpiration(countingSupplier, memoizedSupplier); @@ -111,7 +111,7 @@ public class Suppliers2Test { throws InterruptedException { CountingSupplier countingSupplier = new CountingSupplier(); - Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationOnAbsoluteInterval( + Supplier memoizedSupplier = Suppliers2.memoizeWithExpirationAfterWrite( countingSupplier, 75, TimeUnit.MILLISECONDS); // Calls to the original memoized supplier shouldn't affect its copy. memoizedSupplier.get(); @@ -120,7 +120,7 @@ public class Suppliers2Test { memoizedSupplier.get(); CountingSupplier countingCopy = (CountingSupplier) - ((Suppliers2.ExpiringMemoizingSupplier) copy).delegate; + ((Suppliers2.ExpireAfterWriteSupplier) copy).delegate(); checkExpiration(countingCopy, copy); } @@ -154,7 +154,7 @@ public class Suppliers2Test { Function, Supplier> memoizer = new Function, Supplier>() { @Override public Supplier apply(Supplier supplier) { - return Suppliers2.memoizeWithExpirationOnAbsoluteInterval( + return Suppliers2.memoizeWithExpirationAfterWrite( supplier, Long.MAX_VALUE, TimeUnit.NANOSECONDS); } };