diff --git a/src/main/java/com/github/fge/msgsimple/provider/LoadingMessageSourceProvider.java b/src/main/java/com/github/fge/msgsimple/provider/LoadingMessageSourceProvider.java index 7eff6df..e997640 100644 --- a/src/main/java/com/github/fge/msgsimple/provider/LoadingMessageSourceProvider.java +++ b/src/main/java/com/github/fge/msgsimple/provider/LoadingMessageSourceProvider.java @@ -29,16 +29,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -89,6 +80,7 @@ public final class LoadingMessageSourceProvider public Thread newThread(final Runnable r) { final Thread ret = factory.newThread(r); + ret.setName(LoadingMessageSourceProvider.class.getSimpleName()); ret.setDaemon(true); return ret; } @@ -97,12 +89,17 @@ public Thread newThread(final Runnable r) private static final InternalBundle BUNDLE = InternalBundle.getInstance(); private static final int NTHREADS = 3; + + //We need a central place to put executors so we can shut them all down on request + private static final CopyOnWriteArrayList executors = new CopyOnWriteArrayList(); + + //Need to track if we're shutdown or not. + private static final AtomicBoolean shutdown = new AtomicBoolean(false); /* * Executor service for loading tasks */ - private final ExecutorService service - = Executors.newFixedThreadPool(NTHREADS, THREAD_FACTORY); + private ExecutorService service = Executors.newFixedThreadPool(NTHREADS, THREAD_FACTORY); /* * Loader and default source @@ -146,6 +143,8 @@ private LoadingMessageSourceProvider(final Builder builder) * Mimic an already enabled expiry if, in fact, there is none */ expiryEnabled = new AtomicBoolean(expiryDuration == 0L); + + executors.addIfAbsent(service); } /** @@ -158,9 +157,43 @@ public static Builder newBuilder() return new Builder(); } + /** + * Shuts down all current ExecutorServices by calling shutdownNow() on each of them. + * This is handy for clients that need to explicitly destroy executors/threads before the jvm is terminated. + * + * For example, in an OSGi application, you may need to stop a bundle using this and start it up again later without + * a JVM restart. Without a way to shutdown the executors, it could lead to OOMEs. + */ + public static void shutdown() + { + for(ExecutorService e : executors) + { + e.shutdownNow(); + } + + shutdown.set(true); + } + + /** + * Restarts all ExecutorServices that have been shutdown + */ + public static void restartIfNeeded() + { + if(shutdown.get()) + { + shutdown.set(false); + } + } + @Override public MessageSource getMessageSource(final Locale locale) { + if(shutdown.get()) + { + return defaultSource; + } + + ensureServiceIsReady(); /* * Set up expiry, if necessary */ @@ -236,6 +269,18 @@ public MessageSource getMessageSource(final Locale locale) } } + private void ensureServiceIsReady() + { + if(null == service || service.isShutdown() || service.isTerminated()) + { + executors.remove(service); + + service = Executors.newFixedThreadPool(NTHREADS, THREAD_FACTORY); + + executors.addIfAbsent(service); + } + } + private FutureTask loadingTask(final Locale locale) { return new FutureTask(new Callable()