View Javadoc
1   package com.atlassian.plugin.osgi.spring;
2   
3   import org.slf4j.Logger;
4   import org.slf4j.LoggerFactory;
5   import org.springframework.core.task.AsyncTaskExecutor;
6   
7   import java.util.concurrent.Callable;
8   import java.util.concurrent.ExecutorService;
9   import java.util.concurrent.Executors;
10  import java.util.concurrent.Future;
11  import java.util.concurrent.ThreadFactory;
12  import java.util.concurrent.TimeUnit;
13  import java.util.concurrent.atomic.AtomicInteger;
14  
15  /**
16   * Executes spring tasks using a cached thread pool that expands as necessary.  Overrides the default Spring executor
17   * that spawns a new thread for every application context creation.
18   *
19   * @since 2.5.0
20   */
21  public class ThreadPoolAsyncTaskExecutor implements AsyncTaskExecutor {
22      private static final Logger log = LoggerFactory.getLogger(ThreadPoolAsyncTaskExecutor.class);
23  
24      private final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory());
25  
26      /**
27       * Executes the runnable
28       *
29       * @param task         The runnable task
30       * @param startTimeout The start timeout (ignored)
31       */
32      @Override
33      public void execute(Runnable task, long startTimeout) {
34          // yes, we ignore the start timeout
35          executor.execute(task);
36      }
37  
38      @Override
39      public Future<?> submit(Runnable task) {
40          return executor.submit(task);
41      }
42  
43      @Override
44      public <T> Future<T> submit(Callable<T> task) {
45          return executor.submit(task);
46      }
47  
48      /**
49       * Executes the runnable
50       *
51       * @param task The runnable task
52       */
53      @Override
54      public void execute(Runnable task) {
55          this.execute(task, -1);
56      }
57  
58      /**
59       * Shuts down the internal {@code ExecutorService} to ensure that all threads are stopped in order to allow the JVM
60       * to terminate cleanly in a timely fashion.
61       */
62      public void shutdown() {
63          log.debug("Attempting to shutdown ExecutorService");
64  
65          executor.shutdown();
66          try {
67              if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
68                  log.debug("ExecutorService has shutdown gracefully");
69              } else {
70                  //The executor did not shutdown within the timeout. We can't wait forever, though, so issue a
71                  //shutdownNow() and give it another 5 seconds
72                  log.warn("ExecutorService did not shutdown within the timeout; forcing shutdown");
73  
74                  executor.shutdownNow();
75                  if (executor.awaitTermination(5, TimeUnit.SECONDS)) {
76                      //The forced shutdown has brought the executor down. Not ideal, but acceptable
77                      log.debug("ExecutorService has been forced to shutdown");
78                  } else {
79                      //We can't delay execution indefinitely waiting, so log a warning. The JVM may not shut down
80                      //if this service does not stop (because it uses non-daemon threads), so this may be helpful
81                      //in debugging should that happen.
82                      log.warn("ExecutorService did not shutdown; it will be abandoned");
83                  }
84              }
85          } catch (InterruptedException e) {
86              log.warn("Interrupted while waiting for the executor service to shutdown; some worker threads may " +
87                      "still be running");
88              Thread.currentThread().interrupt();
89          }
90      }
91  
92      /**
93       * Thread factory that names the threads for the executor
94       */
95      private static class NamedThreadFactory implements ThreadFactory {
96          private final AtomicInteger counter = new AtomicInteger();
97  
98          @Override
99          public Thread newThread(Runnable r) {
100             Thread thread = new Thread(r);
101             thread.setDaemon(false);
102             thread.setName("ThreadPoolAsyncTaskExecutor::Thread " + counter.incrementAndGet());
103             return thread;
104         }
105     }
106 }