From dddfab9307aba0718a2f563e572447886da16953 Mon Sep 17 00:00:00 2001 From: Sandipan Date: Thu, 27 Nov 2025 22:48:39 +0530 Subject: [PATCH 1/3] Add support for context propagation in task execution Signed-off-by: Sandipan --- .../task/TaskExecutionProperties.java | 13 ++++++++ .../task/TaskExecutorConfigurations.java | 30 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java index 144fbb60e67c..93eacc89c7f2 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java @@ -44,6 +44,11 @@ public class TaskExecutionProperties { */ private Mode mode = Mode.AUTO; + /** + * Indicates whether to register ContextPropagatingTaskDecorator bean + */ + private boolean propagateContext = false; + /** * Prefix to use for the names of newly created threads. */ @@ -69,6 +74,14 @@ public void setMode(Mode mode) { this.mode = mode; } + public boolean getPropagateContext() { + return this.propagateContext; + } + + public void setPropagateContext(boolean propagateContext) { + this.propagateContext = propagateContext; + } + public String getThreadNamePrefix() { return this.threadNamePrefix; } diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java index a519c65374a9..e4819f210587 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -75,17 +75,39 @@ static class TaskExecutorConfiguration { @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @ConditionalOnThreading(Threading.VIRTUAL) - SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) { - return builder.build(); + SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder, + ObjectProvider decorators) { + TaskDecorator decorator = decorators.getIfAvailable(); + SimpleAsyncTaskExecutor executor = builder.build(); + if (decorator != null) { + executor.setTaskDecorator(decorator); + } + return executor; } @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @Lazy @ConditionalOnThreading(Threading.PLATFORM) - ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) { - return threadPoolTaskExecutorBuilder.build(); + ThreadPoolTaskExecutor applicationTaskExecutor( ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder, + ObjectProvider decorators ) { + TaskDecorator decorator = decorators.getIfAvailable(); + ThreadPoolTaskExecutor executor = builder.build(); + if (decorator != null) { + executor.setTaskDecorator(decorator); + } + return executor; } + @Bean + @ConditionalOnProperty( + prefix = "spring.task.execution", + name = "propagate-context", + havingValue = "true", + matchIfMissing = false + ) + TaskDecorator contextPropagatingTaskDecorator() { + return new ContextPropagatingTaskDecorator(); + } } @Configuration(proxyBeanMethods = false) From 8823ac5828e93f79e4ed23a7328badaf31923207 Mon Sep 17 00:00:00 2001 From: Sandipan Date: Wed, 3 Dec 2025 19:25:16 +0530 Subject: [PATCH 2/3] Add unit tests Signed-off-by: Sandipan --- .../task/TaskExecutionAutoConfigurationTests.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java index 40d6fb26e7a6..a1ef0b3af064 100644 --- a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java +++ b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfigurationTests.java @@ -92,13 +92,15 @@ void simpleAsyncTaskExecutorBuilderShouldReadProperties() { "spring.task.execution.simple.reject-tasks-when-limit-reached=true", "spring.task.execution.simple.concurrency-limit=1", "spring.task.execution.shutdown.await-termination=true", - "spring.task.execution.shutdown.await-termination-period=30s") + "spring.task.execution.shutdown.await-termination-period=30s", + "spring.task.execution.propagate-context=true") .run(assertSimpleAsyncTaskExecutor((taskExecutor) -> { assertThat(taskExecutor).hasFieldOrPropertyWithValue("cancelRemainingTasksOnClose", true); assertThat(taskExecutor).hasFieldOrPropertyWithValue("rejectTasksWhenLimitReached", true); assertThat(taskExecutor.getConcurrencyLimit()).isEqualTo(1); assertThat(taskExecutor.getThreadNamePrefix()).isEqualTo("mytest-"); assertThat(taskExecutor).hasFieldOrPropertyWithValue("taskTerminationTimeout", 30000L); + assertThat(taskExecutor).hasFieldOrPropertyWithValue("propagateContext", true); })); } @@ -253,6 +255,16 @@ void simpleAsyncTaskExecutorBuilderUsesVirtualThreadsWhenEnabled() { }); } + @Test + void asyncTaskExecutorShouldApplyContextPropagatingDecoratorWhenEnabled() { + this.contextRunner.withPropertyValues("spring.task.execution.propagate-context=true") + .run((context) -> { + assertThat(context).hasSingleBean(ContextPropagatingTaskDecorator.class); + assertThat(context.getBean(ContextPropagatingTaskDecorator.class)) + .isSameAs(context.getBean("contextPropagatingTaskDecorator")); + }); + } + @Test void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new).run((context) -> { From fa95de7172893d6698aeb4476b92fb69800463e8 Mon Sep 17 00:00:00 2001 From: Sandipan Date: Thu, 4 Dec 2025 19:20:14 +0530 Subject: [PATCH 3/3] Address review comments Signed-off-by: Sandipan --- .../task/TaskExecutionProperties.java | 2 +- .../task/TaskExecutorConfigurations.java | 27 ++++--------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java index 93eacc89c7f2..306d34630ef8 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java @@ -45,7 +45,7 @@ public class TaskExecutionProperties { private Mode mode = Mode.AUTO; /** - * Indicates whether to register ContextPropagatingTaskDecorator bean + * Whether to propagate the current context to task executions. */ private boolean propagateContext = false; diff --git a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java index e4819f210587..351167085692 100644 --- a/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java +++ b/core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations.java @@ -75,36 +75,19 @@ static class TaskExecutorConfiguration { @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @ConditionalOnThreading(Threading.VIRTUAL) - SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder, - ObjectProvider decorators) { - TaskDecorator decorator = decorators.getIfAvailable(); - SimpleAsyncTaskExecutor executor = builder.build(); - if (decorator != null) { - executor.setTaskDecorator(decorator); - } - return executor; + SimpleAsyncTaskExecutor applicationTaskExecutorVirtualThreads(SimpleAsyncTaskExecutorBuilder builder) { + return builder.build(); } @Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) @Lazy @ConditionalOnThreading(Threading.PLATFORM) - ThreadPoolTaskExecutor applicationTaskExecutor( ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder, - ObjectProvider decorators ) { - TaskDecorator decorator = decorators.getIfAvailable(); - ThreadPoolTaskExecutor executor = builder.build(); - if (decorator != null) { - executor.setTaskDecorator(decorator); - } - return executor; + ThreadPoolTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder threadPoolTaskExecutorBuilder) { + return threadPoolTaskExecutorBuilder.build(); } @Bean - @ConditionalOnProperty( - prefix = "spring.task.execution", - name = "propagate-context", - havingValue = "true", - matchIfMissing = false - ) + @ConditionalOnProperty(prefix = "spring.task.execution.propagate-context", havingValue = "true") TaskDecorator contextPropagatingTaskDecorator() { return new ContextPropagatingTaskDecorator(); }