Skip to content

Separate CHASM scheduler execution phases - #40

Draft
chaptersix wants to merge 6 commits into
sch-readable-buffer-plannerfrom
sch-readable-execution-phases
Draft

Separate CHASM scheduler execution phases#40
chaptersix wants to merge 6 commits into
sch-readable-buffer-plannerfrom
sch-readable-execution-phases

Conversation

@chaptersix

@chaptersix chaptersix commented Aug 19, 2026

Copy link
Copy Markdown
Owner

What changed?

  • Refactored InvokerExecuteTaskHandler.Execute into three explicit phases: load state in a CHASM read transaction, execute external RPCs outside the transaction, and commit results in a CHASM update transaction.
  • Moved the complete Execute ownership boundary into invoker_execute_task.go; process-buffer planning remains separate.
  • Carries detached action snapshots and revalidates their original position and exact state before applying results, including duplicate-request-ID and first-writer-wins races.
  • Preserves terminate, cancel, then start ordering with one shared MaxActionsPerExecution budget, and keeps retry lifecycle writes and successor scheduling in commit.
  • Added a side-effect-free Execute validity evaluator with named invalidation reasons and focused coverage for ordering, budgets, retries, invalidations, metrics, action counts, and conflict-token behavior.

Why?

Execute is a CHASM side-effect task, so CHASM invokes it without holding the component lock or an open component transaction. The handler must explicitly enter chasm.ReadComponent and chasm.UpdateComponent callbacks when it needs safe access to the component tree. Those callbacks are the transactional boundaries: the read callback sees a consistent tree, while the update callback applies state changes and successor tasks together under CHASM's mutable-state conflict handling.

The unlocked side-effect task is the right place for external RPCs, but it cannot retain pointers obtained from a component callback. Running the RPCs inside an update callback would instead extend lock ownership across unbounded network I/O. The load phase therefore enters a short read transaction, clones only the state needed by the RPCs, and exits before making any calls.

Because live scheduler state may change while the unlocked RPCs are in flight, the commit phase opens a new update transaction and compares each result with the exact state and position that produced it. A matching result is applied with the existing lifecycle transition helpers; a mismatched result is invalidated rather than written onto a different or already-updated entry. This preserves first-writer-wins behavior while StartWorkflow request IDs keep repeated external calls idempotent.

Stack: #35 -> #36 -> #38 -> #39 -> #40. This PR is based on #39.

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)
make fmt-imports
go test -tags test_dep ./chasm/lib/scheduler/internal ./chasm/lib/scheduler -count=1
make lint-code
git diff --check

The functional suite was intentionally left to PR CI.

Potential risks

Commit now invalidates a loaded result whenever its exact expected state or position changed. The remaining live work is re-armed through the existing task helpers, and repeated StartWorkflow calls retain request-ID idempotency. No persisted schema, dynamic-config switch, or production fallback was added.

@chaptersix
chaptersix force-pushed the sch-readable-execution-phases branch from a76a062 to 3163bb4 Compare August 19, 2026 11:30
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