Documentation
¶
Overview ¶
Package handoff is a six-phase state machine for migrating owned state from one owner to another with consistency guarantees.
Lifecycle: Lock → Serialize → Transfer → Ack → Unlock → Complete. Phases 1–4 (up to and including Transfer/Ack) are reversible via Rollback. Phase 5 (Unlock) is past the point of no return: the destination already has live ownership and rolling back would risk duplicating state — recovery from a Phase 5 failure is via forward-completion instead.
Use cases:
- Game shard handoff (player zone transitions between server pods).
- Distributed leader / primary handoff.
- Workload migration between worker pods.
- Partition rebalancing.
This package is pure logic. Persistence is the caller's responsibility behind the Repository interface — the Machine transitions in memory and exposes Record() for the caller to write. A MemoryRepository ships for tests.
Index ¶
- Variables
- type Machine
- func (m *Machine) Ack() error
- func (m *Machine) Complete() error
- func (m *Machine) IsTerminal() bool
- func (m *Machine) Record() Record
- func (m *Machine) Rollback(reason string) error
- func (m *Machine) Serialize(snapshot []byte) error
- func (m *Machine) SetClock(f func() time.Time)
- func (m *Machine) State() Phase
- func (m *Machine) Transfer() error
- func (m *Machine) Unlock() error
- type MemoryRepository
- type Phase
- type Record
- type RecordID
- type Repository
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidPhaseTransition = errors.New("handoff: invalid phase transition") ErrTerminal = errors.New("handoff: machine is terminal") )
Errors surfaced by Machine.
var ErrRecordNotFound = errors.New("handoff: record not found")
ErrRecordNotFound is returned by Repository.Get when no record matches the requested id.
Functions ¶
This section is empty.
Types ¶
type Machine ¶
type Machine struct {
// contains filtered or unexported fields
}
Machine is the pure state machine for one handoff record. Methods drive a Record through the six phases; the caller owns persistence by writing Record() after each successful transition.
func FromRecord ¶
FromRecord rehydrates a Machine from a stored Record. Used during crash recovery to resume an in-flight handoff.
func (*Machine) Ack ¶
Ack advances Transfer → Ack. Called from the destination side once it has loaded the snapshot and is live.
func (*Machine) IsTerminal ¶
IsTerminal reports whether the machine has reached PhaseComplete or PhaseRolledBack.
func (*Machine) Rollback ¶
Rollback aborts an in-flight handoff in phases 1–4. PhaseUnlock and beyond are past the point of no return: the destination already has live ownership, and rolling back would risk duplicating state.
type MemoryRepository ¶
type MemoryRepository struct {
// contains filtered or unexported fields
}
MemoryRepository is an in-process Repository. Goroutine-safe.
func NewMemoryRepository ¶
func NewMemoryRepository() *MemoryRepository
NewMemoryRepository constructs an empty MemoryRepository.
func (*MemoryRepository) InProgress ¶
InProgress implements Repository.
type Phase ¶
type Phase uint8
Phase is the wire-stable phase number.
const ( // PhaseLock — owner freezes the subject, rejects further input. PhaseLock Phase = 1 // PhaseSerialize — owner writes the snapshot to the handoff record. PhaseSerialize Phase = 2 // PhaseTransfer — owner notifies destination; dest loads the snapshot. PhaseTransfer Phase = 3 // PhaseAck — destination confirms live + accepting input. PhaseAck Phase = 4 // PhaseUnlock — owner releases the local subject copy. PhaseUnlock Phase = 5 // PhaseComplete — terminal success. PhaseComplete Phase = 6 // PhaseRolledBack — terminal failure. Record.Error has the reason. PhaseRolledBack Phase = 99 )
type Record ¶
type Record struct {
ID RecordID
Subject string // what's moving (player id, partition id, etc.)
From, To string // owner identifiers
State Phase
StartedAt time.Time
LastTransition time.Time
Snapshot []byte
Error string
}
Record captures one handoff's complete state. Stored verbatim by the repository.
type Repository ¶
type Repository interface {
Save(ctx context.Context, r Record) error
Get(ctx context.Context, id RecordID) (Record, error)
// InProgress returns all non-terminal Records owned by `owner` —
// used by a pod at startup to find handoffs to resume.
InProgress(ctx context.Context, owner string) ([]Record, error)
}
Repository persists Records. Implementations must be safe for concurrent use.