diff options
4 files changed, 100 insertions, 23 deletions
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ContainerImages.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ContainerImages.java index b4cb9158a5c..953ccaacb6b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ContainerImages.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ContainerImages.java @@ -1,10 +1,9 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.provisioning; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import com.yahoo.config.provision.DockerImage; import com.yahoo.config.provision.NodeType; +import com.yahoo.lang.CachedSupplier; import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; @@ -12,7 +11,6 @@ import java.time.Duration; import java.util.Collections; import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.logging.Logger; /** @@ -34,17 +32,12 @@ public class ContainerImages { * unnecessary ZK reads. When images change, some nodes may need to wait for TTL until they see the new image, * this is fine. */ - private volatile Supplier<Map<NodeType, DockerImage>> images; + private final CachedSupplier<Map<NodeType, DockerImage>> images; public ContainerImages(CuratorDatabaseClient db, DockerImage defaultImage) { this.db = db; this.defaultImage = defaultImage; - createCache(); - } - - private void createCache() { - this.images = Suppliers.memoizeWithExpiration(() -> Collections.unmodifiableMap(db.readContainerImages()), - cacheTtl.toMillis(), TimeUnit.MILLISECONDS); + this.images = new CachedSupplier<>(() -> Collections.unmodifiableMap(db.readContainerImages()), cacheTtl); } /** Returns the current images for each node type */ @@ -72,7 +65,7 @@ public class ContainerImages { image.ifPresentOrElse(img -> images.put(nodeType, img), () -> images.remove(nodeType)); db.writeContainerImages(images); - createCache(); // Throw away current cache + this.images.refresh(); // Throw away current cache log.info("Set container image for " + nodeType + " nodes to " + image.map(DockerImage::asString).orElse(null)); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FirmwareChecks.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FirmwareChecks.java index 25fcfecc9b8..ef66891af7c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FirmwareChecks.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/FirmwareChecks.java @@ -1,15 +1,13 @@ // Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.provisioning; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; +import com.yahoo.lang.CachedSupplier; import com.yahoo.vespa.hosted.provision.persistence.CuratorDatabaseClient; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Optional; -import java.util.concurrent.TimeUnit; /** * Keeps cached data about when to do a firmware check on the hosts managed by a node repository. @@ -28,13 +26,12 @@ public class FirmwareChecks { private final CuratorDatabaseClient database; private final Clock clock; - - private volatile Supplier<Optional<Instant>> checkAfter; + private final CachedSupplier<Optional<Instant>> checkAfter; public FirmwareChecks(CuratorDatabaseClient database, Clock clock) { this.database = database; this.clock = clock; - createCache(); + this.checkAfter = new CachedSupplier<>(database::readFirmwareCheck, cacheExpiry); } /** Returns the instant after which a firmware check is required, or empty if none currently are. */ @@ -45,17 +42,13 @@ public class FirmwareChecks { /** Requests a firmware check for all hosts managed by this node repository. */ public void request() { database.writeFirmwareCheck(Optional.of(clock.instant())); - createCache(); + checkAfter.refresh(); } /** Clears any outstanding firmware checks for this node repository. */ public void cancel() { database.writeFirmwareCheck(Optional.empty()); - createCache(); - } - - private void createCache() { - checkAfter = Suppliers.memoizeWithExpiration(database::readFirmwareCheck, cacheExpiry.toMillis(), TimeUnit.MILLISECONDS); + checkAfter.refresh(); } } diff --git a/vespajlib/src/main/java/com/yahoo/lang/CachedSupplier.java b/vespajlib/src/main/java/com/yahoo/lang/CachedSupplier.java new file mode 100644 index 00000000000..07ce1250855 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/lang/CachedSupplier.java @@ -0,0 +1,52 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.lang; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.function.Supplier; + +/** + * Supplier that caches the value for a given duration with ability to invalidate at demand. + * Is thread safe. + * + * @author freva + */ +public class CachedSupplier<T> implements Supplier<T> { + + private final Supplier<T> delegate; + private final Duration period; + private final Clock clock; + + private Instant nextRefresh; + private volatile T value; + + public CachedSupplier(Supplier<T> delegate, Duration period) { + this(delegate, period, Clock.systemUTC()); + } + + CachedSupplier(Supplier<T> delegate, Duration period, Clock clock) { + this.delegate = delegate; + this.period = period; + this.clock = clock; + this.nextRefresh = Instant.MIN; + } + + @Override + public T get() { + synchronized (this) { + if (clock.instant().isAfter(nextRefresh)) + refresh(); + } + + return value; + } + + public void refresh() { + synchronized (this) { + this.value = delegate.get(); + this.nextRefresh = clock.instant().plus(period); + } + } + +} diff --git a/vespajlib/src/test/java/com/yahoo/lang/CachedSupplierTest.java b/vespajlib/src/test/java/com/yahoo/lang/CachedSupplierTest.java new file mode 100644 index 00000000000..b0dc1262ebc --- /dev/null +++ b/vespajlib/src/test/java/com/yahoo/lang/CachedSupplierTest.java @@ -0,0 +1,39 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.lang; + +import com.yahoo.test.ManualClock; +import org.junit.Test; + +import java.time.Duration; + +import static org.junit.Assert.assertEquals; + +/** + * @author freva + */ +public class CachedSupplierTest { + + @Test + public void test() { + ManualClock clock = new ManualClock(); + MutableInteger integer = new MutableInteger(0); + CachedSupplier<Integer> supplier = new CachedSupplier<>(() -> integer.add(1), Duration.ofMinutes(1), clock); + + assertEquals(1, supplier.get().intValue()); + assertEquals(1, supplier.get().intValue()); + + clock.advance(Duration.ofSeconds(30)); + assertEquals(1, supplier.get().intValue()); + + clock.advance(Duration.ofSeconds(31)); + assertEquals(2, supplier.get().intValue()); + assertEquals(2, supplier.get().intValue()); + + supplier.refresh(); + assertEquals(3, supplier.get().intValue()); + assertEquals(3, supplier.get().intValue()); + + clock.advance(Duration.ofSeconds(61)); + assertEquals(4, supplier.get().intValue()); + } +} |