52
52
* separate thread. This is an attractive choice with virtual threads on JDK 21,
53
53
* expecting common usage with {@link #setVirtualThreads setVirtualThreads(true)}.
54
54
*
55
- * <p><b>NOTE: Scheduling with a fixed delay enforces execution on the single
55
+ * <p><b>NOTE: Scheduling with a fixed delay enforces execution on a single
56
56
* scheduler thread, in order to provide traditional fixed-delay semantics!</b>
57
57
* Prefer the use of fixed rates or cron triggers instead which are a better fit
58
58
* with this thread-per-task scheduler variant.
@@ -113,9 +113,13 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
113
113
private static final TimeUnit NANO = TimeUnit .NANOSECONDS ;
114
114
115
115
116
- private final ScheduledExecutorService scheduledExecutor = createScheduledExecutor ();
116
+ private final ScheduledExecutorService triggerExecutor = createScheduledExecutor ();
117
117
118
- private final ExecutorLifecycleDelegate lifecycleDelegate = new ExecutorLifecycleDelegate (this .scheduledExecutor );
118
+ private final ExecutorLifecycleDelegate triggerLifecycle = new ExecutorLifecycleDelegate (this .triggerExecutor );
119
+
120
+ private final ScheduledExecutorService fixedDelayExecutor = createFixedDelayExecutor ();
121
+
122
+ private final ExecutorLifecycleDelegate fixedDelayLifecycle = new ExecutorLifecycleDelegate (this .fixedDelayExecutor );
119
123
120
124
@ Nullable
121
125
private ErrorHandler errorHandler ;
@@ -195,11 +199,24 @@ private ScheduledExecutorService createScheduledExecutor() {
195
199
return new ScheduledThreadPoolExecutor (1 , this ::newThread ) {
196
200
@ Override
197
201
protected void beforeExecute (Thread thread , Runnable task ) {
198
- lifecycleDelegate .beforeExecute (thread );
202
+ triggerLifecycle .beforeExecute (thread );
203
+ }
204
+ @ Override
205
+ protected void afterExecute (Runnable task , Throwable ex ) {
206
+ triggerLifecycle .afterExecute ();
207
+ }
208
+ };
209
+ }
210
+
211
+ private ScheduledExecutorService createFixedDelayExecutor () {
212
+ return new ScheduledThreadPoolExecutor (1 , this ::newThread ) {
213
+ @ Override
214
+ protected void beforeExecute (Thread thread , Runnable task ) {
215
+ fixedDelayLifecycle .beforeExecute (thread );
199
216
}
200
217
@ Override
201
218
protected void afterExecute (Runnable task , Throwable ex ) {
202
- lifecycleDelegate .afterExecute ();
219
+ fixedDelayLifecycle .afterExecute ();
203
220
}
204
221
};
205
222
}
@@ -227,7 +244,7 @@ private void shutdownAwareErrorHandler(Throwable ex) {
227
244
if (this .errorHandler != null ) {
228
245
this .errorHandler .handleError (ex );
229
246
}
230
- else if (this .scheduledExecutor .isShutdown ()) {
247
+ else if (this .triggerExecutor .isShutdown ()) {
231
248
LogFactory .getLog (getClass ()).debug ("Ignoring scheduled task exception after shutdown" , ex );
232
249
}
233
250
else {
@@ -271,44 +288,44 @@ public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
271
288
ErrorHandler errorHandler =
272
289
(this .errorHandler != null ? this .errorHandler : TaskUtils .getDefaultErrorHandler (true ));
273
290
return new ReschedulingRunnable (
274
- delegate , trigger , this .clock , this .scheduledExecutor , errorHandler ).schedule ();
291
+ delegate , trigger , this .clock , this .triggerExecutor , errorHandler ).schedule ();
275
292
}
276
293
catch (RejectedExecutionException ex ) {
277
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
294
+ throw new TaskRejectedException (this .triggerExecutor , task , ex );
278
295
}
279
296
}
280
297
281
298
@ Override
282
299
public ScheduledFuture <?> schedule (Runnable task , Instant startTime ) {
283
300
Duration delay = Duration .between (this .clock .instant (), startTime );
284
301
try {
285
- return this .scheduledExecutor .schedule (scheduledTask (task ), NANO .convert (delay ), NANO );
302
+ return this .triggerExecutor .schedule (scheduledTask (task ), NANO .convert (delay ), NANO );
286
303
}
287
304
catch (RejectedExecutionException ex ) {
288
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
305
+ throw new TaskRejectedException (this .triggerExecutor , task , ex );
289
306
}
290
307
}
291
308
292
309
@ Override
293
310
public ScheduledFuture <?> scheduleAtFixedRate (Runnable task , Instant startTime , Duration period ) {
294
311
Duration initialDelay = Duration .between (this .clock .instant (), startTime );
295
312
try {
296
- return this .scheduledExecutor .scheduleAtFixedRate (scheduledTask (task ),
313
+ return this .triggerExecutor .scheduleAtFixedRate (scheduledTask (task ),
297
314
NANO .convert (initialDelay ), NANO .convert (period ), NANO );
298
315
}
299
316
catch (RejectedExecutionException ex ) {
300
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
317
+ throw new TaskRejectedException (this .triggerExecutor , task , ex );
301
318
}
302
319
}
303
320
304
321
@ Override
305
322
public ScheduledFuture <?> scheduleAtFixedRate (Runnable task , Duration period ) {
306
323
try {
307
- return this .scheduledExecutor .scheduleAtFixedRate (scheduledTask (task ),
324
+ return this .triggerExecutor .scheduleAtFixedRate (scheduledTask (task ),
308
325
0 , NANO .convert (period ), NANO );
309
326
}
310
327
catch (RejectedExecutionException ex ) {
311
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
328
+ throw new TaskRejectedException (this .triggerExecutor , task , ex );
312
329
}
313
330
}
314
331
@@ -317,57 +334,66 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTim
317
334
Duration initialDelay = Duration .between (this .clock .instant (), startTime );
318
335
try {
319
336
// Blocking task on scheduler thread for fixed delay semantics
320
- return this .scheduledExecutor .scheduleWithFixedDelay (taskOnSchedulerThread (task ),
337
+ return this .fixedDelayExecutor .scheduleWithFixedDelay (taskOnSchedulerThread (task ),
321
338
NANO .convert (initialDelay ), NANO .convert (delay ), NANO );
322
339
}
323
340
catch (RejectedExecutionException ex ) {
324
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
341
+ throw new TaskRejectedException (this .fixedDelayExecutor , task , ex );
325
342
}
326
343
}
327
344
328
345
@ Override
329
346
public ScheduledFuture <?> scheduleWithFixedDelay (Runnable task , Duration delay ) {
330
347
try {
331
348
// Blocking task on scheduler thread for fixed delay semantics
332
- return this .scheduledExecutor .scheduleWithFixedDelay (taskOnSchedulerThread (task ),
349
+ return this .fixedDelayExecutor .scheduleWithFixedDelay (taskOnSchedulerThread (task ),
333
350
0 , NANO .convert (delay ), NANO );
334
351
}
335
352
catch (RejectedExecutionException ex ) {
336
- throw new TaskRejectedException (this .scheduledExecutor , task , ex );
353
+ throw new TaskRejectedException (this .fixedDelayExecutor , task , ex );
337
354
}
338
355
}
339
356
340
357
341
358
@ Override
342
359
public void start () {
343
- this .lifecycleDelegate .start ();
360
+ this .triggerLifecycle .start ();
361
+ this .fixedDelayLifecycle .start ();
344
362
}
345
363
346
364
@ Override
347
365
public void stop () {
348
- this .lifecycleDelegate .stop ();
366
+ this .triggerLifecycle .stop ();
367
+ this .fixedDelayLifecycle .stop ();
349
368
}
350
369
351
370
@ Override
352
371
public void stop (Runnable callback ) {
353
- this .lifecycleDelegate .stop (callback );
372
+ this .triggerLifecycle .stop (); // no callback necessary since it's just triggers with hand-offs
373
+ this .fixedDelayLifecycle .stop (callback ); // callback for currently executing fixed-delay tasks
354
374
}
355
375
356
376
@ Override
357
377
public boolean isRunning () {
358
- return this .lifecycleDelegate .isRunning ();
378
+ return this .triggerLifecycle .isRunning ();
359
379
}
360
380
361
381
@ Override
362
382
public void onApplicationEvent (ContextClosedEvent event ) {
363
383
if (event .getApplicationContext () == this .applicationContext ) {
364
- this .scheduledExecutor .shutdown ();
384
+ this .triggerExecutor .shutdown ();
385
+ this .fixedDelayExecutor .shutdown ();
365
386
}
366
387
}
367
388
368
389
@ Override
369
390
public void close () {
370
- for (Runnable remainingTask : this .scheduledExecutor .shutdownNow ()) {
391
+ for (Runnable remainingTask : this .triggerExecutor .shutdownNow ()) {
392
+ if (remainingTask instanceof Future <?> future ) {
393
+ future .cancel (true );
394
+ }
395
+ }
396
+ for (Runnable remainingTask : this .fixedDelayExecutor .shutdownNow ()) {
371
397
if (remainingTask instanceof Future <?> future ) {
372
398
future .cancel (true );
373
399
}
0 commit comments