Skip to content

Commit 66235fa

Browse files
committed
Consistent TaskDecorator and ErrorHandler support in schedulers
Closes gh-23755 Closes gh-32460
1 parent cf31d08 commit 66235fa

File tree

8 files changed

+731
-30
lines changed

8 files changed

+731
-30
lines changed

Diff for: spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -199,6 +199,10 @@ private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
199199
return adapter;
200200
}
201201

202+
Runnable decorateTaskIfNecessary(Runnable task) {
203+
return (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
204+
}
205+
202206

203207
/**
204208
* TaskExecutorAdapter subclass that wraps all provided Runnables and Callables

Diff for: spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@
2020
import java.time.Duration;
2121
import java.time.Instant;
2222
import java.util.Date;
23+
import java.util.concurrent.Callable;
2324
import java.util.concurrent.Executor;
2425
import java.util.concurrent.Executors;
26+
import java.util.concurrent.Future;
2527
import java.util.concurrent.RejectedExecutionException;
2628
import java.util.concurrent.ScheduledExecutorService;
2729
import java.util.concurrent.ScheduledFuture;
@@ -39,6 +41,7 @@
3941
import org.springframework.util.Assert;
4042
import org.springframework.util.ClassUtils;
4143
import org.springframework.util.ErrorHandler;
44+
import org.springframework.util.concurrent.ListenableFuture;
4245

4346
/**
4447
* Adapter that takes a {@code java.util.concurrent.ScheduledExecutorService} and
@@ -191,6 +194,7 @@ public void setErrorHandler(ErrorHandler errorHandler) {
191194
* @see Clock#systemDefaultZone()
192195
*/
193196
public void setClock(Clock clock) {
197+
Assert.notNull(clock, "Clock must not be null");
194198
this.clock = clock;
195199
}
196200

@@ -200,6 +204,33 @@ public Clock getClock() {
200204
}
201205

202206

207+
@Override
208+
public void execute(Runnable task) {
209+
super.execute(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
210+
}
211+
212+
@Override
213+
public Future<?> submit(Runnable task) {
214+
return super.submit(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
215+
}
216+
217+
@Override
218+
public <T> Future<T> submit(Callable<T> task) {
219+
return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
220+
}
221+
222+
@SuppressWarnings("deprecation")
223+
@Override
224+
public ListenableFuture<?> submitListenable(Runnable task) {
225+
return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
226+
}
227+
228+
@SuppressWarnings("deprecation")
229+
@Override
230+
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
231+
return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
232+
}
233+
203234
@Override
204235
@Nullable
205236
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
@@ -211,7 +242,9 @@ public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
211242
else {
212243
ErrorHandler errorHandler =
213244
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
214-
return new ReschedulingRunnable(task, trigger, this.clock, scheduleExecutorToUse, errorHandler).schedule();
245+
return new ReschedulingRunnable(
246+
decorateTaskIfNecessary(task), trigger, this.clock, scheduleExecutorToUse, errorHandler)
247+
.schedule();
215248
}
216249
}
217250
catch (RejectedExecutionException ex) {
@@ -283,6 +316,7 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay)
283316

284317
private Runnable decorateTask(Runnable task, boolean isRepeatingTask) {
285318
Runnable result = TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, isRepeatingTask);
319+
result = decorateTaskIfNecessary(result);
286320
if (this.enterpriseConcurrentScheduler) {
287321
result = ManagedTaskBuilder.buildManagedTask(result, task.toString());
288322
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.scheduling.concurrent;
18+
19+
import java.lang.reflect.UndeclaredThrowableException;
20+
import java.util.concurrent.Callable;
21+
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.scheduling.support.TaskUtils;
24+
import org.springframework.util.ErrorHandler;
25+
import org.springframework.util.ReflectionUtils;
26+
27+
/**
28+
* {@link Callable} adapter for an {@link ErrorHandler}.
29+
*
30+
* @author Juergen Hoeller
31+
* @since 6.2
32+
* @param <V> the value type
33+
*/
34+
class DelegatingErrorHandlingCallable<V> implements Callable<V> {
35+
36+
private final Callable<V> delegate;
37+
38+
private final ErrorHandler errorHandler;
39+
40+
41+
public DelegatingErrorHandlingCallable(Callable<V> delegate, @Nullable ErrorHandler errorHandler) {
42+
this.delegate = delegate;
43+
this.errorHandler = (errorHandler != null ? errorHandler :
44+
TaskUtils.getDefaultErrorHandler(false));
45+
}
46+
47+
48+
@Override
49+
@Nullable
50+
public V call() throws Exception {
51+
try {
52+
return this.delegate.call();
53+
}
54+
catch (Throwable ex) {
55+
try {
56+
this.errorHandler.handleError(ex);
57+
}
58+
catch (UndeclaredThrowableException exToPropagate) {
59+
ReflectionUtils.rethrowException(exToPropagate.getUndeclaredThrowable());
60+
}
61+
return null;
62+
}
63+
}
64+
65+
}

Diff for: spring-context/src/main/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskScheduler.java

+51-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Clock;
2020
import java.time.Duration;
2121
import java.time.Instant;
22+
import java.util.concurrent.Callable;
2223
import java.util.concurrent.Executor;
2324
import java.util.concurrent.Future;
2425
import java.util.concurrent.RejectedExecutionException;
@@ -41,7 +42,9 @@
4142
import org.springframework.scheduling.Trigger;
4243
import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
4344
import org.springframework.scheduling.support.TaskUtils;
45+
import org.springframework.util.Assert;
4446
import org.springframework.util.ErrorHandler;
47+
import org.springframework.util.concurrent.ListenableFuture;
4548

4649
/**
4750
* A simple implementation of Spring's {@link TaskScheduler} interface, using
@@ -108,6 +111,9 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
108111

109112
private final ExecutorLifecycleDelegate lifecycleDelegate = new ExecutorLifecycleDelegate(this.scheduledExecutor);
110113

114+
@Nullable
115+
private ErrorHandler errorHandler;
116+
111117
private Clock clock = Clock.systemDefaultZone();
112118

113119
private int phase = DEFAULT_PHASE;
@@ -119,13 +125,22 @@ public class SimpleAsyncTaskScheduler extends SimpleAsyncTaskExecutor implements
119125
private ApplicationContext applicationContext;
120126

121127

128+
/**
129+
* Provide an {@link ErrorHandler} strategy.
130+
* @since 6.2
131+
*/
132+
public void setErrorHandler(ErrorHandler errorHandler) {
133+
Assert.notNull(errorHandler, "ErrorHandler must not be null");
134+
this.errorHandler = errorHandler;
135+
}
136+
122137
/**
123138
* Set the clock to use for scheduling purposes.
124139
* <p>The default clock is the system clock for the default time zone.
125-
* @since 5.3
126140
* @see Clock#systemDefaultZone()
127141
*/
128142
public void setClock(Clock clock) {
143+
Assert.notNull(clock, "Clock must not be null");
129144
this.clock = clock;
130145
}
131146

@@ -194,15 +209,19 @@ protected void doExecute(Runnable task) {
194209
}
195210

196211
private Runnable taskOnSchedulerThread(Runnable task) {
197-
return new DelegatingErrorHandlingRunnable(task, TaskUtils.getDefaultErrorHandler(true));
212+
return new DelegatingErrorHandlingRunnable(task,
213+
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true)));
198214
}
199215

