Skip to content

feat(orchestrator)!: finalize batches from their speculation paths - #507

Open
behinddwalls wants to merge 1 commit into
preetam/speculation-runfrom
preetam/speculation-finalization
Open

feat(orchestrator)!: finalize batches from their speculation paths#507
behinddwalls wants to merge 1 commit into
preetam/speculation-runfrom
preetam/speculation-finalization

Conversation

@behinddwalls

@behinddwalls behinddwalls commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Why?

Speculation was building the right things and merging on the wrong rule. A head still waited for every dependency to succeed before it could merge — the same rule as before any of this — so a batch built without a slow neighbour sat behind that neighbour anyway. The paths were being earned and then ignored. This is the commit that collects them.

What?

A head merges once one of its passed paths has had every dependency it assumed would succeed actually merge; assumed-failing and ignored dependencies impose no wait. That is the whole speedup: the head waits on what its passing build was actually stacked on, not on its full dependency list. A head fails only when no funded path has a future left, and the winner's siblings are superseded — cancelled to free their slots.

Outcomes are reached in finalize, before the Speculator is asked, and committed one generation at a time, so a cascade (A fails → B's last path breaks → B fails → maybe C too) resolves in a single run without ever enacting a dependent of an outcome whose own write lost its compare-and-swap. A batch decided by a cascade gets a recovery signal before it turns terminal, since no retry of the triggering message would ever revisit it.

Cancellation joins the run: a cancelling batch is just another batch the run walks — its live paths are marked cancelling, the poll loop stops their builds, and whichever later run sees them all stopped drives the batch to Cancelled. A cancelling path with no build link is cancelled immediately; there is deliberately no reservation state and no staleness bound, so a crashed dispatch can never keep a batch out of its terminal state.

The legacy per-batch finalizer is deleted; speculate.go keeps only admission and message routing, and the package doc gains the batch lifecycle, a worked example, and the finalize step.

Test Plan

bazel test //submitqueue/orchestrator/... — the merge rule is table-driven per assumption; failure, cascade commit ordering, and CAS-loss isolation are covered, and cancellation end to end: never-dispatched, live-build, no-paths, and lost-race cases.

make fmt, make gazelle

Stack

  1. feat(speculation): generator contract and bestfirst impl #446
  2. feat(speculation): allocator contract and sticky impl #450
  3. feat(speculation): standard composed speculator #451
  4. feat(storage): speculation path set store #501
  5. feat(storage): path-build link store for per-path builds #502
  6. feat(orchestrator): dispatch builds per speculation path #503
  7. feat(orchestrator): poll builds and stop the ones nothing wants #504
  8. fix(orchestrator): mint distinct message IDs for cancel re-publishes #505
  9. feat(orchestrator): re-plan the queue from the Speculator each run #506
  10. @ feat(orchestrator)!: finalize batches from their speculation paths #507
  11. feat(orchestrator): compose per-queue speculators and turn speculation on #508

## Summary

### Why?

Speculation was building the right things and merging on the wrong rule. A head still waited for *every* dependency to succeed before it could merge — the same rule as before any of this — so a batch built without a slow neighbour sat behind that neighbour anyway. The paths were being earned and then ignored. This is the commit that collects them.

### What?

A head merges once one of its **passed** paths has had every dependency it *assumed would succeed* actually merge; assumed-failing and ignored dependencies impose no wait. That is the whole speedup: the head waits on what its passing build was actually stacked on, not on its full dependency list. A head fails only when no funded path has a future left, and the winner's siblings are superseded — cancelled to free their slots.

Outcomes are reached in `finalize`, before the Speculator is asked, and committed one generation at a time, so a cascade (A fails → B's last path breaks → B fails → maybe C too) resolves in a single run without ever enacting a dependent of an outcome whose own write lost its compare-and-swap. A batch decided by a cascade gets a recovery signal before it turns terminal, since no retry of the triggering message would ever revisit it.

Cancellation joins the run: a cancelling batch is just another batch the run walks — its live paths are marked cancelling, the poll loop stops their builds, and whichever later run sees them all stopped drives the batch to Cancelled. A cancelling path with no build link is cancelled immediately; there is deliberately no reservation state and no staleness bound, so a crashed dispatch can never keep a batch out of its terminal state.

The legacy per-batch finalizer is deleted; `speculate.go` keeps only admission and message routing, and the package doc gains the batch lifecycle, a worked example, and the finalize step.

## Test Plan

✅ `bazel test //submitqueue/orchestrator/...` — the merge rule is table-driven per assumption; failure, cascade commit ordering, and CAS-loss isolation are covered, and cancellation end to end: never-dispatched, live-build, no-paths, and lost-race cases.

✅ `make fmt`, `make gazelle`
@behinddwalls
behinddwalls force-pushed the preetam/speculation-finalization branch from 2b97a64 to 93573ae Compare August 4, 2026 05:11
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