Skip to content

Commit 5bd5d14

Browse files
committed
Addressing PR comments
1 parent b587b25 commit 5bd5d14

3 files changed

Lines changed: 78 additions & 6 deletions

File tree

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.grpc.Status;
1111
import io.grpc.StatusRuntimeException;
1212
import io.temporal.api.common.v1.*;
13+
import io.temporal.api.enums.v1.EventType;
1314
import io.temporal.api.enums.v1.UpdateWorkflowExecutionLifecycleStage;
1415
import io.temporal.api.enums.v1.WorkflowExecutionStatus;
1516
import io.temporal.api.errordetails.v1.MultiOperationExecutionFailure;
@@ -105,7 +106,26 @@ public WorkflowStartOutput start(WorkflowStartInput input) {
105106
if (CurrentNexusOperationContext.isNexusContext()) {
106107
// Auto-capture the start-workflow backlink so the task handler drains it onto the
107108
// StartOperationResponse, the same path used for signal/signalWithStart responses.
108-
CurrentNexusOperationContext.get().addBacklink(response.getLink());
109+
if (response.hasLink()) {
110+
CurrentNexusOperationContext.get().addBacklink(response.getLink());
111+
} else {
112+
// Older servers (pre-1.31) don't return a link on the start response. Fabricate one
113+
// pointing at the started workflow's WorkflowExecutionStarted event so the caller still
114+
// gets a backlink.
115+
CurrentNexusOperationContext.get()
116+
.addBacklink(
117+
Link.newBuilder()
118+
.setWorkflowEvent(
119+
Link.WorkflowEvent.newBuilder()
120+
.setNamespace(clientOptions.getNamespace())
121+
.setWorkflowId(execution.getWorkflowId())
122+
.setRunId(execution.getRunId())
123+
.setEventRef(
124+
Link.WorkflowEvent.EventReference.newBuilder()
125+
.setEventType(
126+
EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED)))
127+
.build());
128+
}
109129
}
110130
return new WorkflowStartOutput(execution);
111131
}
@@ -171,9 +191,6 @@ public WorkflowSignalWithStartOutput signalWithStart(WorkflowSignalWithStartInpu
171191
boolean inNexusContext = CurrentNexusOperationContext.isNexusContext();
172192
if (inNexusContext) {
173193
requestBuilder.addAllLinks(CurrentNexusOperationContext.get().getNexusOperationLinks());
174-
} else {
175-
log.debug(
176-
"signalWithStart RPC issued outside a Nexus operation context; no link propagation");
177194
}
178195
SignalWithStartWorkflowExecutionRequest request = requestBuilder.build();
179196
SignalWithStartWorkflowExecutionResponse response = genericClient.signalWithStart(request);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ public void setNexusOperationLinks(List<Link> links) {
9292

9393
/**
9494
* Append a backlink returned by an outbound RPC the operation handler issued (e.g. signal,
95-
* signalWithStart, etc). The task handler drains the list when building the
96-
* operation's StartOperationResponse.
95+
* signalWithStart, etc). The task handler drains the list when building the operation's
96+
* StartOperationResponse.
9797
*/
9898
public void addBacklink(Link link) {
9999
if (link != null) {

temporal-sdk/src/test/java/io/temporal/internal/client/RootWorkflowClientInvokerLinkPropagationTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.temporal.api.workflowservice.v1.SignalWithStartWorkflowExecutionResponse;
1414
import io.temporal.api.workflowservice.v1.SignalWorkflowExecutionRequest;
1515
import io.temporal.api.workflowservice.v1.SignalWorkflowExecutionResponse;
16+
import io.temporal.api.workflowservice.v1.StartWorkflowExecutionRequest;
17+
import io.temporal.api.workflowservice.v1.StartWorkflowExecutionResponse;
1618
import io.temporal.client.WorkflowClient;
1719
import io.temporal.client.WorkflowClientOptions;
1820
import io.temporal.client.WorkflowOptions;
@@ -232,6 +234,51 @@ public void mixedSignalAndSignalWithStartAccumulateAllBacklinks() {
232234
nexusCtx.getBacklinks());
233235
}
234236

237+
/**
238+
* Flag-enabled server: the start response carries a backlink; the SDK captures it verbatim onto
239+
* the operation context (used by the WorkflowRunOperation async path).
240+
*/
241+
@Test
242+
public void startUsesServerStartLinkWhenPresent() {
243+
Link serverLink =
244+
workflowEventLink(
245+
WORKFLOW_ID, "target-run", EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED);
246+
when(genericClient.start(any(StartWorkflowExecutionRequest.class)))
247+
.thenReturn(
248+
StartWorkflowExecutionResponse.newBuilder()
249+
.setRunId("target-run")
250+
.setLink(serverLink)
251+
.build());
252+
253+
invoker.start(newStartInput());
254+
255+
List<Link> captured = nexusCtx.getBacklinks();
256+
Assert.assertEquals("expected the server-provided start link", 1, captured.size());
257+
Assert.assertEquals(serverLink, captured.get(0));
258+
}
259+
260+
/**
261+
* Older server (pre-1.31): the start response has no link. The SDK must fabricate a backlink
262+
* pointing at the started workflow's WorkflowExecutionStarted event so the caller still links to
263+
* the callee.
264+
*/
265+
@Test
266+
public void startFabricatesStartLinkWhenServerOmitsIt() {
267+
when(genericClient.start(any(StartWorkflowExecutionRequest.class)))
268+
.thenReturn(StartWorkflowExecutionResponse.newBuilder().setRunId("target-run").build());
269+
270+
invoker.start(newStartInput());
271+
272+
List<Link> captured = nexusCtx.getBacklinks();
273+
Assert.assertEquals("expected one fabricated backlink", 1, captured.size());
274+
Link.WorkflowEvent we = captured.get(0).getWorkflowEvent();
275+
Assert.assertEquals(NAMESPACE, we.getNamespace());
276+
Assert.assertEquals(WORKFLOW_ID, we.getWorkflowId());
277+
Assert.assertEquals("target-run", we.getRunId());
278+
Assert.assertEquals(
279+
EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, we.getEventRef().getEventType());
280+
}
281+
235282
// ── helpers ──────────────────────────────────────────────────────────────────────────────
236283

237284
private static WorkflowSignalInput newSignalInput() {
@@ -251,6 +298,14 @@ private static WorkflowSignalWithStartInput newSignalWithStartInput() {
251298
startInput, "test-signal", new Object[] {"signal-payload"});
252299
}
253300

301+
private static WorkflowStartInput newStartInput() {
302+
// Disable eager execution so start() takes the plain RPC path (no local worker dispatch).
303+
WorkflowOptions options =
304+
WorkflowOptions.newBuilder().setTaskQueue("tq").setDisableEagerExecution(true).build();
305+
return new WorkflowStartInput(
306+
WORKFLOW_ID, "TestWorkflow", Header.empty(), new Object[] {}, options);
307+
}
308+
254309
private static Link workflowEventLink(String workflowId, String runId, EventType eventType) {
255310
return Link.newBuilder()
256311
.setWorkflowEvent(

0 commit comments

Comments
 (0)