|
16 | 16 |
|
17 | 17 | package org.springframework.boot.autoconfigure.task; |
18 | 18 |
|
| 19 | +import java.lang.annotation.ElementType; |
| 20 | +import java.lang.annotation.Retention; |
| 21 | +import java.lang.annotation.RetentionPolicy; |
| 22 | +import java.lang.annotation.Target; |
19 | 23 | import java.util.concurrent.CompletableFuture; |
20 | 24 | import java.util.concurrent.CountDownLatch; |
21 | 25 | import java.util.concurrent.Executor; |
|
24 | 28 | import java.util.concurrent.atomic.AtomicReference; |
25 | 29 | import java.util.function.Consumer; |
26 | 30 |
|
| 31 | +import io.micrometer.context.ThreadLocalAccessor; |
27 | 32 | import org.assertj.core.api.InstanceOfAssertFactories; |
28 | 33 | import org.jspecify.annotations.Nullable; |
29 | 34 | import org.junit.jupiter.api.Test; |
|
42 | 47 | import org.springframework.boot.test.context.runner.ApplicationContextRunner; |
43 | 48 | import org.springframework.boot.test.context.runner.ContextConsumer; |
44 | 49 | import org.springframework.boot.test.system.OutputCaptureExtension; |
| 50 | +import org.springframework.boot.testsupport.classpath.resources.WithResource; |
45 | 51 | import org.springframework.context.ConfigurableApplicationContext; |
46 | 52 | import org.springframework.context.annotation.Bean; |
47 | 53 | import org.springframework.context.annotation.Configuration; |
|
50 | 56 | import org.springframework.core.task.TaskDecorator; |
51 | 57 | import org.springframework.core.task.TaskExecutor; |
52 | 58 | import org.springframework.core.task.support.CompositeTaskDecorator; |
| 59 | +import org.springframework.core.task.support.ContextPropagatingTaskDecorator; |
53 | 60 | import org.springframework.scheduling.TaskScheduler; |
54 | 61 | import org.springframework.scheduling.annotation.Async; |
55 | 62 | import org.springframework.scheduling.annotation.AsyncConfigurer; |
@@ -253,6 +260,35 @@ void simpleAsyncTaskExecutorBuilderUsesVirtualThreadsWhenEnabled() { |
253 | 260 | }); |
254 | 261 | } |
255 | 262 |
|
| 263 | + @Test |
| 264 | + @WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor", |
| 265 | + content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests$TestThreadLocalAccessor") |
| 266 | + void asyncTaskExecutorShouldNotNotRegisterContextPropagatingTaskDecoratorByDefault() { |
| 267 | + this.contextRunner.withUserConfiguration(AsyncConfiguration.class, TestBean.class).run((context) -> { |
| 268 | + assertThat(context).doesNotHaveBean(ContextPropagatingTaskDecorator.class); |
| 269 | + TestBean bean = context.getBean(TestBean.class); |
| 270 | + TestThreadLocalHolder.setValue("from-context"); |
| 271 | + String text = bean.echoContext().get(); |
| 272 | + assertThat(text).contains("task-").endsWith("null"); |
| 273 | + }); |
| 274 | + |
| 275 | + } |
| 276 | + |
| 277 | + @Test |
| 278 | + @WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor", |
| 279 | + content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests$TestThreadLocalAccessor") |
| 280 | + void asyncTaskExecutorWhenContextPropagationIsEnabledShouldRegisterBean() { |
| 281 | + this.contextRunner.withUserConfiguration(AsyncConfiguration.class, TestBean.class) |
| 282 | + .withPropertyValues("spring.task.execution.propagate-context=true") |
| 283 | + .run((context) -> { |
| 284 | + assertThat(context).hasSingleBean(ContextPropagatingTaskDecorator.class); |
| 285 | + TestBean bean = context.getBean(TestBean.class); |
| 286 | + TestThreadLocalHolder.setValue("from-context"); |
| 287 | + String text = bean.echoContext().get(); |
| 288 | + assertThat(text).contains("task-").endsWith("from-context"); |
| 289 | + }); |
| 290 | + } |
| 291 | + |
256 | 292 | @Test |
257 | 293 | void taskExecutorWhenHasCustomTaskExecutorShouldBackOff() { |
258 | 294 | this.contextRunner.withBean("customTaskExecutor", Executor.class, SyncTaskExecutor::new).run((context) -> { |
@@ -591,6 +627,14 @@ private String virtualThreadName(SimpleAsyncTaskExecutor taskExecutor) throws In |
591 | 627 | return thread.getName(); |
592 | 628 | } |
593 | 629 |
|
| 630 | + @Target(ElementType.METHOD) |
| 631 | + @Retention(RetentionPolicy.RUNTIME) |
| 632 | + @WithResource(name = "META-INF/services/io.micrometer.context.ThreadLocalAccessor", |
| 633 | + content = "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationTests.TestThreadLocalAccessor") |
| 634 | + @interface WithThreadLocalAccessor { |
| 635 | + |
| 636 | + } |
| 637 | + |
594 | 638 | @Configuration(proxyBeanMethods = false) |
595 | 639 | static class CustomThreadPoolTaskExecutorBuilderConfig { |
596 | 640 |
|
@@ -622,6 +666,56 @@ Future<String> echo(String text) { |
622 | 666 | return CompletableFuture.completedFuture(Thread.currentThread().getName() + " " + text); |
623 | 667 | } |
624 | 668 |
|
| 669 | + @Async |
| 670 | + Future<String> echoContext() { |
| 671 | + return CompletableFuture |
| 672 | + .completedFuture(Thread.currentThread().getName() + " " + TestThreadLocalHolder.getValue()); |
| 673 | + } |
| 674 | + |
| 675 | + } |
| 676 | + |
| 677 | + static class TestThreadLocalHolder { |
| 678 | + |
| 679 | + private static final ThreadLocal<String> holder = new ThreadLocal<>(); |
| 680 | + |
| 681 | + static void setValue(String value) { |
| 682 | + holder.set(value); |
| 683 | + } |
| 684 | + |
| 685 | + static String getValue() { |
| 686 | + return holder.get(); |
| 687 | + } |
| 688 | + |
| 689 | + static void reset() { |
| 690 | + holder.remove(); |
| 691 | + } |
| 692 | + |
| 693 | + } |
| 694 | + |
| 695 | + public static class TestThreadLocalAccessor implements ThreadLocalAccessor<String> { |
| 696 | + |
| 697 | + static final String KEY = "test.threadlocal"; |
| 698 | + |
| 699 | + @Override |
| 700 | + public Object key() { |
| 701 | + return KEY; |
| 702 | + } |
| 703 | + |
| 704 | + @Override |
| 705 | + public String getValue() { |
| 706 | + return TestThreadLocalHolder.getValue(); |
| 707 | + } |
| 708 | + |
| 709 | + @Override |
| 710 | + public void setValue(String value) { |
| 711 | + TestThreadLocalHolder.setValue(value); |
| 712 | + } |
| 713 | + |
| 714 | + @Override |
| 715 | + public void setValue() { |
| 716 | + TestThreadLocalHolder.reset(); |
| 717 | + } |
| 718 | + |
625 | 719 | } |
626 | 720 |
|
627 | 721 | } |
0 commit comments