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:
57
async/docs/06-context-propagation.md
Normal file
57
async/docs/06-context-propagation.md
Normal file
@@ -0,0 +1,57 @@
|
||||
prev: [Pool sizing](05-pool-sizing.md) · [README](../README.md) · next: [Which executor runs it](07-which-executor-runs-it.md)
|
||||
|
||||
# 6. `spring.task.execution.propagate-context` — new in Boot 4.1
|
||||
|
||||
Handing a `Callable` to another thread leaves every `ThreadLocal` behind. That is why the async
|
||||
thread in a stock application sees no MDC entries, no `RequestAttributes`, and no
|
||||
`SecurityContext` — and why "the trace id disappears in the async part" is such a common report.
|
||||
|
||||
Spring Boot 4.1.0 added a property for it:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
task:
|
||||
execution:
|
||||
propagate-context: true
|
||||
```
|
||||
|
||||
It decorates the auto-configured executor with `ContextPropagatingTaskDecorator`, which takes a
|
||||
snapshot of everything registered with micrometer's `ContextRegistry` at submission time and
|
||||
restores it around the task on the executing thread.
|
||||
|
||||
## How new is it, exactly
|
||||
|
||||
New in **4.1.0**. It is absent from `TaskExecutionProperties` in 4.0.8 and in 3.5.9 — checked by
|
||||
extracting `TaskExecutionProperties.class` from each `spring-boot-autoconfigure` jar and running
|
||||
`javap` over it, rather than by reading release notes. The accessor pair
|
||||
`getPropagateContext()`/`setPropagateContext(boolean)` appears first in the 4.1.0 jar.
|
||||
|
||||
Note also what it is *not*: there is no matching `spring.task.scheduling.propagate-context`. The
|
||||
scheduler is not covered.
|
||||
|
||||
## Measured
|
||||
|
||||
`RequestId` in this module is a `ThreadLocal<String>` with a `ThreadLocalAccessor` registered
|
||||
against `ContextRegistry`. Two tests differ only in the property:
|
||||
|
||||
- `ContextNotPropagatedTest` — the async thread reads `null`.
|
||||
- `ContextPropagatedTest` — the async thread reads `req-4711`
|
||||
([output](output/context-propagation.txt)).
|
||||
|
||||
## The three things that make it silently do nothing
|
||||
|
||||
1. **micrometer's `context-propagation` is not on the classpath.** The property is still bound
|
||||
and still accepted; nothing decorates the executor. This module declares
|
||||
`io.micrometer:context-propagation` explicitly for that reason.
|
||||
2. **Nothing registered an accessor.** The snapshot only carries what `ContextRegistry` knows
|
||||
about. Libraries that ship accessors (Micrometer tracing, Reactor) register their own; a
|
||||
`ThreadLocal` of your own does not register itself.
|
||||
3. **The executor is not the auto-configured one.** The decorator is applied by Boot's
|
||||
auto-configuration. Declare your own `Executor` bean (chapter 7) and you have opted out of
|
||||
the property along with everything else Boot was doing.
|
||||
|
||||
`SecurityContext` propagation is a related but separate mechanism, with its own set of ways to
|
||||
get it wrong; that is covered in
|
||||
[Virtual Threads and SecurityContext Propagation](https://ankurm.com/spring-security-virtual-threads-context-propagation/).
|
||||
|
||||
next: [Which executor runs it](07-which-executor-runs-it.md)
|
||||
Reference in New Issue
Block a user