Support starting multiple activities from a synchronous Nexus operation handler - #3048
Open
tekkaya wants to merge 1 commit into
Open
Support starting multiple activities from a synchronous Nexus operation handler#3048tekkaya wants to merge 1 commit into
tekkaya wants to merge 1 commit into
Conversation
tekkaya
force-pushed
the
nexus-saa-bypass-links-dedup
branch
2 times, most recently
from
August 28, 2026 20:10
eddaa54 to
507ea44
Compare
tekkaya
marked this pull request as ready for review
August 28, 2026 20:46
tekkaya
force-pushed
the
nexus-saa-bypass-links-dedup
branch
from
August 29, 2026 04:55
507ea44 to
9377346
Compare
…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.
tekkaya
force-pushed
the
nexus-saa-bypass-links-dedup
branch
from
August 29, 2026 05:02
9377346 to
0d6c817
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was changed
RootActivityClientInvoker.startActivitynow attaches the Nexus operation's inbound links to every activity start made during the invocation, not just the one guarded callTemporalNexusClientallows. Only that guarded backing start reuses the Nexus task's request ID; every bypass-path start gets its own fresh one.Why?
TemporalNexusClientonly guards one primitive start per operation invocation, so a synchronous handler starting a second activity has to bypass it with a rawActivityClient— and that bypass-path activity got no links at all.sdk-pythonis fixing the identical gap the same way: temporalio/sdk-python#1722.Checklist
Closes: Multiple activity firing from a sync nexus handler gap found during Java SDK Test Plan execution for Nexus + SAA
How was this tested:
RootActivityClientInvokerTest(unit) andActivityOperationLinkingTest(functional, requires a real server, gated behindSDKTestWorkflowRule.useExternalService) — see test names for what each covers.Any docs updates needed?
No.