200216
private Runnable scheduledTask(Runnable task) {
201217
return () -> execute(new DelegatingErrorHandlingRunnable(task, this::shutdownAwareErrorHandler));
202218
}
203219

204220
private void shutdownAwareErrorHandler(Throwable ex) {
205-
if (this.scheduledExecutor.isTerminated()) {
221+
if (this.errorHandler != null) {
222+
this.errorHandler.handleError(ex);
223+
}
224+
else if (this.scheduledExecutor.isTerminated()) {
206225
LogFactory.getLog(getClass()).debug("Ignoring scheduled task exception after shutdown", ex);
207226
}
208227
else {
@@ -211,12 +230,40 @@ private void shutdownAwareErrorHandler(Throwable ex) {
211230
}
212231

213232

233+
@Override
234+
public void execute(Runnable task) {
235+
super.execute(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
236+
}
237+
238+
@Override
239+
public Future<?> submit(Runnable task) {
240+
return super.submit(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
241+
}
242+
243+
@Override
244+
public <T> Future<T> submit(Callable<T> task) {
245+
return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
246+
}
247+
248+
@SuppressWarnings("deprecation")
249+
@Override
250+
public ListenableFuture<?> submitListenable(Runnable task) {
251+
return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false));
252+
}
253+
254+
@SuppressWarnings("deprecation")
255+
@Override
256+
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
257+
return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler));
258+
}
259+
214260
@Override
215261
@Nullable
216262
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
217263
try {
218264
Runnable delegate = scheduledTask(task);
219-
ErrorHandler errorHandler = TaskUtils.getDefaultErrorHandler(true);
265+
ErrorHandler errorHandler =
266+
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
220267
return new ReschedulingRunnable(
221268
delegate, trigger, this.clock, this.scheduledExecutor, errorHandler).schedule();
222269
}

0 commit comments

Comments
 (0)