-
Notifications
You must be signed in to change notification settings - Fork 0
Context Propagation
In modern Java applications with virtual threads, thread pools, and async frameworks, ThreadLocal values are lost when work crosses thread boundaries:
MDC.put("request.id", "abc-123");
executor.submit(() -> {
MDC.get("request.id"); // null! Context is lost.
});ScopeFlow solves this with transparent context propagation.
// Runnable
Runnable wrapped = scopeFlow.wrap(() -> {
log.info("Context available here!");
});
// Callable
Callable<String> wrapped = scopeFlow.wrap(() -> {
return scopeFlow.currentContext().get("request.id").orElse("?");
});
// Supplier
Supplier<String> wrapped = scopeFlow.wrap((Supplier<String>) () -> {
return "Result with context";
});// Wrap any Executor — all submitted tasks get context automatically
Executor wrapped = scopeFlow.wrapExecutor(Executors.newVirtualThreadPerTaskExecutor());
wrapped.execute(() -> {
// Context is here!
});
// Wrap ExecutorService — all submit/invoke methods propagate context
ExecutorService wrapped = scopeFlow.wrapExecutorService(
Executors.newFixedThreadPool(4));
Future<String> future = wrapped.submit(() -> {
return scopeFlow.currentContext().get("request.id").orElse("missing");
});Thread A (opener) Thread B (executor)
───────────────── ────────────────────
scopeFlow.open("req", values)
│
scopeFlow.wrap(task)
├── snapshot = capture()
├── context frozen
│ task.run()
│ ├── scope = snapshot.open()
│ ├── propagators fired (MDC set)
│ ├── original task executes
│ ├── scope.close()
│ └── MDC cleaned up
ScopeFlow is designed specifically for Java 21+ virtual threads:
Each virtual thread gets its own independent scope stack via ThreadLocal. This means:
- No shared mutable state
- No synchronization needed
- No lock contention
- Scales to millions of virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var wrapped = scopeFlow.wrapExecutorService(executor);
IntStream.range(0, 10_000)
.parallel()
.forEach(i -> wrapped.submit(() -> {
try (Scope scope = scopeFlow.open("request",
Map.of("request.id", "req-" + i))) {
// Each virtual thread has its own isolated context
// No cross-contamination between requests
assertThat(scopeFlow.currentContext().get("request.id"))
.hasValue("req-" + i);
}
}));
}InheritableThreadLocal has issues with virtual threads:
- Stale values: Child threads inherit the parent's value at creation time, not at execution time
- Memory leaks: Platform thread pools reuse threads, so inherited values may persist
- Unpredictable: With virtual threads, the carrier thread may change, making ITL unreliable
ScopeFlow uses explicit snapshot-based propagation, which is:
- Predictable: Values captured at wrap-time, restored at execution-time
- Clean: Values always cleaned up via scope lifecycle
- Safe: No memory leaks from stale ThreadLocal values
For custom propagation scenarios:
// Capture current context
ScopeSnapshot snapshot = scopeFlow.capture();
// Use wherever you need the context restored
CompletableFuture.supplyAsync(() -> {
try (Scope restored = snapshot.open("async.compute")) {
// All parent context values are available
return compute();
}
}, executor);
// Snapshots are immutable and can be stored/shared
cache.put("context-" + requestId, snapshot);ScopeSnapshot snapshot = scopeFlow.capture();
CompletableFuture
.supplyAsync(scopeFlow.wrap(() -> fetchData()), executor)
.thenApplyAsync(scopeFlow.wrap(data -> transform(data)), executor)
.thenAcceptAsync(scopeFlow.wrap(result -> save(result)), executor)
.join();With the Spring Boot starter, @Async context propagation is automatic:
@Service
public class OrderService {
@Async
public CompletableFuture<String> processAsync(String orderId) {
// ScopeFlow context is automatically available here
// MDC has request.id from the calling thread
log.info("Processing order {} async", orderId);
return CompletableFuture.completedFuture("OK");
}
}No additional configuration needed. The ScopeFlowTaskDecorator is auto-applied to all Spring-managed task executors.
ScopeFlow • Distributed Context & Tracing Propagation • Licensed under Apache-2.0