Skip to content

Only send request ID when in a backing nexus context when starting a SAA - #1722

Closed
VegetarianOrc wants to merge 3 commits into
mainfrom
amazzeo/nexus-saa-request-id
Closed

Only send request ID when in a backing nexus context when starting a SAA#1722
VegetarianOrc wants to merge 3 commits into
mainfrom
amazzeo/nexus-saa-request-id

Conversation

@VegetarianOrc

Copy link
Copy Markdown
Contributor

What was changed

Only send request ID when in a backing nexus context when starting a SAA.

Why?

Sending request IDs for every SAA prevents starting multiple from the Nexus handler.

Checklist

  1. How was this tested:
  • Tests updated

@VegetarianOrc
VegetarianOrc requested a review from a team as a code owner August 4, 2026 18:43

@THardy98 THardy98 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a test for this case:

Sending request IDs for every SAA prevents starting multiple from the Nexus handler.

That should fail prior to this change and fixed post-change

@VegetarianOrc
VegetarianOrc force-pushed the amazzeo/nexus-saa-request-id branch from ec84128 to a7c37da Compare August 20, 2026 20:17
@VegetarianOrc

Copy link
Copy Markdown
Contributor Author

Could we add a test for this case:

Sending request IDs for every SAA prevents starting multiple from the Nexus handler.

That should fail prior to this change and fixed post-change

Added!

tekkaya added a commit to temporalio/sdk-java that referenced this pull request Aug 29, 2026
…t-ID scoping

TemporalNexusClient guards its own activity/workflow/update start to at most
one per operation invocation, so a synchronous Nexus operation handler that
starts two or more activities inline has to bypass it and use a raw
ActivityClient obtained from Nexus.getOperationContext() instead. That bypass
path got no request links, because RootActivityClientInvoker.startActivity
derived link attachment, on-conflict dedup, and completion-callback
attachment all from the same NexusOperationMetadata, which only the guarded
TemporalNexusClient call ever sets.

Every activity start made during the invocation now gets the inbound Nexus
request's links, regardless of which client object issued it.
NexusOperationMetadata keeps its narrow, one-shot scope and remains the only
thing that can attach a completion callback or a reused request ID -- an
earlier attempt at this fix also gave bypass-path activity starts the
invocation's ambient request ID (first raw, then via a per-call derived
counter to avoid collisions between two starts sharing an activity ID), but
both versions were dropped: correctly identifying a start as "the same call,
redelivered" vs. "a new call that happens to land on the same ordinal
position" requires assuming the handler reissues an identical sequence of
calls on every retry, an assumption the SDK has no way to verify. Under
non-determinism this can silently fail to dedup a genuinely redelivered
start, reintroducing the very duplicate-execution bug the reuse was meant to
prevent. sdk-python reached the same conclusion independently
(temporalio/sdk-python#1722, "Only send request ID when in a backing nexus
context when starting a SAA") and made the same choice: only the one guarded
backing start reuses the Nexus task's request ID; every bypass-path start
gets its own fresh, uncorrelated one. This change aligns Java with that.

ActivityOperationLinkingTest (functional, requires a real server) drives a
synchronous handler that starts two bypass-path activities and asserts both
the forward link (each activity's own ActivityExecutionInfo) and the
backward links (both activities' completions landing on the caller's single
NexusOperationCompleted event), the same way SignalOperationLinkingTest
already does for signals. It also covers the same-activity-ID restart
pattern from sdk-python's regression test: starting a fresh run with an
already-used activity ID within one invocation must not resolve to the
stale, already-completed run.

RootActivityClientInvokerTest covers the ambient-links-but-fresh-request-ID
case, the outside-Nexus-context case, and two bypass-path starts for the
same activity ID getting distinct request IDs, at the unit level.
tekkaya added a commit to temporalio/sdk-java that referenced this pull request Aug 29, 2026
…on handler

TemporalNexusClient guards its own activity/workflow/update start to at most
one per operation invocation, so a synchronous Nexus operation handler that
starts two or more activities inline has to bypass it and use a raw
ActivityClient obtained from Nexus.getOperationContext() instead. That bypass
path got no request links, because RootActivityClientInvoker.startActivity
derived link attachment, on-conflict dedup, and completion-callback
attachment all from the same NexusOperationMetadata, which only the guarded
TemporalNexusClient call ever sets.

Every activity start made during the invocation now gets the inbound Nexus
request's links, regardless of which client object issued it.
NexusOperationMetadata keeps its narrow, one-shot scope and remains the only
thing that can attach a completion callback or a reused request ID -- an
earlier attempt at this fix also gave bypass-path activity starts the
invocation's ambient request ID (first raw, then via a per-call derived
counter to avoid collisions between two starts sharing an activity ID), but
both versions were dropped: correctly identifying a start as "the same call,
redelivered" vs. "a new call that happens to land on the same ordinal
position" requires assuming the handler reissues an identical sequence of
calls on every retry, an assumption the SDK has no way to verify. Under
non-determinism this can silently fail to dedup a genuinely redelivered
start, reintroducing the very duplicate-execution bug the reuse was meant to
prevent. sdk-python reached the same conclusion independently
(temporalio/sdk-python#1722, "Only send request ID when in a backing nexus
context when starting a SAA") and made the same choice: only the one guarded
backing start reuses the Nexus task's request ID; every bypass-path start
gets its own fresh, uncorrelated one. This change aligns Java with that.

ActivityOperationLinkingTest (functional, requires a real server) drives a
synchronous handler that starts two bypass-path activities and asserts both
the forward link (each activity's own ActivityExecutionInfo) and the
backward links (both activities' completions landing on the caller's single
NexusOperationCompleted event), the same way SignalOperationLinkingTest
already does for signals. It also covers the same-activity-ID restart
pattern from sdk-python's regression test: starting a fresh run with an
already-used activity ID within one invocation must not resolve to the
stale, already-completed run.

RootActivityClientInvokerTest covers the ambient-links-but-fresh-request-ID
case, the outside-Nexus-context case, and two bypass-path starts for the
same activity ID getting distinct request IDs, at the unit level.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants