Skip to content

Commit dc2c8d6

Browse files
committed
Add execution metadata to tasks and scheduled tasks
This commit adds new information about the execution and scheduling of tasks. The `Task` type now exposes the `TaskExecutionOutcome` of the latest execution; this includes the instant the execution started, the execution outcome and any thrown exception. The `ScheduledTask` contract can now provide the time when the next execution is scheduled. Closes gh-24560
1 parent 46dccd8 commit dc2c8d6

File tree

8 files changed

+494
-100
lines changed

8 files changed

+494
-100
lines changed

Diff for: spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTask.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 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.
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.scheduling.config;
1818

19+
import java.time.Instant;
1920
import java.util.concurrent.ScheduledFuture;
21+
import java.util.concurrent.TimeUnit;
2022

2123
import org.springframework.lang.Nullable;
2224

@@ -25,6 +27,7 @@
2527
* used as a return value for scheduling methods.
2628
*
2729
* @author Juergen Hoeller
30+
* @author Brian Clozel
2831
* @since 4.3
2932
* @see ScheduledTaskRegistrar#scheduleCronTask(CronTask)
3033
* @see ScheduledTaskRegistrar#scheduleFixedRateTask(FixedRateTask)
@@ -76,6 +79,22 @@ public void cancel(boolean mayInterruptIfRunning) {
7679
}
7780
}
7881

82+
/**
83+
* Return the next scheduled execution of the task, or {@code null}
84+
* if the task has been cancelled or no new execution is scheduled.
85+
* @since 6.2
86+
*/
87+
@Nullable
88+
public Instant nextExecution() {
89+
if (this.future != null && !this.future.isCancelled()) {
90+
long delay = this.future.getDelay(TimeUnit.MILLISECONDS);
91+
if (delay > 0) {
92+
return Instant.now().plusMillis(delay);
93+
}
94+
}
95+
return null;
96+
}
97+
7998
@Override
8099
public String toString() {
81100
return this.task.toString();

Diff for: spring-context/src/main/java/org/springframework/scheduling/config/Task.java

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 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.
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.scheduling.config;
1818

19+
import java.time.Instant;
20+
1921
import org.springframework.util.Assert;
2022

2123
/**
@@ -24,20 +26,24 @@
2426
*
2527
* @author Chris Beams
2628
* @author Juergen Hoeller
29+
* @author Brian Clozel
2730
* @since 3.2
2831
*/
2932
public class Task {
3033

3134
private final Runnable runnable;
3235

36+
private TaskExecutionOutcome lastExecutionOutcome;
37+
3338

3439
/**
3540
* Create a new {@code Task}.
3641
* @param runnable the underlying task to execute
3742
*/
3843
public Task(Runnable runnable) {
3944
Assert.notNull(runnable, "Runnable must not be null");
40-
this.runnable = runnable;
45+
this.runnable = new OutcomeTrackingRunnable(runnable);
46+
this.lastExecutionOutcome = TaskExecutionOutcome.create();
4147
}
4248

4349

@@ -48,10 +54,45 @@ public Runnable getRunnable() {
4854
return this.runnable;
4955
}
5056

57+
/**
58+
* Return the outcome of the last task execution.
59+
* @since 6.2
60+
*/
61+
public TaskExecutionOutcome getLastExecutionOutcome() {
62+
return this.lastExecutionOutcome;
63+
}
5164

5265
@Override
5366
public String toString() {
5467
return this.runnable.toString();
5568
}
5669

70+
71+
private class OutcomeTrackingRunnable implements Runnable {
72+
73+
private final Runnable runnable;
74+
75+
public OutcomeTrackingRunnable(Runnable runnable) {
76+
this.runnable = runnable;
77+
}
78+
79+
@Override
80+
public void run() {
81+
try {
82+
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.start(Instant.now());
83+
this.runnable.run();
84+
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.success();
85+
}
86+
catch (Throwable exc) {
87+
Task.this.lastExecutionOutcome = Task.this.lastExecutionOutcome.failure(exc);
88+
throw exc;
89+
}
90+
}
91+
92+
@Override
93+
public String toString() {
94+
return this.runnable.toString();
95+
}
96+
}
97+
5798
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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.config;
18+
19+
import java.time.Instant;
20+
21+
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Outcome of a {@link Task} execution.
26+
* @param executionTime the instant when the task execution started, {@code null} if the task has not started.
27+
* @param status the {@link Status} of the execution outcome.
28+
* @param throwable the exception thrown from the task execution, if any.
29+
* @author Brian Clozel
30+
* @since 6.2
31+
*/
32+
public record TaskExecutionOutcome(@Nullable Instant executionTime, Status status, @Nullable Throwable throwable) {
33+
34+
TaskExecutionOutcome start(Instant executionTime) {
35+
return new TaskExecutionOutcome(executionTime, Status.STARTED, null);
36+
}
37+
38+
TaskExecutionOutcome success() {
39+
Assert.state(this.executionTime != null, "Task has not been started yet");
40+
return new TaskExecutionOutcome(this.executionTime, Status.SUCCESS, null);
41+
}
42+
43+
TaskExecutionOutcome failure(Throwable throwable) {
44+
Assert.state(this.executionTime != null, "Task has not been started yet");
45+
return new TaskExecutionOutcome(this.executionTime, Status.ERROR, throwable);
46+
}
47+
48+
static TaskExecutionOutcome create() {
49+
return new TaskExecutionOutcome(null, Status.NONE, null);
50+
}
51+
52+
53+
/**
54+
* Status of the task execution outcome.
55+
*/
56+
public enum Status {
57+
/**
58+
* The task has not been executed so far.
59+
*/
60+
NONE,
61+
/**
62+
* The task execution has been started and is ongoing.
63+
*/
64+
STARTED,
65+
/**
66+
* The task execution finished successfully.
67+
*/
68+
SUCCESS,
69+
/**
70+
* The task execution finished with an error.
71+
*/
72+
ERROR
73+
}
74+
}

0 commit comments

Comments
 (0)