Skip to content

💥 Schedule async markers like regular threads - #3110

Merged
Quinn-With-Two-Ns merged 5 commits into
temporalio:mainfrom
Quinn-With-Two-Ns:schedule-async-marker
Oct 3, 2026
Merged

Quinn-With-Two-Ns merged 5 commits into
temporalio:mainfrom
Quinn-With-Two-Ns:schedule-async-marker

Conversation

@Quinn-With-Two-Ns

@Quinn-With-Two-Ns Quinn-With-Two-Ns commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What was changed

Added SCHEDULE_ASYNC_STUB_OPERATIONS, which queues detected Async stub calls in command order without allocating a workflow thread. The flag remains disabled by default. Previously async stub were executed inline.

Why?

Changing an Async call between a stub method reference and a lambda can change command order and break replay. The flag provides a way to preserve that order for histories that record it.

In practice this meant that changing workflow code from this:

    @Override
    public String execute() {
      Promise<String> first =  Async.function(activities::first)
      String second = activities.second();
      return first.get() + "," + second;
    }

to this

    @Override
    public String execute() {
      Promise<String> first = Async.function(() -> activities.first())
      String second = activities.second();
      return first.get() + "," + second;
    }

was not deterministic. After this change and the flag is enabled it would be a deterministic change.

Checklist

  1. Closes

  2. How was this tested:

  1. Any docs updates needed?

Note

Medium Risk
Touches core Async/replay command ordering in AsyncInternal; mitigated by default-off flag and replay tests, but enabling it changes scheduling semantics for new runs.

Overview
Introduces versioned SCHEDULE_ASYNC_STUB_OPERATIONS SDK flag (off by default) so that when it is recorded in workflow history, Async.function / Async.procedure on Temporal stubs are dispatched through a workflow thread like generic lambdas, instead of running stub logic inline on the caller thread.

AsyncInternal.execute now checks the flag before branching: legacy behavior keeps immediate invokeTemporalStub; with the flag, stub work is completeFrom(invokeTemporalStub(...)) inside WorkflowThread.newThread, aligning command order between equivalent forms such as activities::first and () -> activities.first() and letting outbound interceptors observe/block around scheduling. Unflagged histories still replay with the old inline-stub path.

CHANGELOG notes the behavior; tests add replay fixtures (method reference ↔ lambda, unflagged), blocking-interceptor and cancellation cases (activities and child workflows).

Reviewed by Cursor Bugbot for commit 3caba2c. Bugbot is set up for automated code reviews on this repo. Configure here.

@Quinn-With-Two-Ns
Quinn-With-Two-Ns requested a review from a team as a code owner October 1, 2026 15:59
@Quinn-With-Two-Ns

Copy link
Copy Markdown
Contributor Author

related: #2981

@Quinn-With-Two-Ns Quinn-With-Two-Ns changed the title Schedule async markers like regular threads 💥 Schedule async markers like regular threads Oct 1, 2026

@maciejdudko maciejdudko 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.

Awesome! The code looks good, just needs rebasing. LGTM!

Comment thread temporal-sdk/src/main/java/io/temporal/internal/common/SdkFlag.java
@Quinn-With-Two-Ns
Quinn-With-Two-Ns merged commit 1af71ba into temporalio:main Oct 3, 2026
28 of 29 checks passed
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