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