|
20 | 20 |
|
21 | 21 | import groovy.lang.Closure; |
22 | 22 |
|
| 23 | +import org.apache.groovy.runtime.async.ScopedLocal; |
| 24 | + |
23 | 25 | import java.util.Collections; |
24 | 26 | import java.util.LinkedHashMap; |
25 | 27 | import java.util.Map; |
|
50 | 52 | * {@code null} as a value removes the key. |
51 | 53 | * |
52 | 54 | * <h2>Thread safety</h2> |
53 | | - * Each thread owns its own {@code AsyncContext} instance via a |
54 | | - * {@link ThreadLocal}. Instance methods ({@link #put}, {@link #get}, |
| 55 | + * Each thread owns its own {@code AsyncContext} instance via |
| 56 | + * {@link ScopedLocal}. On JDK 25+, this leverages |
| 57 | + * {@code ScopedValue} for optimal virtual-thread performance; |
| 58 | + * on earlier JDKs it falls back to {@code ThreadLocal}. |
| 59 | + * Instance methods ({@link #put}, {@link #get}, |
55 | 60 | * {@link #remove}) are <em>not</em> synchronized — they are only called |
56 | 61 | * on the owning thread. Static methods ({@link #withSnapshot}, |
57 | | - * {@link #capture}) follow a save-and-restore pattern protected by |
58 | | - * {@code try/finally}, ensuring the previous context is always reinstated |
59 | | - * even if the action throws. This design prevents stale context from |
60 | | - * leaking to thread-pool threads between task executions. |
| 62 | + * {@link #capture}) use a scope-based binding pattern, ensuring the |
| 63 | + * previous context is always reinstated even if the action throws. |
| 64 | + * This design prevents stale context from leaking to thread-pool |
| 65 | + * threads between task executions. |
61 | 66 | * |
62 | 67 | * @since 6.0.0 |
63 | 68 | */ |
64 | 69 | public final class AsyncContext { |
65 | 70 |
|
66 | | - private static final ThreadLocal<AsyncContext> CURRENT = |
67 | | - ThreadLocal.withInitial(AsyncContext::new); |
| 71 | + private static final ScopedLocal<AsyncContext> CURRENT = |
| 72 | + ScopedLocal.withInitial(AsyncContext::new); |
68 | 73 |
|
69 | 74 | private final Map<String, Object> values; |
70 | 75 |
|
@@ -120,13 +125,8 @@ public static Snapshot capture() { |
120 | 125 | public static <T> T withSnapshot(Snapshot snapshot, Supplier<T> supplier) { |
121 | 126 | Objects.requireNonNull(snapshot, "snapshot must not be null"); |
122 | 127 | Objects.requireNonNull(supplier, "supplier must not be null"); |
123 | | - AsyncContext previous = CURRENT.get(); |
124 | | - CURRENT.set(new AsyncContext(new LinkedHashMap<>(snapshot.values), true)); |
125 | | - try { |
126 | | - return supplier.get(); |
127 | | - } finally { |
128 | | - CURRENT.set(previous); |
129 | | - } |
| 128 | + AsyncContext restored = new AsyncContext(new LinkedHashMap<>(snapshot.values), true); |
| 129 | + return CURRENT.where(restored, supplier); |
130 | 130 | } |
131 | 131 |
|
132 | 132 | /** |
|
0 commit comments