Skip to content

Commit 7daf427

Browse files
committed
Progress
1 parent 86fdfff commit 7daf427

4 files changed

Lines changed: 432 additions & 129 deletions

File tree

temporal-sdk/src/main/java/io/temporal/internal/client/RootWorkflowClientInvoker.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public WorkflowSignalOutput signal(WorkflowSignalInput input) {
139139
// Server >=1.31 with EnableCHASMSignalBacklinks returns a backlink pointing at the signal
140140
// event; older servers leave it unset. Propagate when present.
141141
if (CurrentNexusOperationContext.isNexusContext() && response.hasLink()) {
142-
CurrentNexusOperationContext.get().setSignalWorkflowResponseLink(response.getLink());
142+
CurrentNexusOperationContext.get().addSignalWorkflowResponseLink(response.getLink());
143143
}
144144
return new WorkflowSignalOutput();
145145
}
@@ -178,7 +178,7 @@ public WorkflowSignalWithStartOutput signalWithStart(WorkflowSignalWithStartInpu
178178
// Server >=1.31 with EnableCHASMSignalBacklinks returns a backlink pointing at the signal
179179
// event; older servers leave it unset. Propagate when present.
180180
if (CurrentNexusOperationContext.isNexusContext() && response.hasSignalLink()) {
181-
CurrentNexusOperationContext.get().setSignalWorkflowResponseLink(response.getSignalLink());
181+
CurrentNexusOperationContext.get().addSignalWorkflowResponseLink(response.getSignalLink());
182182
}
183183
// TODO currently SignalWithStartWorkflowExecutionResponse doesn't have eagerWorkflowTask.
184184
// We should wire it when it's implemented server-side.

temporal-sdk/src/main/java/io/temporal/internal/nexus/InternalNexusOperationContext.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.temporal.common.interceptors.NexusOperationOutboundCallsInterceptor;
77
import io.temporal.nexus.NexusOperationContext;
88
import io.temporal.nexus.NexusOperationInfo;
9+
import java.util.ArrayList;
910
import java.util.Collections;
1011
import java.util.List;
1112

@@ -21,12 +22,17 @@ public class InternalNexusOperationContext {
2122
// workflow client (signal, signalWithStart) can attach them to outgoing requests via
2223
// SignalWorkflowExecutionRequest.links, matching the Go SDK's NexusOperationLinksKey ctx value.
2324
private List<Link> nexusOperationLinks = Collections.emptyList();
24-
// Backlink returned by SignalWorkflowExecutionResponse.link /
25-
// SignalWithStartWorkflowExecutionResponse.signal_link.
26-
// Populated by the workflow client and consumed by the task handler when building
27-
// StartOperationResponse, so the caller workflow gets a link pointing at the signal event on
28-
// the callee.
29-
private Link signalWorkflowResponseLink;
25+
// Backlinks returned by SignalWorkflowExecutionResponse.link /
26+
// SignalWithStartWorkflowExecutionResponse.signal_link. One entry per signal RPC issued from
27+
// within the Nexus operation handler. Drained by the task handler when building
28+
// StartOperationResponse so every signal the handler issues gets a corresponding link on the
29+
// caller workflow's history event.
30+
//
31+
// NOTE: this context is only safe for use from the single thread that runs the operation
32+
// handler (the Nexus task executor's thread). Handlers that spawn their own threads to issue
33+
// signals will not see the thread-local context, so the links from those signals will not
34+
// propagate.
35+
private final List<Link> signalWorkflowResponseLinks = new ArrayList<>();
3036

3137
public InternalNexusOperationContext(
3238
String namespace,
@@ -93,12 +99,20 @@ public List<Link> getNexusOperationLinks() {
9399
return nexusOperationLinks;
94100
}
95101

96-
public void setSignalWorkflowResponseLink(Link link) {
97-
this.signalWorkflowResponseLink = link;
102+
/**
103+
* Append a backlink returned by a signal-class RPC (signal or signalWithStart). Each signal the
104+
* operation handler issues should add one entry; the task handler drains the list when building
105+
* the operation's StartOperationResponse.
106+
*/
107+
public void addSignalWorkflowResponseLink(Link link) {
108+
if (link != null) {
109+
this.signalWorkflowResponseLinks.add(link);
110+
}
98111
}
99112

100-
public Link getSignalWorkflowResponseLink() {
101-
return signalWorkflowResponseLink;
113+
/** Backlinks from every signal RPC issued by the handler. Never null; may be empty. */
114+
public List<Link> getSignalWorkflowResponseLinks() {
115+
return signalWorkflowResponseLinks;
102116
}
103117

104118
private class NexusOperationContextImpl implements NexusOperationContext {

temporal-sdk/src/main/java/io/temporal/internal/nexus/NexusTaskHandlerImpl.java

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,20 @@ private StartOperationResponse handleStartOperation(
301301
"Invalid link URL: " + link.getUrl(),
302302
e);
303303
}
304+
// LinkConverter only returns a WorkflowEvent-shaped common.v1.Link; nexus links of
305+
// other shapes (e.g. non-temporal URLs) come back null and are intentionally not
306+
// forwarded onto SignalWorkflowExecutionRequest.links, which requires the
307+
// WorkflowEvent variant. Log so a debugging session can see what was dropped.
304308
io.temporal.api.common.v1.Link commonLink =
305309
LinkConverter.nexusLinkToWorkflowEvent(link);
306310
if (commonLink != null) {
307311
inboundCommonLinks.add(commonLink);
312+
} else {
313+
log.warn(
314+
"Dropping inbound Nexus link from outbound signal propagation: type='{}',"
315+
+ " url='{}' (not a parseable temporal WorkflowEvent link)",
316+
link.getType(),
317+
link.getUrl());
308318
}
309319
});
310320
CurrentNexusOperationContext.get().setNexusOperationLinks(inboundCommonLinks);
@@ -318,27 +328,30 @@ private StartOperationResponse handleStartOperation(
318328
try {
319329
OperationStartResult<HandlerResultContent> result =
320330
startOperation(context, operationStartDetails.build(), input.build());
321-
// If a signal RPC issued by the handler returned a backlink, propagate it to the caller
322-
// so the caller workflow's history event links to the signal event on the callee. Same
323-
// backlink applies to both sync and async response variants.
324-
io.temporal.api.nexus.v1.Link signalBacklink = null;
325-
io.temporal.api.common.v1.Link signalResponseLink =
326-
CurrentNexusOperationContext.get().getSignalWorkflowResponseLink();
327-
if (signalResponseLink != null && signalResponseLink.hasWorkflowEvent()) {
328-
signalBacklink =
331+
// If signal/signalWithStart RPCs issued by the handler returned backlinks, propagate
332+
// them to the caller so the caller workflow's history event links to each signal event
333+
// on the callee. Same set of backlinks applies to both sync and async response variants.
334+
List<io.temporal.api.nexus.v1.Link> signalBacklinks = new ArrayList<>();
335+
for (io.temporal.api.common.v1.Link signalResponseLink :
336+
CurrentNexusOperationContext.get().getSignalWorkflowResponseLinks()) {
337+
if (!signalResponseLink.hasWorkflowEvent()) {
338+
continue;
339+
}
340+
io.temporal.api.nexus.v1.Link converted =
329341
LinkConverter.workflowEventToNexusLink(signalResponseLink.getWorkflowEvent());
342+
if (converted != null) {
343+
signalBacklinks.add(converted);
344+
}
330345
}
331346

332347
if (result.isSync()) {
333-
StartOperationResponse.Sync.Builder syncBuilder =
348+
startResponseBuilder.setSyncSuccess(
334349
StartOperationResponse.Sync.newBuilder()
335-
.setPayload(Payload.parseFrom(result.getSyncResult().getDataBytes()));
336-
if (signalBacklink != null) {
337-
syncBuilder.addLinks(signalBacklink);
338-
}
339-
startResponseBuilder.setSyncSuccess(syncBuilder.build());
350+
.setPayload(Payload.parseFrom(result.getSyncResult().getDataBytes()))
351+
.addAllLinks(signalBacklinks)
352+
.build());
340353
} else {
341-
StartOperationResponse.Async.Builder asyncBuilder =
354+
startResponseBuilder.setAsyncSuccess(
342355
StartOperationResponse.Async.newBuilder()
343356
.setOperationId(result.getAsyncOperationToken())
344357
.setOperationToken(result.getAsyncOperationToken())
@@ -350,11 +363,9 @@ private StartOperationResponse handleStartOperation(
350363
.setType(link.getType())
351364
.setUrl(link.getUri().toString())
352365
.build())
353-
.collect(Collectors.toList()));
354-
if (signalBacklink != null) {
355-
asyncBuilder.addLinks(signalBacklink);
356-
}
357-
startResponseBuilder.setAsyncSuccess(asyncBuilder.build());
366+
.collect(Collectors.toList()))
367+
.addAllLinks(signalBacklinks)
368+
.build());
358369
}
359370
} catch (OperationException e) {
360371
throw e;

0 commit comments

Comments
 (0)