23
23
import java .util .concurrent .ThreadFactory ;
24
24
import java .util .concurrent .ThreadPoolExecutor ;
25
25
import java .util .concurrent .TimeUnit ;
26
+ import java .util .concurrent .locks .Condition ;
27
+ import java .util .concurrent .locks .ReentrantLock ;
26
28
27
29
import org .apache .commons .logging .Log ;
28
30
import org .apache .commons .logging .LogFactory ;
29
31
30
32
import org .springframework .beans .factory .BeanNameAware ;
31
33
import org .springframework .beans .factory .DisposableBean ;
32
34
import 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 ;
33
40
import org .springframework .lang .Nullable ;
34
41
35
42
/**
50
57
*/
51
58
@ SuppressWarnings ("serial" )
52
59
public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
53
- implements BeanNameAware , InitializingBean , DisposableBean {
60
+ implements BeanNameAware , ApplicationContextAware , InitializingBean , DisposableBean ,
61
+ SmartLifecycle , ApplicationListener <ContextClosedEvent > {
54
62
55
63
protected final Log logger = LogFactory .getLog (getClass ());
56
64
@@ -60,16 +68,34 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
60
68
61
69
private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor .AbortPolicy ();
62
70
71
+ private boolean acceptTasksAfterContextClose = false ;
72
+
63
73
private boolean waitForTasksToCompleteOnShutdown = false ;
64
74
65
75
private long awaitTerminationMillis = 0 ;
66
76
77
+ private int phase = DEFAULT_PHASE ;
78
+
67
79
@ Nullable
68
80
private String beanName ;
69
81
82
+ @ Nullable
83
+ private ApplicationContext applicationContext ;
84
+
70
85
@ Nullable
71
86
private ExecutorService executor ;
72
87
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
+
73
99
74
100
/**
75
101
* Set the ThreadFactory to use for the ExecutorService's thread pool.
@@ -105,12 +131,32 @@ public void setRejectedExecutionHandler(@Nullable RejectedExecutionHandler rejec
105
131
(rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor .AbortPolicy ());
106
132
}
107
133
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
+
108
154
/**
109
155
* Set whether to wait for scheduled tasks to complete on shutdown,
110
156
* 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.
114
160
* <p>Note that Spring's container shutdown continues while ongoing tasks
115
161
* are being completed. If you want this executor to block and wait for the
116
162
* termination of tasks before the rest of the container continues to shut
@@ -161,11 +207,36 @@ public void setAwaitTerminationMillis(long awaitTerminationMillis) {
161
207
this .awaitTerminationMillis = awaitTerminationMillis ;
162
208
}
163
209
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
+
164
230
@ Override
165
231
public void setBeanName (String name ) {
166
232
this .beanName = name ;
167
233
}
168
234
235
+ @ Override
236
+ public void setApplicationContext (ApplicationContext applicationContext ) {
237
+ this .applicationContext = applicationContext ;
238
+ }
239
+
169
240
170
241
/**
171
242
* Calls {@code initialize()} after the container applied all property values.
@@ -211,9 +282,33 @@ public void destroy() {
211
282
}
212
283
213
284
/**
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
215
309
* @see java.util.concurrent.ExecutorService#shutdown()
216
310
* @see java.util.concurrent.ExecutorService#shutdownNow()
311
+ * @see java.util.concurrent.ExecutorService#awaitTermination
217
312
*/
218
313
public void shutdown () {
219
314
if (logger .isDebugEnabled ()) {
@@ -270,4 +365,136 @@ private void awaitTerminationIfNecessary(ExecutorService executor) {
270
365
}
271
366
}
272
367
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
+
273
500
}
0 commit comments