handoff

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 6 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidPhaseTransition = errors.New("handoff: invalid phase transition")
	ErrTerminal               = errors.New("handoff: machine is terminal")
)

Errors surfaced by Machine.

View Source
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

func FromRecord(r Record) *Machine

FromRecord rehydrates a Machine from a stored Record. Used during crash recovery to resume an in-flight handoff.

func New

func New(subject, from, to string) *Machine

New constructs a fresh handoff Machine in PhaseLock.

func (*Machine) Ack

func (m *Machine) Ack() error

Ack advances Transfer → Ack. Called from the destination side once it has loaded the snapshot and is live.

func (*Machine) Complete

func (m *Machine) Complete() error

Complete advances Unlock → Complete. Terminal success.

func (*Machine) IsTerminal

func (m *Machine) IsTerminal() bool

IsTerminal reports whether the machine has reached PhaseComplete or PhaseRolledBack.

func (*Machine) Record

func (m *Machine) Record() Record

Record returns a copy of the current record.

func (*Machine) Rollback

func (m *Machine) Rollback(reason string) error

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.

func (*Machine) Serialize

func (m *Machine) Serialize(snapshot []byte) error

Serialize advances Lock → Serialize, stamping the snapshot.

func (*Machine) SetClock

func (m *Machine) SetClock(f func() time.Time)

SetClock overrides the wall clock; used in tests.

func (*Machine) State

func (m *Machine) State() Phase

State returns the current phase.

func (*Machine) Transfer

func (m *Machine) Transfer() error

Transfer advances Serialize → Transfer. The caller notifies the destination after persisting.

func (*Machine) Unlock

func (m *Machine) Unlock() error

Unlock advances Ack → Unlock. The caller deletes its local copy of the subject after persisting.

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) Get

Get implements Repository.

func (*MemoryRepository) InProgress

func (r *MemoryRepository) InProgress(_ context.Context, owner string) ([]Record, error)

InProgress implements Repository.

func (*MemoryRepository) Save

func (r *MemoryRepository) Save(_ context.Context, rec Record) error

Save 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
)

func (Phase) String

func (p Phase) String() string

String returns the phase label for logs and audit.

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 RecordID

type RecordID string

RecordID uniquely identifies a handoff record.

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.

Jump to

Keyboard shortcuts

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