Allow time skipping during Nexus retry backoff by tekkaya · Pull Request #12607 · temporalio/temporal · GitHub
Skip to content

Allow time skipping during Nexus retry backoff - #12607

Open
tekkaya wants to merge 1 commit into
mainfrom
gokhan/nexus-989-timeskipping
Open

tekkaya wants to merge 1 commit into
mainfrom
gokhan/nexus-989-timeskipping

Conversation

@tekkaya

@tekkaya tekkaya commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What changed?

  • Allow workflow execution time skipping when every pending HSM and CHASM Nexus Operation is waiting in retry backoff.
  • Include each Operation's next retry, Schedule-to-Close, and Schedule-to-Start deadlines when selecting the next virtual-time target.
  • Regenerate HSM and CHASM timer tasks against the new virtual-time offset on active and standby clusters.
  • Defer workflow CHASM timer regeneration until CHASM transaction close has removed invalid tasks and materialized new tasks, so every surviving logical timer is emitted once.
  • Scope HSM same-transaction deduplication to regeneration instead of silently deduplicating all MutableState.AddTasks callers.
  • Convert every CHASM pure-task deletion cutoff from workflow virtual time to the physical timer frame.

Why?

A pending Nexus Operation currently blocks workflow time skipping even when it cannot make progress until a future retry deadline. Treating retry backoff as idle time allows the workflow to advance to the next relevant deadline without skipping past the Operation's timeout boundaries.

Each retry skip intentionally consumes one SessionSkipCount. MaxSessionSkipCount remains the runaway guard: after the limit is reached, time skipping is disabled and subsequent retries observe their normal wall-clock backoff.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

Potential risks

  • This broadens workflow time-skipping eligibility while Nexus Operations are backing off. The implementation permits the transition only when every pending Operation is in BACKING_OFF and all retry and timeout targets are strictly in the future; SCHEDULED and STARTED Operations still block skipping.
  • Re-stamping side-effect timers leaves prior physical timer rows to be rejected by normal stale-task validation. This follows the existing time-skipping task model but can temporarily increase timer-task volume after a skip.
  • Standby clusters now re-stamp CHASM timers when a replicated accumulated-skip update is applied, preventing failover from using pre-skip fire times.

@tekkaya
tekkaya force-pushed the gokhan/nexus-989-timeskipping branch 10 times, most recently from 7ff4f0f to e3307e2 Compare October 22, 2026 02:01
@tekkaya
tekkaya force-pushed the gokhan/nexus-989-timeskipping branch from e3307e2 to 6e86f86 Compare October 22, 2026 02:17
@tekkaya
tekkaya marked this pull request as ready for review October 22, 2026 02:24
@tekkaya
tekkaya requested review from a team as code owners October 22, 2026 02:24
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

operationTargets, allTargetsFuture := collectFutureNexusOperationTargets(
now,
op.GetScheduledTime().AsTime(),
op.GetNextAttemptScheduleTime().AsTime(),

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.

med — HSM NextAttemptScheduleTime is in wall-clock time, but it is compared with virtual ms.Now() and used as a virtual skip target.

The HSM executor sets EventAttemptFailed.Time from env.Now() (hsm/nexusoperations/executors.go:570). stateMachineEnvironment.Now() returns shardContext.GetTimeSource().Now(), which is wall-clock time. So NextAttemptScheduleTime = wall + delay. ms.Now() is the time-skipping wrapped source, so it equals wall + S, where S is the accumulated skip. ScheduledTime comes from the event time, so it is virtual, and these targets mix both frames. The CHASM path does not have this problem because it uses ctx.Now(o).

After any earlier skip (S > 0):

  • If delay <= S, collectFutureNexusOperationTargets sees the retry as already due and blocks skipping. User timers are then not skipped while the operation backs off.
  • If delay > S, the transition skips only delay - S, and TIME_SKIPPING_TRANSITIONED records the wrong target and duration.

This happens on the second retry of the PR's own scenario. The first retry skip sets S = delay₁, and attempt 2 then computes wall + delay₂. The functional test makes only one retry, starting from S = 0, so it does not catch this. A related problem already exists: executeStateMachineTimers treats the same wall-clock deadline as virtual and subtracts S, so the HSM backoff timer fires S early. This PR now uses that value as a skip target.

Suggestion: Convert the HSM retry deadline to the virtual frame before using it, for example AdjustNowWithTimeSkipping/+ ms.accumulatedSkippedDuration() as of when it was computed. A better fix is to make the HSM Nexus executor stamp NextAttemptScheduleTime from the mutable state's virtual clock, matching CHASM. Also add a test with two retries, or one that starts with a non-zero accumulated skip.

return workflowSkipEligibility{}
}
if nexusEvaluation.blocksSkipping {
noSkippingReason = "has pending nexus operations"

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.

small — CHASM Nexus Operations that are SCHEDULED or STARTED now block time skipping, which is new behavior the PR description doesn't mention.

Before this PR, isWorkflowSkippable checked only nexusoperations.MachineCollection(ms.HSM()).Size(). A workflow whose Nexus Operations lived in the CHASM Workflow.Operations map could therefore skip time while an Operation was scheduled or running. Now evaluateCHASMNexusOperationsForTimeSkipping blocks skipping for any CHASM Operation that is not BACKING_OFF. The description says SCHEDULED/STARTED Operations "still block skipping", which is true only for HSM. This is probably the right behavior and matches HSM, but it is a new restriction for CHASM-backed workflows. For example, a long-running async CHASM Operation now stops user timers from being skipped.

Suggestion: State in the PR description that CHASM Nexus Operations now gate time skipping the same way HSM Operations do. Add a CHASM STARTED case to TestEvaluateWorkflowSkipEligibility, since only SCHEDULED is covered now.

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.

nit — Only tests call findNextSkipTarget now.

The close path calls findNextSkipTargetWithNexus(eligibility.nexusTimerTargets) directly. That leaves findNextSkipTarget as a production wrapper used only by tests, and it repeats the Nexus evaluation and its error logging.

Suggestion: Remove findNextSkipTarget. Have the tests call ms.findNextSkipTargetWithNexus(ms.evaluateWorkflowSkipEligibility().nexusTimerTargets), or rename findNextSkipTargetWithNexus to findNextSkipTarget.

This branch has not been deployed

No deployments
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