2323import java .util .concurrent .ThreadFactory ;
2424import java .util .concurrent .ThreadPoolExecutor ;
2525import java .util .concurrent .TimeUnit ;
26+ import java .util .concurrent .locks .Condition ;
27+ import java .util .concurrent .locks .ReentrantLock ;
2628
2729import org .apache .commons .logging .Log ;
2830import org .apache .commons .logging .LogFactory ;
2931
3032import org .springframework .beans .factory .BeanNameAware ;
3133import org .springframework .beans .factory .DisposableBean ;
3234import org .springframework .beans .factory .InitializingBean ;
35+ import org .springframework .context .ApplicationContext ;
36+ import org .springframework .context .ApplicationContextAware ;
37+ import org .springframework .context .ApplicationListener ;
38+ import org .springframework .context .SmartLifecycle ;
39+ import org .springframework .context .event .ContextClosedEvent ;
3340import org .springframework .lang .Nullable ;
3441
3542/**
5057 */
5158@ SuppressWarnings ("serial" )
5259public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
53- implements BeanNameAware , InitializingBean , DisposableBean {
60+ implements BeanNameAware , ApplicationContextAware , InitializingBean , DisposableBean ,
61+ SmartLifecycle , ApplicationListener <ContextClosedEvent > {
5462
5563 protected final Log logger = LogFactory .getLog (getClass ());
5664
@@ -60,16 +68,34 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
6068
6169 private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor .AbortPolicy ();
6270
71+ private boolean acceptTasksAfterContextClose = false ;
72+
6373 private boolean waitForTasksToCompleteOnShutdown = false ;
6474
6575 private long awaitTerminationMillis = 0 ;
6676
77+ private int phase = DEFAULT_PHASE ;
78+
6779 @ Nullable
6880 private String beanName ;
6981
82+ @ Nullable
83+ private ApplicationContext applicationContext ;
84+
7085 @ Nullable
7186 private ExecutorService executor ;
7287
88+ private final ReentrantLock pauseLock = new ReentrantLock ();
89+
90+ private final Condition unpaused = this .pauseLock .newCondition ();
91+
92+ private volatile boolean paused ;
93+
94+ private int executingTaskCount = 0 ;
95+
96+ @ Nullable
97+ private Runnable stopCallback ;
98+
7399
74100 /**
75101 * Set the ThreadFactory to use for the ExecutorService's thread pool.
@@ -105,12 +131,32 @@ public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejec
105131 (rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor .AbortPolicy ());
106132 }
107133
134+ /**
135+ * Set whether to accept further tasks after the application context close phase
136+ * has begun.
137+ * <p>Default is {@code false} as of 6.1, triggering an early soft shutdown of
138+ * the executor and therefore rejecting any further task submissions. Switch this
139+ * to {@code true} in order to let other components submit tasks even during their
140+ * own destruction callbacks, at the expense of a longer shutdown phase.
141+ * This will usually go along with
142+ * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}.
143+ * <p>This flag will only have effect when the executor is running in a Spring
144+ * application context and able to receive the {@link ContextClosedEvent}.
145+ * @since 6.1
146+ * @see org.springframework.context.ConfigurableApplicationContext#close()
147+ * @see DisposableBean#destroy()
148+ * @see #shutdown()
149+ */
150+ public void setAcceptTasksAfterContextClose (boolean acceptTasksAfterContextClose ) {
151+ this .acceptTasksAfterContextClose = acceptTasksAfterContextClose ;
152+ }
153+
108154 /**
109155 * Set whether to wait for scheduled tasks to complete on shutdown,
110156 * not interrupting running tasks and executing all tasks in the queue.
111- * <p>Default is " false" , shutting down immediately through interrupting
112- * ongoing tasks and clearing the queue. Switch this flag to " true" if you
113- * prefer fully completed tasks at the expense of a longer shutdown phase.
157+ * <p>Default is {@code false} , shutting down immediately through interrupting
158+ * ongoing tasks and clearing the queue. Switch this flag to {@code true} if
159+ * you prefer fully completed tasks at the expense of a longer shutdown phase.
114160 * <p>Note that Spring's container shutdown continues while ongoing tasks
115161 * are being completed. If you want this executor to block and wait for the
116162 * termination of tasks before the rest of the container continues to shut
@@ -161,11 +207,36 @@ public void setAwaitTerminationMillis(long awaitTerminationMillis) {
161207 this .awaitTerminationMillis = awaitTerminationMillis ;
162208 }
163209
210+ /**
211+ * Specify the lifecycle phase for pausing and resuming this executor.
212+ * The default is {@link #DEFAULT_PHASE}.
213+ * @since 6.1
214+ * @see SmartLifecycle#getPhase()
215+ */
216+ public void setPhase (int phase ) {
217+ this .phase = phase ;
218+ }
219+
220+ /**
221+ * Return the lifecycle phase for pausing and resuming this executor.
222+ * @since 6.1
223+ * @see #setPhase
224+ */
225+ @ Override
226+ public int getPhase () {
227+ return this .phase ;
228+ }
229+
164230 @ Override
165231 public void setBeanName (String name ) {
166232 this .beanName = name ;
167233 }
168234
235+ @ Override
236+ public void setApplicationContext (ApplicationContext applicationContext ) {
237+ this .applicationContext = applicationContext ;
238+ }
239+
169240
170241 /**
171242 * Calls {@code initialize()} after the container applied all property values.
@@ -211,9 +282,33 @@ public void destroy() {
211282 }
212283
213284 /**
214- * Perform a shutdown on the underlying ExecutorService.
285+ * Initiate a shutdown on the underlying ExecutorService,
286+ * rejecting further task submissions.
287+ * <p>The executor will not accept further tasks and will prevent further
288+ * scheduling of periodic tasks, letting existing tasks complete still.
289+ * This step is non-blocking and can be applied as an early shutdown signal
290+ * before following up with a full {@link #shutdown()} call later on.
291+ * @since 6.1
292+ * @see #shutdown()
293+ * @see java.util.concurrent.ExecutorService#shutdown()
294+ */
295+ public void initiateShutdown () {
296+ if (this .executor != null ) {
297+ this .executor .shutdown ();
298+ }
299+ }
300+
301+ /**
302+ * Perform a full shutdown on the underlying ExecutorService,
303+ * according to the corresponding configuration settings.
304+ * <p>This step potentially blocks for the configured termination period,
305+ * waiting for remaining tasks to complete. For an early shutdown signal
306+ * to not accept further tasks, call {@link #initiateShutdown()} first.
307+ * @see #setWaitForTasksToCompleteOnShutdown
308+ * @see #setAwaitTerminationMillis
215309 * @see java.util.concurrent.ExecutorService#shutdown()
216310 * @see java.util.concurrent.ExecutorService#shutdownNow()
311+ * @see java.util.concurrent.ExecutorService#awaitTermination
217312 */
218313 public void shutdown () {
219314 if (logger .isDebugEnabled ()) {
@@ -270,4 +365,136 @@ private void awaitTerminationIfNecessary(ExecutorService executor) {
270365 }
271366 }
272367
368+
369+ /**
370+ * Resume this executor if paused before (otherwise a no-op).
371+ * @since 6.1
372+ */
373+ @ Override
374+ public void start () {
375+ this .pauseLock .lock ();
376+ try {
377+ this .paused = false ;
378+ this .unpaused .signalAll ();
379+ }
380+ finally {
381+ this .pauseLock .unlock ();
382+ }
383+ }
384+
385+ /**
386+ * Pause this executor, not waiting for tasks to complete.
387+ * @since 6.1
388+ */
389+ @ Override
390+ public void stop () {
391+ this .pauseLock .lock ();
392+ try {
393+ this .paused = true ;
394+ this .stopCallback = null ;
395+ }
396+ finally {
397+ this .pauseLock .unlock ();
398+ }
399+ }
400+
401+ /**
402+ * Pause this executor, triggering the given callback
403+ * once all currently executing tasks have completed.
404+ * @since 6.1
405+ */
406+ @ Override
407+ public void stop (Runnable callback ) {
408+ this .pauseLock .lock ();
409+ try {
410+ this .paused = true ;
411+ if (this .executingTaskCount == 0 ) {
412+ this .stopCallback = null ;
413+ callback .run ();
414+ }
415+ else {
416+ this .stopCallback = callback ;
417+ }
418+ }
419+ finally {
420+ this .pauseLock .unlock ();
421+ }
422+ }
423+
424+ /**
425+ * Check whether this executor is not paused and has not been shut down either.
426+ * @since 6.1
427+ * @see #start()
428+ * @see #stop()
429+ */
430+ @ Override
431+ public boolean isRunning () {
432+ return (this .executor != null && !this .executor .isShutdown () & !this .paused );
433+ }
434+
435+ /**
436+ * A before-execute callback for framework subclasses to delegate to
437+ * (for start/stop handling), and possibly also for custom subclasses
438+ * to extend (making sure to call this implementation as well).
439+ * @param thread the thread to run the task
440+ * @param task the task to be executed
441+ * @since 6.1
442+ * @see ThreadPoolExecutor#beforeExecute(Thread, Runnable)
443+ */
444+ protected void beforeExecute (Thread thread , Runnable task ) {
445+ this .pauseLock .lock ();
446+ try {
447+ while (this .paused && this .executor != null && !this .executor .isShutdown ()) {
448+ this .unpaused .await ();
449+ }
450+ }
451+ catch (InterruptedException ex ) {
452+ thread .interrupt ();
453+ }
454+ finally {
455+ this .executingTaskCount ++;
456+ this .pauseLock .unlock ();
457+ }
458+ }
459+
460+ /**
461+ * An after-execute callback for framework subclasses to delegate to
462+ * (for start/stop handling), and possibly also for custom subclasses
463+ * to extend (making sure to call this implementation as well).
464+ * @param task the task that has been executed
465+ * @param ex the exception thrown during execution, if any
466+ * @since 6.1
467+ * @see ThreadPoolExecutor#afterExecute(Runnable, Throwable)
468+ */
469+ protected void afterExecute (Runnable task , @ Nullable Throwable ex ) {
470+ this .pauseLock .lock ();
471+ try {
472+ this .executingTaskCount --;
473+ if (this .executingTaskCount == 0 ) {
474+ Runnable callback = this .stopCallback ;
475+ if (callback != null ) {
476+ callback .run ();
477+ this .stopCallback = null ;
478+ }
479+ }
480+ }
481+ finally {
482+ this .pauseLock .unlock ();
483+ }
484+ }
485+
486+ /**
487+ * {@link ContextClosedEvent} handler for initiating an early shutdown.
488+ * @since 6.1
489+ * @see #initiateShutdown()
490+ */
491+ @ Override
492+ public void onApplicationEvent (ContextClosedEvent event ) {
493+ if (event .getApplicationContext () == this .applicationContext && !this .acceptTasksAfterContextClose ) {
494+ // Early shutdown signal: accept no further tasks, let existing tasks complete
495+ // before hitting the actual destruction step in the shutdown() method above.
496+ initiateShutdown ();
497+ }
498+ }
499+
273500}
0 commit comments