speculate

package
v0.3.0-20260807212926-... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package speculate plans a queue's speculative builds: which guesses about the queue's future are worth building, within the queue's cap on concurrent builds (the build budget).

Batch outcomes — merge or fail — are still decided by the legacy per-batch finalizer in speculate.go, which waits on every dependency. Deriving them from the paths planned here replaces it in the next change; this package doc grows with it.

Why speculation

Batches in a queue depend on the batches ahead of them, so without speculation everything is serial: C waits for B, B waits for A. Speculation builds a batch against a guess about how its dependencies turn out. When the guess holds, the head's build has already run by the time its dependencies resolve — it never waits for a build of its own to start afterwards.

Paths

The batch being speculated on is the head. One complete guess about it is a path: one assumption per dependency, each "succeeds", "fails", or "ignored" (no claim either way). A path's ID hashes the head and its assumptions, so a path *is* its guess; building the same guess again is a new attempt of the same path, and (path ID, attempt) names the resulting build.

The life of a path

A path's status tracks its current attempt:

              funded            observed running       build finished
(no entry) ─────────► pending ─────────► building ──────┬──► passed
                         │                    │         └──► failed
           "stop this":  │                    │
           broken or     ▼                    ▼
           preempted   ─────► cancelling ◄────┘
                                  │
                                  │  build observed stopped
                                  ▼
                              cancelled

A path is funded — given one of the slots under the queue's cap on concurrent builds, the build budget — when its guess is judged worth building, and every pending, building, and cancelling path holds its slot until its build stops. A path is broken once a dependency's actual result proves one of its assumptions wrong: its guess can no longer come true, so its build is cancelled to free the slot.

Cancelling is intent, not fact: the build keeps its slot until CI actually stops it, and only an observation of that stop moves the path to cancelled. The intent needs no dispatch of its own — the poll loop reads it off the set and asks the runner to stop the build. A terminal path can be resurrected by a new build proposal — status returns to pending and Attempt increments, the one backwards step in the diagram.

How a run works

Every message is only a dirty signal — "this queue changed" — naming the batch that changed. The run then re-plans the whole queue from a single read; nothing carries over from earlier runs, so duplicated, delayed, or reordered signals are harmless, and a later run repairs whatever an earlier one left half-done.

signal ──► read ──► cancel ──► ask ──► check ──► dispatch
           one      broken     the     filter    save changes,
           read of  paths      Specu-  its       hand builds to
           queue +             lator   proposals the build stage
           paths

The Speculator is the extension that proposes which paths to fund or preempt. It only ever proposes: check.go filters its answer, and broken paths are cancelled before it is asked, so it reasons over facts as they now stand rather than over a picture the run is about to invalidate.

Ownership

The path set has exactly one writer: this controller's run. The build stage starts builds and the buildsignal stage watches and stops them; both write only per-build records of their own (the build link and the build's status), which read folds into the snapshot, and buildsignal reads the set only as the kill list for builds nothing wants any more. One writer is what lets a run hold one version of a head's paths across its whole decision without a poll invalidating it mid-thought.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller handles speculate queue messages.

Each message is a dirty signal: it names the batch that changed, but only so the queue wakes up. The controller then re-plans that whole queue from a single read — see run — asking the Speculator which paths are worth building within the budget and cancelling the ones a resolved dependency has ruled out. Nothing carries over between runs, so duplicated or reordered signals are harmless and a later run repairs whatever an earlier one left half-done.

Batch verdicts are still the naive per-batch state machine below, which advances the triggering batch one step:

  • Created → admit to Speculating so the Speculator can act on it.
  • Speculating → if all deps are Succeeded, publish to merge and transition to Merging; otherwise no-op (or fail-fast if a dep is in a non-succeeding terminal state).
  • Cancelling → cancel any in-flight Build entity, respeculate dependents, CAS to terminal Cancelled, publish to conclude.
  • Merging → no-op (owned by the merge controller).
  • Terminal → re-fan-out to conclude for self-healing in case a prior publish was lost.

Waiting on every dependency is strictly stricter than waiting on the ones a passed path assumed, so this is correct while the path-aware finalization that replaces it is written — it just does not yet collect the speedup the paths are earning.

func NewController

func NewController(
	logger *zap.SugaredLogger,
	scope tally.Scope,
	stores storage.Factory,
	speculators speculator.Factory,
	registry consumer.TopicRegistry,
	topicKey consumer.TopicKey,
	consumerGroup string,
) *Controller

NewController creates a new speculate controller for the orchestrator.

func (*Controller) ConsumerGroup

func (c *Controller) ConsumerGroup() string

ConsumerGroup returns the consumer group for offset tracking.

func (*Controller) Name

func (c *Controller) Name() string

Name returns the controller name for logging and metrics.

func (*Controller) Process

func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) error

Process re-plans the triggering batch's queue, then advances that batch one step along the legacy per-batch state machine (see the package doc). Returns nil to ack (success), or error to nack (retry).

func (*Controller) TopicKey

func (c *Controller) TopicKey() consumer.TopicKey

TopicKey returns the topic key this controller subscribes to.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL