fix where executor service doesn't close when context does
diff --git a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
index 5cc0f45..0413205 100644
--- a/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
+++ b/core/src/main/java/org/jclouds/concurrent/config/ExecutorServiceModule.java
@@ -18,14 +18,24 @@
*/
package org.jclouds.concurrent.config;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;
import org.jclouds.Constants;
+import org.jclouds.lifecycle.Closer;
+import org.jclouds.logging.Logger;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.NamingThreadFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@@ -39,14 +49,35 @@
*/
@ConfiguresExecutorService
public class ExecutorServiceModule extends AbstractModule {
- private final ExecutorService userThreads;
- private final ExecutorService ioThreads;
+
+ @VisibleForTesting
+ static final class ShutdownExecutorOnClose implements Closeable {
+ @Resource
+ protected Logger logger = Logger.NULL;
+
+ private final ExecutorService service;
+
+ private ShutdownExecutorOnClose(ExecutorService service) {
+ this.service = service;
+ }
+
+ @Override
+ public void close() throws IOException {
+ List<Runnable> runnables = service.shutdownNow();
+ if (runnables.size() > 0)
+ logger.warn("when shutting down executor %s, runnables outstanding: %s", service,
+ runnables);
+ }
+ }
+
+ private final ExecutorService userExecutorFromConstructor;
+ private final ExecutorService ioExecutorFromConstructor;
public ExecutorServiceModule(
@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioThreads) {
- this.userThreads = userThreads;
- this.ioThreads = ioThreads;
+ this.userExecutorFromConstructor = userThreads;
+ this.ioExecutorFromConstructor = ioThreads;
}
public ExecutorServiceModule() {
@@ -60,22 +91,43 @@
@Provides
@Singleton
@Named(Constants.PROPERTY_USER_THREADS)
- ExecutorService provideExecutorService(@Named(Constants.PROPERTY_USER_THREADS) int userThreads) {
- return this.userThreads != null ? this.userThreads : userThreads == 0 ? Executors
- .newCachedThreadPool(new NamingThreadFactory("user thread %d"))
- : newNamedThreadPool("user thread %d", userThreads);
- }
-
- public static ExecutorService newNamedThreadPool(String name, int maxCount) {
- return Executors.newFixedThreadPool(maxCount, new NamingThreadFactory(name));
+ ExecutorService provideExecutorService(@Named(Constants.PROPERTY_USER_THREADS) int count,
+ Closer closer) {
+ if (userExecutorFromConstructor != null)
+ return shutdownOnClose(userExecutorFromConstructor, closer);
+ return shutdownOnClose(newThreadPoolNamed("user thread %d", count), closer);
}
@Provides
@Singleton
@Named(Constants.PROPERTY_IO_WORKER_THREADS)
- ExecutorService provideIOExecutor(@Named(Constants.PROPERTY_IO_WORKER_THREADS) int ioThreads) {
- return this.ioThreads != null ? this.ioThreads : ioThreads == 0 ? Executors
- .newCachedThreadPool(new NamingThreadFactory("i/o thread %d")) : newNamedThreadPool(
- "i/o thread %d", ioThreads);
+ ExecutorService provideIOExecutor(@Named(Constants.PROPERTY_IO_WORKER_THREADS) int count,
+ Closer closer) {
+ if (ioExecutorFromConstructor != null)
+ return shutdownOnClose(ioExecutorFromConstructor, closer);
+ return shutdownOnClose(newThreadPoolNamed("i/o thread %d", count), closer);
}
+
+ @VisibleForTesting
+ static ExecutorService shutdownOnClose(final ExecutorService service, Closer closer) {
+ closer.addToClose(new ShutdownExecutorOnClose(service));
+ return service;
+ }
+
+ @VisibleForTesting
+ static ExecutorService newCachedThreadPoolNamed(String name) {
+ return Executors.newCachedThreadPool(new NamingThreadFactory(name));
+ }
+
+ @VisibleForTesting
+ static ExecutorService newThreadPoolNamed(String name, int count) {
+ return count == 0 ? newCachedThreadPoolNamed(name) : newFixedThreadPoolNamed(name, count);
+ }
+
+ @VisibleForTesting
+ static ExecutorService newFixedThreadPoolNamed(String name, int maxCount) {
+ return new ThreadPoolExecutor(0, maxCount, 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<Runnable>(), new NamingThreadFactory(name));
+ }
+
}
\ No newline at end of file
diff --git a/core/src/test/java/org/jclouds/concurrent/config/ExecutorServiceModuleTest.java b/core/src/test/java/org/jclouds/concurrent/config/ExecutorServiceModuleTest.java
new file mode 100644
index 0000000..477d999
--- /dev/null
+++ b/core/src/test/java/org/jclouds/concurrent/config/ExecutorServiceModuleTest.java
@@ -0,0 +1,55 @@
+/**
+ *
+ * Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
+ *
+ * ====================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+package org.jclouds.concurrent.config;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.jclouds.lifecycle.Closer;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test
+public class ExecutorServiceModuleTest {
+
+ private Closer closer;
+
+ @BeforeTest
+ public void setUp() throws Exception {
+ Injector i = Guice.createInjector();
+ closer = i.getInstance(Closer.class);
+ }
+
+ @Test
+ public void testShutdownOnClose() throws IOException {
+ ExecutorService executor = Executors.newCachedThreadPool();
+ assert !executor.isShutdown();
+ ExecutorServiceModule.shutdownOnClose(executor, closer);
+ closer.close();
+ assert executor.isShutdown();
+ }
+}
diff --git a/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java b/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java
index 780200b..7f74bfe 100644
--- a/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java
+++ b/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java
@@ -22,8 +22,6 @@
import java.util.concurrent.ExecutorService;
import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.Constants;
@@ -39,7 +37,6 @@
import com.google.inject.Provides;
/**
- * // TODO: Adrian: Document this!
*
* @author Adrian Cole
*/
@@ -98,45 +95,17 @@
assert executor.isShutdown();
}
- static class PreDestroyable {
- boolean isClosed = false;
- private final ExecutorService userThreads;
- private final ExecutorService ioThreads;
-
- @Inject
- PreDestroyable(@Named(Constants.PROPERTY_USER_THREADS) ExecutorService userThreads,
- @Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioThreads) {
- this.userThreads = userThreads;
- this.ioThreads = ioThreads;
- }
-
- @PreDestroy
- public void close() {
- assert !userThreads.isShutdown();
- assert !ioThreads.isShutdown();
-
- isClosed = true;
- }
- }
-
@Test
void testCloserPreDestroyOrder() throws IOException {
- Injector i = createInjector().createChildInjector(new AbstractModule() {
- protected void configure() {
- bind(PreDestroyable.class);
- }
- });
+ Injector i = createInjector();
ExecutorService userThreads = i.getInstance(Key.get(ExecutorService.class, Jsr330
.named(Constants.PROPERTY_USER_THREADS)));
assert !userThreads.isShutdown();
ExecutorService ioThreads = i.getInstance(Key.get(ExecutorService.class, Jsr330
.named(Constants.PROPERTY_IO_WORKER_THREADS)));
assert !ioThreads.isShutdown();
- PreDestroyable preDestroyable = i.getInstance(PreDestroyable.class);
- assert !preDestroyable.isClosed;
Closer closer = i.getInstance(Closer.class);
closer.close();
- assert preDestroyable.isClosed;
assert userThreads.isShutdown();
assert ioThreads.isShutdown();
}