Skip to content

Support starting multiple activities from a synchronous Nexus operation handler - #3048

Open
tekkaya wants to merge 1 commit into
mainfrom
nexus-saa-bypass-links-dedup
Open

Support starting multiple activities from a synchronous Nexus operation handler#3048
tekkaya wants to merge 1 commit into
mainfrom
nexus-saa-bypass-links-dedup

Conversation

@tekkaya

@tekkaya tekkaya commented Aug 28, 2026

Copy link
Copy Markdown

What was changed

RootActivityClientInvoker.startActivity now attaches the Nexus operation's inbound links to every activity start made during the invocation, not just the one guarded call TemporalNexusClient allows. Only that guarded backing start reuses the Nexus task's request ID; every bypass-path start gets its own fresh one.

Why?

TemporalNexusClient only guards one primitive start per operation invocation, so a synchronous handler starting a second activity has to bypass it with a raw ActivityClient — and that bypass-path activity got no links at all.

sdk-python is fixing the identical gap the same way: temporalio/sdk-python#1722.

Checklist

  1. Closes: Multiple activity firing from a sync nexus handler gap found during Java SDK Test Plan execution for Nexus + SAA

  2. How was this tested:
    RootActivityClientInvokerTest (unit) and ActivityOperationLinkingTest (functional, requires a real server, gated behind SDKTestWorkflowRule.useExternalService) — see test names for what each covers.

  3. Any docs updates needed?
    No.

@tekkaya
tekkaya force-pushed the nexus-saa-bypass-links-dedup branch 2 times, most recently from eddaa54 to 507ea44 Compare August 28, 2026 20:10
@tekkaya
tekkaya marked this pull request as ready for review August 28, 2026 20:46
@tekkaya
tekkaya requested a review from a team as a code owner August 28, 2026 20:46
@tekkaya
tekkaya force-pushed the nexus-saa-bypass-links-dedup branch from 507ea44 to 9377346 Compare August 29, 2026 04:55
@tekkaya tekkaya changed the title Give bypass-path Nexus SAA starts links and request-ID dedup Give bypass-path Nexus SAA starts links, matching sdk-python's request-ID scoping Aug 29, 2026
@tekkaya tekkaya changed the title Give bypass-path Nexus SAA starts links, matching sdk-python's request-ID scoping Support starting multiple activities from a synchronous Nexus operation handler 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.
@tekkaya
tekkaya force-pushed the nexus-saa-bypass-links-dedup branch from 9377346 to 0d6c817 Compare August 29, 2026 05:02
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.

1 participant