rzo1 commented on code in PR #2940:
URL: https://github.com/apache/tomee/pull/2940#discussion_r3981971755


##########
container/openejb-core/src/main/java/org/apache/openejb/core/ThreadContext.java:
##########
@@ -151,13 +152,98 @@ public ThreadContext(final BeanContext beanContext, final 
Object primaryKey, fin
         this.currentOperation = operation;
     }
 
+    /**
+     * Copy constructor. Must be called on the thread that owns 
<code>that</code>, since a
+     * ThreadContext is confined to its thread. Use {@link #capture()} to pass 
a context to
+     * another thread.
+     */
     public ThreadContext(final ThreadContext that) {
         this.beanContext = that.beanContext;
         this.primaryKey = that.primaryKey;
-        this.data.putAll(that.data);
+        synchronized (that.data) {
+            this.data.putAll(that.data);
+        }
         this.oldClassLoader = that.oldClassLoader;
     }
 
+    /**
+     * Returns an immutable copy of the calling thread's context, which may be 
passed to other
+     * threads. Must be called on the thread that owns the context.
+     *
+     * @return the capture, or <code>null</code> if no context is entered on 
this thread
+     */
+    public static Capture capture() {
+        final ThreadContext current = threadStorage.get();
+        return current == null ? null : new Capture(current);
+    }
+
+    /**
+     * Immutable copy of the state a {@link ThreadContext} propagates: bean 
context, primary key and
+     * context data. Per-thread state such as the class loader to restore, the 
entered flag and the
+     * current operation is not included.
+     * <p>
+     * A capture may be applied to any number of threads, including 
concurrently.
+     * {@link #newThreadContext()} returns a separate mutable {@link 
ThreadContext} for each caller,
+     * since {@link ThreadContext#enter(ThreadContext)} modifies its argument 
and fails if that
+     * context was already entered.
+     */
+    public static final class Capture {
+
+        /**
+         * Context data tied to the invocation a capture is taken from, listed 
by class name to avoid
+         * a dependency on the types. It is not propagated:
+         * <ul>
+         *   <li><code>InvocationContext</code> is part of the interceptor 
chain the calling thread is
+         *       still in. It is single use, and {@link 
BaseContext#getContextData()} exposes its
+         *       unsynchronized map to application code.</li>
+         *   <li><code>DestroyContext</code> references the captured context 
and would keep it
+         *       reachable for the lifetime of the capture. A new one is 
created when the context is
+         *       entered on another thread.</li>
+         * </ul>
+         */
+        private static final Set<String> NON_PROPAGATED = Set.of(
+            "jakarta.interceptor.InvocationContext",
+            
"org.apache.openejb.cdi.RequestScopedThreadContextListener$DestroyContext");
+
+        private final BeanContext beanContext;
+        private final Object primaryKey;
+        private final Map<Class, Object> data;
+
+        private Capture(final ThreadContext that) {
+            this.beanContext = that.beanContext;
+            this.primaryKey = that.primaryKey;
+
+            final Map<Class, Object> copy = new HashMap<>();
+            synchronized (that.data) {
+                for (final Map.Entry<Class, Object> entry : 
that.data.entrySet()) {
+                    if (!NON_PROPAGATED.contains(entry.getKey().getName())) {
+                        copy.put(entry.getKey(), entry.getValue());
+                    }
+                }

Review Comment:
   Thanks, applied in a728ba18a6.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to