From 8e281bad42cc30763d9f58ffc31e0ce4b65703f0 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Wed, 27 Dec 2023 22:29:41 -0800 Subject: [PATCH 1/2] feat: make Cache closeable --- .../java/io/github/xanthic/cache/api/Cache.java | 13 ++++++++++++- .../cache/provider/androidx/AndroidLruProvider.java | 12 ++++++++++-- .../provider/androidx/ExpiringLruDelegate.java | 10 ++++++++++ .../cache/provider/cache2k/Cache2kDelegate.java | 6 ++++++ .../cache/provider/caffeine/CaffeineDelegate.java | 6 ++++++ .../cache/provider/caffeine3/Caffeine3Delegate.java | 6 ++++++ .../xanthic/cache/provider/guava/GuavaDelegate.java | 6 ++++++ .../infinispanjdk11/InfinispanDelegate.java | 6 ++++++ .../provider/infinispan/InfinispanDelegate.java | 6 ++++++ 9 files changed, 68 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/io/github/xanthic/cache/api/Cache.java b/api/src/main/java/io/github/xanthic/cache/api/Cache.java index 26f33e94..30495b80 100644 --- a/api/src/main/java/io/github/xanthic/cache/api/Cache.java +++ b/api/src/main/java/io/github/xanthic/cache/api/Cache.java @@ -25,7 +25,7 @@ * @param The type of keys that form the cache * @param The type of values contained in the cache */ -public interface Cache { +public interface Cache extends AutoCloseable { /** * Obtains the value associated with the specified key. @@ -194,4 +194,15 @@ default void forEach(@NotNull BiConsumer action) { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + *

+ * Avoid further usage of the cache once it has been closed; + * some implementations may throw exceptions while others are more tolerant. + */ + @Override + default void close() { + this.clear(); + } + } diff --git a/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/AndroidLruProvider.java b/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/AndroidLruProvider.java index fff87f2a..bb0d6816 100644 --- a/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/AndroidLruProvider.java +++ b/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/AndroidLruProvider.java @@ -31,8 +31,16 @@ public Cache build(ICacheSpec spec) { Duration expiryTime = spec.expiryTime(); if (executor == null) handleUnsupportedExpiry(expiryTime); if (expiryTime == null) return new LruDelegate<>(buildSimple(spec.maxSize(), spec.removalListener())); - ScheduledExecutorService exec = executor != null ? executor : Executors.newSingleThreadScheduledExecutor(); - return new ExpiringLruDelegate<>(spec.maxSize(), spec.removalListener(), expiryTime.toNanos(), getExpiryType(spec.expiryType()), exec); + ScheduledExecutorService exec; + boolean createdExecutor; + if (executor != null) { + exec = executor; + createdExecutor = false; + } else { + exec = Executors.newSingleThreadScheduledExecutor(); + createdExecutor = true; + } + return new ExpiringLruDelegate<>(spec.maxSize(), spec.removalListener(), expiryTime.toNanos(), getExpiryType(spec.expiryType()), exec, createdExecutor); } private static LruCache buildSimple(Long maxSize, RemovalListener listener) { diff --git a/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/ExpiringLruDelegate.java b/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/ExpiringLruDelegate.java index 420ff98c..07eac83c 100644 --- a/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/ExpiringLruDelegate.java +++ b/provider-androidx/src/main/java/io/github/xanthic/cache/provider/androidx/ExpiringLruDelegate.java @@ -36,6 +36,8 @@ class ExpiringLruDelegate extends AbstractCache { @EqualsAndHashCode.Exclude ScheduledExecutorService exec; @EqualsAndHashCode.Exclude + boolean shouldCloseExecutor; + @EqualsAndHashCode.Exclude Map, Future> tracker = new ConcurrentHashMap<>(); LruCache cache = new LruCache(getMaxSize() != null ? getMaxSize().intValue() : Integer.MAX_VALUE) { @@ -97,6 +99,14 @@ public void clear() { } } + @Override + public void close() { + super.close(); + if (shouldCloseExecutor) { + this.exec.shutdownNow(); + } + } + @Override public long size() { return cache.size(); diff --git a/provider-cache2k/src/main/java/io/github/xanthic/cache/provider/cache2k/Cache2kDelegate.java b/provider-cache2k/src/main/java/io/github/xanthic/cache/provider/cache2k/Cache2kDelegate.java index 3786482a..c20a2e53 100644 --- a/provider-cache2k/src/main/java/io/github/xanthic/cache/provider/cache2k/Cache2kDelegate.java +++ b/provider-cache2k/src/main/java/io/github/xanthic/cache/provider/cache2k/Cache2kDelegate.java @@ -40,6 +40,12 @@ public void clear() { cache.clear(); } + @Override + public void close() { + super.close(); + cache.close(); + } + @Override public V computeIfAbsent(@NotNull K key, @NotNull Function computeFunc) { return cache.computeIfAbsent(key, computeFunc); diff --git a/provider-caffeine/src/main/java/io/github/xanthic/cache/provider/caffeine/CaffeineDelegate.java b/provider-caffeine/src/main/java/io/github/xanthic/cache/provider/caffeine/CaffeineDelegate.java index 52fa8375..d2ace41b 100644 --- a/provider-caffeine/src/main/java/io/github/xanthic/cache/provider/caffeine/CaffeineDelegate.java +++ b/provider-caffeine/src/main/java/io/github/xanthic/cache/provider/caffeine/CaffeineDelegate.java @@ -34,6 +34,12 @@ public void clear() { cache.invalidateAll(); } + @Override + public void close() { + cache.invalidateAll(); + cache.cleanUp(); + } + @Override public long size() { cache.cleanUp(); diff --git a/provider-caffeine3/src/main/java/io/github/xanthic/cache/provider/caffeine3/Caffeine3Delegate.java b/provider-caffeine3/src/main/java/io/github/xanthic/cache/provider/caffeine3/Caffeine3Delegate.java index 970fa8d1..34ac7e93 100644 --- a/provider-caffeine3/src/main/java/io/github/xanthic/cache/provider/caffeine3/Caffeine3Delegate.java +++ b/provider-caffeine3/src/main/java/io/github/xanthic/cache/provider/caffeine3/Caffeine3Delegate.java @@ -34,6 +34,12 @@ public void clear() { cache.invalidateAll(); } + @Override + public void close() { + cache.invalidateAll(); + cache.cleanUp(); + } + @Override public long size() { cache.cleanUp(); diff --git a/provider-guava/src/main/java/io/github/xanthic/cache/provider/guava/GuavaDelegate.java b/provider-guava/src/main/java/io/github/xanthic/cache/provider/guava/GuavaDelegate.java index 45aa214a..b259376e 100644 --- a/provider-guava/src/main/java/io/github/xanthic/cache/provider/guava/GuavaDelegate.java +++ b/provider-guava/src/main/java/io/github/xanthic/cache/provider/guava/GuavaDelegate.java @@ -36,6 +36,12 @@ public void clear() { cache.invalidateAll(); } + @Override + public void close() { + cache.invalidateAll(); + cache.cleanUp(); + } + @Override public long size() { cache.cleanUp(); diff --git a/provider-infinispan-java11/src/main/java/io/github/xanthic/cache/provider/infinispanjdk11/InfinispanDelegate.java b/provider-infinispan-java11/src/main/java/io/github/xanthic/cache/provider/infinispanjdk11/InfinispanDelegate.java index 000dad6b..bb31566a 100644 --- a/provider-infinispan-java11/src/main/java/io/github/xanthic/cache/provider/infinispanjdk11/InfinispanDelegate.java +++ b/provider-infinispan-java11/src/main/java/io/github/xanthic/cache/provider/infinispanjdk11/InfinispanDelegate.java @@ -34,6 +34,12 @@ public void clear() { cache.clear(); } + @Override + public void close() { + cache.clear(); + cache.shutdown(); + } + @Override public long size() { return cache.size(); diff --git a/provider-infinispan/src/main/java/io/github/xanthic/cache/provider/infinispan/InfinispanDelegate.java b/provider-infinispan/src/main/java/io/github/xanthic/cache/provider/infinispan/InfinispanDelegate.java index baf56d8f..5d0098ea 100644 --- a/provider-infinispan/src/main/java/io/github/xanthic/cache/provider/infinispan/InfinispanDelegate.java +++ b/provider-infinispan/src/main/java/io/github/xanthic/cache/provider/infinispan/InfinispanDelegate.java @@ -34,6 +34,12 @@ public void clear() { cache.clear(); } + @Override + public void close() { + cache.clear(); + cache.shutdown(); + } + @Override public long size() { return cache.size(); From 790b78f132a7808fa90d82f3444ab3e801ff6448 Mon Sep 17 00:00:00 2001 From: iProdigy Date: Wed, 27 Dec 2023 22:36:38 -0800 Subject: [PATCH 2/2] chore: update tests --- .../cache/core/provider/ProviderTestBase.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/core/src/testFixtures/java/io/github/xanthic/cache/core/provider/ProviderTestBase.java b/core/src/testFixtures/java/io/github/xanthic/cache/core/provider/ProviderTestBase.java index b86a23fa..e9c5e753 100644 --- a/core/src/testFixtures/java/io/github/xanthic/cache/core/provider/ProviderTestBase.java +++ b/core/src/testFixtures/java/io/github/xanthic/cache/core/provider/ProviderTestBase.java @@ -63,6 +63,8 @@ public void putGetClearTest() { for (int i = 0; i < 4; i++) { Assertions.assertEquals(i, m.get(String.valueOf(i))); } + + cache.close(); } @Test @@ -102,6 +104,8 @@ public void computeMergeRemoveTest() { Assertions.assertNull(cache.compute("a", (k, v) -> null)); Assertions.assertNull(cache.get("a")); + + cache.close(); } @Test @@ -133,6 +137,8 @@ public void replaceTest() { Assertions.assertFalse(cache.replace("9", -9)); Assertions.assertNull(cache.get("9")); + + cache.close(); } @Test @@ -157,6 +163,7 @@ public void iterateTest() { expected.put("2", 2); Assertions.assertEquals(expected, observed); + cache.close(); } @Test @@ -166,6 +173,7 @@ public void zeroMaxSizeTest() { cache.put("1", 1); Assertions.assertNull(cache.get("1")); Assertions.assertEquals(0, cache.size()); + cache.close(); } @Test @@ -175,6 +183,7 @@ public void zeroExpiryTimeTest() { cache.put("1", 1); Assertions.assertNull(cache.get("1")); Assertions.assertEquals(0, cache.size()); + cache.close(); } @Test @@ -200,6 +209,8 @@ public void sizeEvictionTest() { for (int i = 1; i < 5; i++) { Assertions.assertEquals(i, cache.get(String.valueOf(i))); } + + cache.close(); } @Test @@ -227,6 +238,7 @@ public void sizeEvictionListenerTest() { // Ensure listener is called the appropriate number of times await().atMost(30, TimeUnit.SECONDS).until(() -> removals.get() == expectedEvictions); + cache.close(); } @Test @@ -250,6 +262,8 @@ public void timeEvictionTest() { await().atLeast(expiry * 3 / 4, TimeUnit.MILLISECONDS) .atMost(90, TimeUnit.SECONDS) .until(() -> cache.size() == 0); + + cache.close(); } @Test @@ -280,6 +294,8 @@ public void timeEvictionListenerTest() { await().atLeast(expiry * 3 / 4, TimeUnit.MILLISECONDS) .atMost(90, TimeUnit.SECONDS) .until(() -> evictions.get() == n); + + cache.close(); } @Test @@ -310,6 +326,7 @@ public void replacedListenerTest() { // Ensure listener was called the appropriate number of times await().atMost(30, TimeUnit.SECONDS).until(() -> replacements.get() == n); + cache.close(); } @Test @@ -340,6 +357,7 @@ public void manualRemovalListenerTest() { // Ensure listener was called the appropriate number of times await().atMost(90, TimeUnit.SECONDS).until(() -> removals.get() == n); + cache.close(); } @Test @@ -351,12 +369,8 @@ public void registeredAsDefaultTest() { @Test @DisplayName("Test whether cache can be built with contention flag and custom executor") public void buildTest() { - Assertions.assertNotNull( - build(spec -> spec.highContention(true).maxSize(null)) - ); - Assertions.assertNotNull( - build(spec -> spec.highContention(true).executor(Executors.newSingleThreadScheduledExecutor())) - ); + build(spec -> spec.highContention(true).maxSize(null)).close(); + build(spec -> spec.highContention(true).executor(Executors.newSingleThreadScheduledExecutor())).close(); } protected Cache build(Consumer> additionalSpec) {