`SimpleAsyncTaskScheduler` does not apply its `TaskDecorator` to fixed-delay tasks
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 82/100
Direzione di ricerca
Inizia da SimpleAsyncTaskScheduler, in particolare da taskOnSchedulerThread e execute, per confrontare la gestione delle attività con ritardo fisso e delle altre attività pianificate. Esegui SimpleAsyncTaskSchedulerTests e controlla il test del decorator per il ritardo fisso descritto nell’issue. Il lavoro è completato quando le attività con ritardo fisso ricevono il decorator e il test ha esito positivo.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
SimpleAsyncTaskScheduler runs fixed-delay tasks on its scheduler thread (#31334), wrapped by taskOnSchedulerThread, which adds the error handler but not the TaskDecorator. One-time, fixed-rate and trigger tasks go through execute, where the decorator is applied. ThreadPoolTaskScheduler decorates all of them, fixed-delay included (decorateTask).
So a @Scheduled(fixedDelay = …) method runs without whatever the decorator sets up (MDC, security or context propagation), and nothing warns about it. With Spring Boot it changes when virtual threads are enabled: Boot then creates a SimpleAsyncTaskScheduler instead of a ThreadPoolTaskScheduler, passing the application's TaskDecorator beans to either of them.
The Javadoc of setTaskDecorator says the decorator is "applied to any Runnable about to be executed". The class Javadoc says that fixed-delay tasks run on the single scheduler thread, but not that they skip the decorator.
Affects 7.0.10; the code is the same on main.
Reproducer
Spring Framework 7.0.10 on JDK 25: java -cp <spring-context and its dependencies> FixedDelayTaskDecorator.java
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
public class FixedDelayTaskDecorator {
// Set by the TaskDecorator around each task, as a context-propagating decorator would do
static final ThreadLocal<Boolean> decorated = ThreadLocal.withInitial(() -> false);
static final Map<String, Boolean> results = new ConcurrentHashMap<>();
static final CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
try (var context = new AnnotationConfigApplicationContext(Config.class)) {
latch.await(5, TimeUnit.SECONDS);
System.out.println("fixedRate task decorated: " + results.get("fixedRate"));
System.out.println("fixedDelay task decorated: " + results.get("fixedDelay"));
}
}
@Configuration
@EnableScheduling
static class Config {
@Bean
SimpleAsyncTaskScheduler taskScheduler() {
SimpleAsyncTaskScheduler scheduler = new SimpleAsyncTaskScheduler();
scheduler.setVirtualThreads(true);
scheduler.setTaskDecorator(task -> () -> {
decorated.set(true);
try {
task.run();
}
finally {
decorated.remove();
}
});
return scheduler;
}
@Bean
Tasks tasks() {
return new Tasks();
}
}
static class Tasks {
@Scheduled(fixedRate = 100)
void fixedRate() {
record("fixedRate");
}
@Scheduled(fixedDelay = 100)
void fixedDelay() {
record("fixedDelay");
}
private static void record(String task) {
if (results.putIfAbsent(task, decorated.get()) == null) {
latch.countDown();
}
}
}
}
Output:
fixedRate task decorated: true
fixedDelay task decorated: false
With a ThreadPoolTaskScheduler instead (and no setVirtualThreads), both lines print true.
Possible fix
Keep a reference to the decorator and apply it in taskOnSchedulerThread, around the error handling, as execute does:
--- a/SimpleAsyncTaskScheduler.java
+++ b/SimpleAsyncTaskScheduler.java
@@ -37,6 +37,7 @@
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
@@ -124,6 +125,8 @@
private @Nullable ErrorHandler errorHandler;
+ private @Nullable TaskDecorator taskDecorator;
+
private Clock clock = Clock.systemDefaultZone();
private int phase = DEFAULT_PHASE;
@@ -142,6 +145,12 @@
this.errorHandler = errorHandler;
}
+ @Override
+ public void setTaskDecorator(TaskDecorator taskDecorator) {
+ super.setTaskDecorator(taskDecorator);
+ this.taskDecorator = taskDecorator;
+ }
+
/**
* Set the clock to use for scheduling purposes.
* <p>The default clock is the system clock for the default time zone.
@@ -230,8 +239,9 @@
}
private Runnable taskOnSchedulerThread(Runnable task) {
- return new DelegatingErrorHandlingRunnable(task,
+ Runnable taskToUse = new DelegatingErrorHandlingRunnable(task,
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true)));
+ return (this.taskDecorator != null ? this.taskDecorator.decorate(taskToUse) : taskToUse);
}
private Runnable scheduledTask(Runnable task) {
With that change on top of 7.0.10, the reproducer prints true twice, and SimpleAsyncTaskSchedulerTests passes together with this test, which fails without it (its buildExecutor() already sets a decorator that flags taskRun):
@Test
void scheduleWithFixedDelay() {
TestTask task = new TestTask(this.testName, 3);
Future<?> future = scheduler.scheduleWithFixedDelay(task, Duration.ofMillis(10));
await(task);
future.cancel(true);
assertThat(taskRun.get()).isTrue();
}
- Lingua principale
- Java
- Stelle
- 60.3k
- Fork
- 38.7k
- Merge medio
- 1g 14h
- PR unite (30g)
- 34
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di spring-projects/spring-framework
-
PropertyEditorRegistrySupport.createDefaultEditors is not thread-safe when a registry is shared across threadsForse già presa @codingkush l’ha presa 1 giorno fa. Apertastatus: waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
spring-projects/spring-framework#37614 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
status: waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 87/100
spring-projects/spring-framework#37613 ·
I maintainer di solito rispondono entro 1 giorno
-
`YamlProcessor.getFlattenedMap` with `includeEmpty` returns a `null` key for an empty root mappingForse di nuovo libera @erfannvb l’ha presa 13 giorni fa e non c’è nessuna pull request aperta. Apertastatus: waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
spring-projects/spring-framework#37522 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
`WebClient` with default codecs drops `ProblemDetail` extension membersForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Apertastatus: waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
spring-projects/spring-framework#37503 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
StandardApiVersionDeprecationHandler generates nonconforming Sunset HTTP datesForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Apertastatus: waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
spring-projects/spring-framework#37486 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di spring-projects/spring-framework
Issue simili
-
[Bug] 资源包列表 与 模组/数据包/世界 列表排列顺序不一致Forse già presa @fishdry1000 l’ha presa 4 giorni fa. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
AtlasOfLivingAustralia/species-lists#882 ·
I maintainer di solito rispondono entro 1 giorno
-
Type/Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
ballerina-platform/ballerina-library#9458 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
winder/Universal-G-Code-Sender#3242 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
PCL-Community/PCL-CE#3767 ·
I maintainer di solito rispondono entro 1 giorno