Skip to content

Commit d9187e3

Browse files
authored
Propagate context into promise callbacks
1 parent bcb98ed commit d9187e3

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

‎temporal-sdk/src/main/java/io/temporal/internal/sync/CompletablePromiseImpl.java‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.temporal.internal.sync;
22

33
import io.temporal.failure.TemporalFailure;
4+
import io.temporal.internal.context.ContextThreadLocal;
45
import io.temporal.workflow.CancellationScope;
56
import io.temporal.workflow.CompletablePromise;
67
import io.temporal.workflow.Functions;
@@ -9,6 +10,7 @@
910
import java.time.Duration;
1011
import java.util.ArrayList;
1112
import java.util.List;
13+
import java.util.Map;
1214
import java.util.concurrent.TimeUnit;
1315
import java.util.concurrent.TimeoutException;
1416

@@ -246,7 +248,19 @@ private <U> Promise<U> then(Functions.Proc1<CompletablePromise<U>> proc) {
246248
proc.apply(resultPromise);
247249
unregisterWithRunner();
248250
} else {
249-
handlers.add(() -> proc.apply(resultPromise));
251+
// Handlers run in a callback thread created by the runner, which inherits its propagated
252+
// contexts from the workflow header rather than from the thread registering the handler.
253+
Map<String, Object> contexts = ContextThreadLocal.getCurrentContextForPropagation();
254+
handlers.add(
255+
() -> {
256+
Map<String, Object> previous = ContextThreadLocal.getCurrentContextForPropagation();
257+
ContextThreadLocal.propagateContextToCurrentThread(contexts);
258+
try {
259+
proc.apply(resultPromise);
260+
} finally {
261+
ContextThreadLocal.propagateContextToCurrentThread(previous);
262+
}
263+
});
250264
}
251265
return resultPromise;
252266
}

‎temporal-sdk/src/test/java/io/temporal/workflow/ContextPropagationTest.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ public void testThreadContextPropagation() {
104104
assertEquals("asynctesting123", result);
105105
}
106106

107+
@Test
108+
public void testPromiseCallbackContextPropagation() {
109+
Worker worker = testEnvironment.newWorker(TASK_QUEUE);
110+
worker.registerWorkflowImplementationTypes(ContextPropagationCallbackWorkflowImpl.class);
111+
testEnvironment.start();
112+
MDC.put("test", "testing123");
113+
WorkflowClient client = testEnvironment.getWorkflowClient();
114+
WorkflowOptions options = WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build();
115+
TestWorkflows.TestWorkflow1 workflow =
116+
client.newWorkflowStub(TestWorkflows.TestWorkflow1.class, options);
117+
String result = workflow.execute("input1");
118+
assertEquals("asynccallback123", result);
119+
}
120+
107121
@Test
108122
public void testActivityContextPropagation() {
109123
Worker worker = testEnvironment.newWorker(TASK_QUEUE);
@@ -339,6 +353,25 @@ private String async() {
339353
}
340354
}
341355

356+
public static class ContextPropagationCallbackWorkflowImpl
357+
implements TestWorkflows.TestWorkflow1 {
358+
359+
@Override
360+
public String execute(String input) {
361+
Promise<String> asyncPromise = Async.function(this::async);
362+
// The callback must see the context of the thread that registered it, not the context the
363+
// thread completing the promise happens to carry.
364+
MDC.put("test", "callback123");
365+
Promise<String> callback = asyncPromise.thenApply(result -> result + MDC.get("test"));
366+
MDC.put("test", "testing123");
367+
return callback.get();
368+
}
369+
370+
private String async() {
371+
return "async";
372+
}
373+
}
374+
342375
public static class ContextActivityImpl implements TestActivities.TestActivity1 {
343376
@Override
344377
public String execute(String input) {

0 commit comments

Comments
 (0)