Add the async module
Every @Async behaviour that surprises people, asserted by a test and captured to docs/output/: the self-invocation trap, what CGLIB cannot override, the IllegalArgumentException a plain return type throws, the unbounded queue that makes max-size decoration, spring.task.execution.propagate-context (new in Boot 4.1.0), the two Executor beans that leave @Async on an unpooled SimpleAsyncTaskExecutor, and JEP 491 measured on JDK 21 against JDK 25.
This commit is contained in:
6
async/docs/output/context-propagation.txt
Normal file
6
async/docs/output/context-propagation.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
== spring.task.execution.propagate-context (new in Boot 4.1.0) ==
|
||||
|
||||
caller thread : main (virtual=false)
|
||||
RequestId set : req-4711
|
||||
@Async thread saw: req-4711
|
||||
|
||||
14
async/docs/output/executor-force-mode.txt
Normal file
14
async/docs/output/executor-force-mode.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
== Two custom Executor beans plus spring.task.execution.mode=force ==
|
||||
|
||||
Executor beans in this context: 3
|
||||
reportsExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=2 maxPoolSize=2 queueCapacity=2147483647 threadNamePrefix=reports-
|
||||
emailsExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=2 maxPoolSize=2 queueCapacity=2147483647 threadNamePrefix=emails-
|
||||
applicationTaskExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=8 maxPoolSize=2147483647 queueCapacity=2147483647 threadNamePrefix=task-
|
||||
Bean named 'taskExecutor' present: false
|
||||
Bean named 'applicationTaskExecutor' present: true
|
||||
|
||||
@Async ran on : task-1 (virtual=false)
|
||||
|
||||
10
async/docs/output/executor-one-custom.txt
Normal file
10
async/docs/output/executor-one-custom.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
== A single custom Executor bean ==
|
||||
|
||||
Executor beans in this context: 1
|
||||
myExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=2 maxPoolSize=2 queueCapacity=2147483647 threadNamePrefix=mine-
|
||||
Bean named 'taskExecutor' present: false
|
||||
Bean named 'applicationTaskExecutor' present: false
|
||||
|
||||
@Async ran on : mine-1 (virtual=false)
|
||||
|
||||
8
async/docs/output/executor-report.txt
Normal file
8
async/docs/output/executor-report.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
== Stock Spring Boot 4.1.1 context, nothing configured ==
|
||||
|
||||
Executor beans in this context: 1
|
||||
applicationTaskExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=8 maxPoolSize=2147483647 queueCapacity=2147483647 threadNamePrefix=task-
|
||||
Bean named 'taskExecutor' present: false
|
||||
Bean named 'applicationTaskExecutor' present: true
|
||||
|
||||
12
async/docs/output/executor-two-custom.txt
Normal file
12
async/docs/output/executor-two-custom.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
== Two custom Executor beans, no 'taskExecutor' ==
|
||||
|
||||
Executor beans in this context: 2
|
||||
reportsExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=2 maxPoolSize=2 queueCapacity=2147483647 threadNamePrefix=reports-
|
||||
emailsExecutor -> org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor [TaskExecutor]
|
||||
corePoolSize=2 maxPoolSize=2 queueCapacity=2147483647 threadNamePrefix=emails-
|
||||
Bean named 'taskExecutor' present: false
|
||||
Bean named 'applicationTaskExecutor' present: false
|
||||
|
||||
@Async ran on : SimpleAsyncTaskExecutor-1 (virtual=false)
|
||||
|
||||
20
async/docs/output/pinning-probe.txt
Normal file
20
async/docs/output/pinning-probe.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
---------------------------------------------------------------
|
||||
java.version : 21.0.12.1
|
||||
jdk.virtualThreadScheduler.parallelism: 2
|
||||
tasks=32 sleep=300ms
|
||||
|
||||
no monitor held : 313 ms
|
||||
blocked inside synchronized: 4806 ms
|
||||
|
||||
Pinned would be about 4800 ms for the synchronized run (32 tasks / 2 carriers x 300 ms).
|
||||
Not pinned is about 300 ms for both.
|
||||
---------------------------------------------------------------
|
||||
java.version : 25.0.4.1
|
||||
jdk.virtualThreadScheduler.parallelism: 2
|
||||
tasks=32 sleep=300ms
|
||||
|
||||
no monitor held : 312 ms
|
||||
blocked inside synchronized: 301 ms
|
||||
|
||||
Pinned would be about 4800 ms for the synchronized run (32 tasks / 2 carriers x 300 ms).
|
||||
Not pinned is about 300 ms for both.
|
||||
6
async/docs/output/pool-bounded-queue.txt
Normal file
6
async/docs/output/pool-bounded-queue.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
== core-size=4, max-size=12, queue-capacity=4, 16 blocking tasks ==
|
||||
|
||||
distinct threads that ran a task : 12
|
||||
thread names : [task-6, task-7, task-11, task-12, task-10, task-8, task-9, task-1, task-2, task-3, task-4, task-5]
|
||||
4 core threads, 4 tasks queued, 8 more threads created up to max-size.
|
||||
|
||||
6
async/docs/output/pool-unbounded-queue.txt
Normal file
6
async/docs/output/pool-unbounded-queue.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
== core-size=4, max-size=12, queue-capacity=<unbounded default>, 16 blocking tasks ==
|
||||
|
||||
distinct threads that ran a task : 4
|
||||
thread names : [task-1, task-2, task-3, task-4]
|
||||
max-size had no effect: the queue never refused a task.
|
||||
|
||||
11
async/docs/output/return-types.txt
Normal file
11
async/docs/output/return-types.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
== What each @Async return type hands back ==
|
||||
|
||||
completableFuture().get() : task-5 (virtual=false)
|
||||
plainString() : java.lang.IllegalArgumentException: Invalid return type for async method (only Future and void supported): class java.lang.String
|
||||
futureThatThrows().get() : IllegalStateException: thrown from a CompletableFuture @Async method
|
||||
voidThatThrows() : returned normally. The exception went to
|
||||
SimpleAsyncUncaughtExceptionHandler, which logs it at
|
||||
ERROR under the logger
|
||||
o.s.a.i.SimpleAsyncUncaughtExceptionHandler and
|
||||
discards it. The caller is never told.
|
||||
|
||||
7
async/docs/output/self-invocation.txt
Normal file
7
async/docs/output/self-invocation.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
== Which thread each call actually ran on (Boot 4.1.1, JDK 25) ==
|
||||
|
||||
caller thread : main (virtual=false)
|
||||
service.annotated() : task-7 (virtual=false)
|
||||
service.viaSelfInvocation() : main (virtual=false)
|
||||
service.viaSelfReference() : task-8 (virtual=false)
|
||||
|
||||
26
async/docs/output/tests.txt
Normal file
26
async/docs/output/tests.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
[INFO] Running com.ankurm.async.ContextNotPropagatedTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.804 s -- in com.ankurm.async.ContextNotPropagatedTest
|
||||
[INFO] Running com.ankurm.async.VisibilityTest
|
||||
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.068 s -- in com.ankurm.async.VisibilityTest
|
||||
[INFO] Running com.ankurm.async.VirtualThreadsTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.204 s -- in com.ankurm.async.VirtualThreadsTest
|
||||
[INFO] Running com.ankurm.async.ExecutorReportTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.015 s -- in com.ankurm.async.ExecutorReportTest
|
||||
[INFO] Running com.ankurm.async.BoundedQueueTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.612 s -- in com.ankurm.async.BoundedQueueTest
|
||||
[INFO] Running com.ankurm.async.OwnExecutorTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.102 s -- in com.ankurm.async.OwnExecutorTest
|
||||
[INFO] Running com.ankurm.async.UnboundedQueueTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.624 s -- in com.ankurm.async.UnboundedQueueTest
|
||||
[INFO] Running com.ankurm.async.ForceModeTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.126 s -- in com.ankurm.async.ForceModeTest
|
||||
[INFO] Running com.ankurm.async.SelfInvocationTest
|
||||
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.030 s -- in com.ankurm.async.SelfInvocationTest
|
||||
[INFO] Running com.ankurm.async.TwoExecutorsTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.116 s -- in com.ankurm.async.TwoExecutorsTest
|
||||
[INFO] Running com.ankurm.async.ReturnTypeTest
|
||||
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.025 s -- in com.ankurm.async.ReturnTypeTest
|
||||
[INFO] Running com.ankurm.async.ContextPropagatedTest
|
||||
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.096 s -- in com.ankurm.async.ContextPropagatedTest
|
||||
[INFO] Tests run: 22, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
10
async/docs/output/virtual-threads.txt
Normal file
10
async/docs/output/virtual-threads.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
== spring.threads.virtual.enabled=true, with pool properties still set ==
|
||||
|
||||
applicationTaskExecutor : org.springframework.core.task.SimpleAsyncTaskExecutor
|
||||
@Async ran on : task-1 (virtual=true)
|
||||
|
||||
Executor beans in this context: 1
|
||||
applicationTaskExecutor -> org.springframework.core.task.SimpleAsyncTaskExecutor [TaskExecutor]
|
||||
Bean named 'taskExecutor' present: false
|
||||
Bean named 'applicationTaskExecutor' present: true
|
||||
|
||||
9
async/docs/output/visibility.txt
Normal file
9
async/docs/output/visibility.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
== @Async against final, protected and private methods ==
|
||||
|
||||
proxy class : com.ankurm.async.VisibilityService$$SpringCGLIB$$0
|
||||
isCglibProxy : true
|
||||
publicMethod() : task-4 (virtual=false)
|
||||
finalMethod() : main (virtual=false)
|
||||
protectedMethod() via the proxy : task-5 (virtual=false)
|
||||
callProtectedInternally() : main (virtual=false)
|
||||
|
||||
Reference in New Issue
Block a user