Documentation
¶
Overview ¶
Package runtime holds the runner pieces that pkg/migration, pkg/move and pkg/datasync would otherwise each keep a copy of, and let drift apart.
The finite runners (migration and move) walk the same states with the same subsystems, so their periodic status block and Progress report are built here from a Snapshot rather than from two copies of the same switch. FatalGate is the state transition both make when their change feed fails, and SharedThrottler the throttler both resolve while already being polled. All three runners use Lifecycle, the cancel function and correctness evidence of a Run invocation, and RecordCopyCompleted.
The name matches the standard library's runtime package. A file that needs both must import one under an alias.
Index ¶
- func RecordCopyCompleted(tracker *status.Tracker, c copier.Copier, rowsAtResume uint64)
- type FatalGate
- type FatalTarget
- type Lifecycle
- func (l *Lifecycle) Begin(ctx context.Context) (context.Context, func(*error))
- func (l *Lifecycle) Cancel(cause error)
- func (l *Lifecycle) MarkDurableMutation()
- func (l *Lifecycle) RecordError(err error)
- func (l *Lifecycle) Result() status.WorkflowResult
- func (l *Lifecycle) SetCancel(cancel context.CancelCauseFunc)
- func (l *Lifecycle) SetTerminalOwnership(o status.WorkflowTerminalOwnership)
- type SharedThrottler
- type Snapshot
- type Source
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RecordCopyCompleted ¶
RecordCopyCompleted reports to tracker the copy aggregate settled during this Run invocation. The chunker restores its settled row count from the checkpoint while its chunk count starts afresh, so rowsAtResume, the row count it restored, is subtracted to keep the two figures on the same invocation. It does nothing until c has a chunker.
Used by all three runners, including datasync.
Types ¶
type FatalGate ¶
type FatalGate struct {
// contains filtered or unexported fields
}
FatalGate is the change feed's fatal-condition handler (change.ClientConfig.CancelFunc) shared by the finite runners. The zero value is ready to use. A runner owns one gate for its lifetime and wires every change feed it starts to it, so a burst of fatal events from several feed goroutines is realistic: the gate acts on the first and ignores the rest.
Datasync does not use it. It has no cutover to guard and always keeps its checkpoint (see change.FatalReason.PreservesCheckpoint).
func (*FatalGate) Trip ¶
func (g *FatalGate) Trip(reason change.FatalReason, t FatalTarget) bool
Trip aborts the run for reason. It returns false, and does nothing, once the run has reached cutover: the tables have been swapped, so there is nothing left to abort. Otherwise it returns true, and on the first call only:
- sets the state to status.ErrCleanup,
- logs reason's operator advice (change.FatalReason.Advice),
- drops the checkpoint unless reason leaves it resumable (change.FatalReason.PreservesCheckpoint). A schema change, for example, would otherwise block the run from proceeding permanently; starting again is the better choice. A dead stream preserves it: that is exactly the failure checkpoint resume exists to recover from.
- cancels the run with a status.FatalAbort cause.
Doing these once keeps the side effects from racing each other and the runner's Close.
type FatalTarget ¶
type FatalTarget struct {
// Noun names the run in log lines and the abort error ("migration", "move").
Noun string
Tracker *status.Tracker
Logger *slog.Logger
// DropCheckpoint invalidates the run's checkpoint. It must return nil
// without doing anything when setup has not created the checkpoint table
// yet: a feed can fail during early setup.
DropCheckpoint func(context.Context) error
// Cancel cancels the run with cause. Run returns the cause (see
// status.AbortCause), so the abort is reported as the failure it is and
// not as an operator cancellation.
Cancel func(cause error)
}
FatalTarget is the run a FatalGate aborts.
type Lifecycle ¶
type Lifecycle struct {
// contains filtered or unexported fields
}
Lifecycle is the state a runner's Run invocation shares with the methods an API caller or a background goroutine calls while it runs: the function that cancels it, and the correctness evidence (status.WorkflowResult) it leaves behind. The zero value is ready to use. A runner owns one as a named field and forwards its own Cancel, Abort and Result to it, so the runner's public API does not grow the methods that record evidence.
func (*Lifecycle) Begin ¶
Begin starts a Run invocation. It derives a context that Cancel cancels, publishes its cancel function, and clears the evidence the previous invocation left. Run must defer the returned function with the address of its named error result:
ctx, end := r.lifecycle.Begin(ctx) defer end(&retErr)
end replaces a context.Canceled result with a fatal-abort cause (see status.AbortCause), then records the evidence the result carries (see RecordError), then cancels the context. Recording after the substitution records the evidence of the error Run returns, which a fatal-abort cause can carry and context.Canceled cannot. Cancelling last changes nothing: a context keeps the first cause it was cancelled with.
func (*Lifecycle) Cancel ¶
Cancel cancels the running invocation with cause. A nil cause is an operator cancellation: Run returns context.Canceled. A cause marked with status.FatalAbort is returned by Run instead. It does nothing before Begin (early setup, or test paths that bypass Run).
func (*Lifecycle) MarkDurableMutation ¶
func (l *Lifecycle) MarkDurableMutation()
MarkDurableMutation records that this invocation changed something that a retry cannot undo.
func (*Lifecycle) RecordError ¶
RecordError records the evidence err carries: status.ErrDurableMutation and status.ErrOwnershipAmbiguous.
func (*Lifecycle) Result ¶
func (l *Lifecycle) Result() status.WorkflowResult
Result returns the evidence retained from the most recent invocation.
func (*Lifecycle) SetCancel ¶
func (l *Lifecycle) SetCancel(cancel context.CancelCauseFunc)
SetCancel replaces the cancel function. Begin calls it; tests that drive a runner's phases without Run call it to observe or supply cancellation.
func (*Lifecycle) SetTerminalOwnership ¶
func (l *Lifecycle) SetTerminalOwnership(o status.WorkflowTerminalOwnership)
SetTerminalOwnership records which side owns the table or traffic when the invocation ends.
type SharedThrottler ¶
type SharedThrottler struct {
// contains filtered or unexported fields
}
SharedThrottler holds the throttler a runner resolves during setup. Setup assigns it partway through, while an API caller may already be polling Progress, which reports whether the run is paused, and a change feed's UnderLoad callback may already be reading it. The zero value holds nil.
func (*SharedThrottler) Get ¶
func (s *SharedThrottler) Get() throttler.Throttler
Get returns the resolved throttler, or nil if setup has not resolved one.
func (*SharedThrottler) Set ¶
func (s *SharedThrottler) Set(t throttler.Throttler)
Set publishes the resolved throttler.
type Snapshot ¶
type Snapshot struct {
// Noun names the workflow in the status header ("migration", "move").
Noun string
State status.State
Tracker *status.Tracker
// Tables is the copy chunker's per-table progress. Progress and Status
// both derive their copy figures from it, so the API and the log block
// report one measure: settled rows against the tables' cardinality
// estimates, kept past the end of the copy.
Tables []status.TableProgress
Checkpoint *status.LastCheckpoint
Resumed bool
Source Source
}
Snapshot is what a runner reports on. The runner builds one per call, and the state is read once when it does, so every field of a report describes the same state rather than whichever state each field happened to observe.
func (*Snapshot) Status ¶
Status returns the periodic report on the whole run: a header line plus one indented row per subsystem (see status.Block). It absorbs what used to be separate periodic lines from the change feeds (flushes, rotations) and the checkpoint dumper, which each ran on their own interval — see github.com/block/spirit/issues/329.
func (*Snapshot) ThrottleStatus ¶
func (s *Snapshot) ThrottleStatus() status.ThrottleStatus
ThrottleStatus reports only signals honored by the work currently running. Checksum passes honor load signals; interval waits and cutover are unpaced. The sentinel wait only reports pacing while a continuous checksum runs.
type Source ¶
type Source struct {
Copier func() copier.Copier
Applier func() applier.Applier
Checker func() checksum.Checker
Feeds func() []change.Source
Throttler func() throttler.Throttler
// SentinelSchema, when set and not empty, adds the sentinel table's name
// to the sentinel row. A move's sources can span several schemas, so it
// leaves it nil.
SentinelSchema func() string
}
Source gives a Snapshot the runner's subsystems. Setup assigns them while an API caller may already be polling, so they are read through functions, and a Snapshot only calls the ones the current state reports on, which are the ones setup has finished assigning. Every function but SentinelSchema must be set.