Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Cache#close #218

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
13 changes: 12 additions & 1 deletion api/src/main/java/io/github/xanthic/cache/api/Cache.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* @param <K> The type of keys that form the cache
* @param <V> The type of values contained in the cache
*/
public interface Cache<K, V> {
public interface Cache<K, V> extends AutoCloseable {

/**
* Obtains the value associated with the specified key.
Expand Down Expand Up @@ -194,4 +194,15 @@ default void forEach(@NotNull BiConsumer<? super K, ? super V> action) {
throw new UnsupportedOperationException();
}

/**
* {@inheritDoc}
* <p>
* 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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,6 +104,8 @@ public void computeMergeRemoveTest() {

Assertions.assertNull(cache.compute("a", (k, v) -> null));
Assertions.assertNull(cache.get("a"));

cache.close();
}

@Test
Expand Down Expand Up @@ -133,6 +137,8 @@ public void replaceTest() {

Assertions.assertFalse(cache.replace("9", -9));
Assertions.assertNull(cache.get("9"));

cache.close();
}

@Test
Expand All @@ -157,6 +163,7 @@ public void iterateTest() {
expected.put("2", 2);

Assertions.assertEquals(expected, observed);
cache.close();
}

@Test
Expand All @@ -166,6 +173,7 @@ public void zeroMaxSizeTest() {
cache.put("1", 1);
Assertions.assertNull(cache.get("1"));
Assertions.assertEquals(0, cache.size());
cache.close();
}

@Test
Expand All @@ -175,6 +183,7 @@ public void zeroExpiryTimeTest() {
cache.put("1", 1);
Assertions.assertNull(cache.get("1"));
Assertions.assertEquals(0, cache.size());
cache.close();
}

@Test
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 <K, V> Cache<K, V> build(Consumer<CacheApiSpec<K, V>> additionalSpec) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,16 @@ public <K, V> Cache<K, V> build(ICacheSpec<K, V> 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 <K, V> LruCache<K, V> buildSimple(Long maxSize, RemovalListener<K, V> listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class ExpiringLruDelegate<K, V> extends AbstractCache<K, V> {
@EqualsAndHashCode.Exclude
ScheduledExecutorService exec;
@EqualsAndHashCode.Exclude
boolean shouldCloseExecutor;
@EqualsAndHashCode.Exclude
Map<Map.Entry<K, V>, Future<?>> tracker = new ConcurrentHashMap<>();

LruCache<K, V> cache = new LruCache<K, V>(getMaxSize() != null ? getMaxSize().intValue() : Integer.MAX_VALUE) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V> computeFunc) {
return cache.computeIfAbsent(key, computeFunc);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void clear() {
cache.invalidateAll();
}

@Override
public void close() {
cache.invalidateAll();
cache.cleanUp();
}

@Override
public long size() {
cache.cleanUp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void clear() {
cache.invalidateAll();
}

@Override
public void close() {
cache.invalidateAll();
cache.cleanUp();
}

@Override
public long size() {
cache.cleanUp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public void clear() {
cache.invalidateAll();
}

@Override
public void close() {
cache.invalidateAll();
cache.cleanUp();
}

@Override
public long size() {
cache.cleanUp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void clear() {
cache.clear();
}

@Override
public void close() {
cache.clear();
cache.shutdown();
}

@Override
public long size() {
return cache.size();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public void clear() {
cache.clear();
}

@Override
public void close() {
cache.clear();
cache.shutdown();
}

@Override
public long size() {
return cache.size();
Expand Down
Loading