diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java index 62db1f1de6..b7be092c8f 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheFactory.java @@ -47,11 +47,6 @@ * @author ben.manes@gmail.com (Ben Manes) */ final class CacheFactory { - // Avoid asynchronous executions due to the TCK's poor test practices - // https://github.com/jsr107/jsr107tck/issues/78 - static final boolean USE_DIRECT_EXECUTOR = - System.getProperties().containsKey("org.jsr107.tck.management.agentId"); - final Config rootConfig; final CacheManager cacheManager; @@ -143,8 +138,8 @@ private final class Builder { this.caffeine = Caffeine.newBuilder(); this.statistics = new JCacheStatisticsMXBean(); this.ticker = config.getTickerFactory().create(); + this.executor = config.getExecutorFactory().create(); this.expiryPolicy = config.getExpiryPolicyFactory().create(); - this.executor = USE_DIRECT_EXECUTOR ? Runnable::run : config.getExecutorFactory().create(); this.dispatcher = new EventDispatcher<>(executor); caffeine.executor(executor); diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java index 5f0cee1508..7fc2280d9d 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/CacheProxy.java @@ -19,6 +19,7 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; +import java.io.Closeable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -57,6 +58,7 @@ import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration; import com.github.benmanes.caffeine.jcache.copy.Copier; import com.github.benmanes.caffeine.jcache.event.EventDispatcher; +import com.github.benmanes.caffeine.jcache.event.Registration; import com.github.benmanes.caffeine.jcache.integration.DisabledCacheWriter; import com.github.benmanes.caffeine.jcache.management.JCacheMXBean; import com.github.benmanes.caffeine.jcache.management.JCacheStatisticsMXBean; @@ -864,6 +866,11 @@ public CacheManager getCacheManager() { return cacheManager; } + @Override + public boolean isClosed() { + return closed; + } + @Override public void close() { if (isClosed()) { @@ -875,14 +882,43 @@ public void close() { enableStatistics(false); cacheManager.destroyCache(name); closed = true; + + Throwable thrown = null; + thrown = tryClose(expiry, thrown); + thrown = tryClose(writer, thrown); + thrown = tryClose(cacheLoader.orElse(null), thrown); + for (Registration registration : dispatcher.registrations()) { + thrown = tryClose(registration.getCacheEntryListener(), thrown); + } + if (thrown != null) { + logger.log(Level.WARNING, "Failure when closing cache resources", thrown); + } } } cache.invalidateAll(); } - @Override - public boolean isClosed() { - return closed; + /** + * Attempts to close the resource. If an error occurs and an outermost exception is set, then adds + * the error to the suppression list. + * + * @param o the resource to close if Closeable + * @param outer the outermost error, or null if unset + * @return the outermost error, or null if unset and successful + */ + private static Throwable tryClose(Object o, @Nullable Throwable outer) { + if (o instanceof Closeable) { + try { + ((Closeable) o).close(); + } catch (Throwable t) { + if (outer == null) { + return t; + } + outer.addSuppressed(t); + return outer; + } + } + return null; } @Override diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java index a37d909958..c0df4afef7 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventDispatcher.java @@ -18,8 +18,10 @@ import static java.util.Objects.requireNonNull; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; @@ -62,6 +64,11 @@ public EventDispatcher(Executor exectuor) { this.exectuor = requireNonNull(exectuor); } + /** Returns the cache entry listener registrations. */ + public Set> registrations() { + return Collections.unmodifiableSet(dispatchQueues.keySet()); + } + /** * Registers a cache entry listener based on the supplied configuration. * diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java index ed7620e8fa..79d06fe680 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/EventTypeAwareListener.java @@ -17,6 +17,8 @@ import static java.util.Objects.requireNonNull; +import java.io.Closeable; +import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,7 +38,7 @@ */ final class EventTypeAwareListener implements CacheEntryCreatedListener, CacheEntryUpdatedListener, CacheEntryRemovedListener, - CacheEntryExpiredListener { + CacheEntryExpiredListener, Closeable { static final Logger logger = Logger.getLogger(EventTypeAwareListener.class.getName()); final CacheEntryListener listener; @@ -63,6 +65,9 @@ public boolean isCompatible(@Nonnull EventType eventType) { /** Processes the event and logs if an exception is thrown. */ public void dispatch(@Nonnull JCacheEntryEvent event) { try { + if (event.getSource().isClosed()) { + return; + } switch (event.getEventType()) { case CREATED: onCreated(event); @@ -116,4 +121,11 @@ public void onExpired(Iterable> events ((CacheEntryExpiredListener) listener).onExpired(events); } } + + @Override + public void close() throws IOException { + if (listener instanceof Closeable) { + ((Closeable) listener).close(); + } + } } diff --git a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/Registration.java b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/Registration.java index 00c47822e4..18a727d24c 100644 --- a/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/Registration.java +++ b/jcache/src/main/java/com/github/benmanes/caffeine/jcache/event/Registration.java @@ -26,7 +26,7 @@ * * @author ben.manes@gmail.com (Ben Manes) */ -final class Registration { +public final class Registration { private final CacheEntryListenerConfiguration configuration; private final EventTypeAwareListener listener; private final CacheEntryEventFilter filter;