Documentation
¶
Overview ¶
Package lockstep is an actor runtime for Go whose actor code runs unchanged on the worker pool of New and on a seeded simulation in tests (package lockstep/sim), where a failing seed replays exactly.
A system built by New runs actor turns on a fixed pool of worker goroutines (WithWorkers): turns of different actors can run in parallel, while the turns of one actor never overlap. Effects started with Do run on a separate bounded pool (WithEffectPool).
Index ¶
- Constants
- Variables
- func NewReply[R any](from Sender, opts ...AskOption) (ReplyTo[R], *Reply[R])
- func Pipe[R, M any](ctx *Context[M], r *Reply[R], to func(R, error) M)
- func Send[M any](from Sender, to PID[M], msg M) error
- func SendContext[M any](ctx context.Context, from Sender, to PID[M], msg M) error
- func Stop(from Sender, addr Address) error
- func Unwatch[M any](ctx *Context[M], addr Address)
- func Watch[M any](ctx *Context[M], addr Address) error
- type Actor
- type ActorEstimate
- type Address
- type AskOption
- type ChildFailed
- type Context
- func (c *Context[M]) After(d time.Duration, msg M) TimerID
- func (c *Context[M]) CancelEffect(id EffectID) bool
- func (c *Context[M]) CancelTimer(id TimerID) bool
- func (c *Context[M]) Every(d time.Duration, msg M) TimerID
- func (c *Context[M]) Fail(err error)
- func (c *Context[M]) Logf(format string, args ...any)
- func (c *Context[M]) Logger() *slog.Logger
- func (c *Context[M]) Now() time.Time
- func (c *Context[M]) Own(r io.Closer)
- func (c *Context[M]) Parent() Address
- func (c *Context[M]) Rand() *rand.Rand
- func (c *Context[M]) Self() PID[M]
- func (c *Context[M]) Stop()
- func (c *Context[M]) SubscribeOverload()
- func (c *Context[M]) UnsubscribeOverload()
- type Counter
- type Directive
- type Effect
- type EffectID
- type EffectOption
- type MailboxPolicy
- type MemSizer
- type MetricLabel
- type Metrics
- type Option
- func WithAdmission(high, low uint64) Option
- func WithDefaultMailbox(capacity int, policy MailboxPolicy) Option
- func WithEffectPool(workers, queue int) Option
- func WithGoroutineWarning(bound int) Option
- func WithGuardianStrategy(s Strategy) Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxActors(n int) Option
- func WithMemoryEstimates() Option
- func WithMetrics(m Metrics) Option
- func WithNodeName(name string) Option
- func WithThroughput(n int) Option
- func WithWatchdog(d time.Duration) Option
- func WithWorkers(n int) Option
- type OverloadReason
- type Overloaded
- type PID
- type PanicError
- type Reply
- type ReplyTo
- type Sender
- type Signal
- type SignalReceiver
- type Sizer
- type SpawnOption
- type Spawner
- type Starter
- type StopKind
- type StopReason
- type Stopper
- type Strategy
- type System
- type Terminated
- type TimerID
Examples ¶
Constants ¶
const DefaultEffectTimeout = 30 * time.Second
DefaultEffectTimeout is the timeout of an effect started without WithEffectTimeout.
const DefaultGoroutineWarning = 10_000
DefaultGoroutineWarning is the goroutine count above which a production System logs a warning.
const DefaultMaxActors = 1_000_000
DefaultMaxActors is the actor cap of a System built without WithMaxActors.
const DefaultWatchdogTimeout = 5 * time.Second
DefaultWatchdogTimeout is the stuck-turn timeout of a production System without WithWatchdog.
Variables ¶
var ( ErrStopped = errors.New("lockstep: stopped") ErrNameTaken = errors.New("lockstep: name taken") ErrWrongType = errors.New("lockstep: wrong message type") ErrNotFound = errors.New("lockstep: not found") ErrInvalidOption = errors.New("lockstep: invalid option") ErrInvalidName = errors.New("lockstep: invalid name") ErrNotLocal = errors.New("lockstep: address not local") )
Errors of package lockstep.
var ( ErrTimeout = errors.New("lockstep: timeout") ErrTerminated = errors.New("lockstep: actor terminated") ErrAwaitInActor = errors.New("lockstep: blocking wait inside an actor") )
Errors of sending and asking.
var ( Normal = StopReason{Kind: StopNormal} ParentStopping = StopReason{Kind: StopParentStopping} Shutdown = StopReason{Kind: StopShutdown} // NoActor is the reason of a Terminated for an address that was not alive when it was watched // (Erlang's noproc). It never reaches Stopped. NoActor = StopReason{Kind: StopNoActor} )
var ErrActorCap = errors.New("lockstep: actor cap reached")
ErrActorCap is the error of a spawn refused because the System has reached its actor cap (WithMaxActors). The refusal's text names the parent, the live actor count, the cap and its reserve, and errors.Is matches ErrActorCap.
var ErrMailboxFull = errors.New("lockstep: mailbox full")
ErrMailboxFull is the error of a message that a full mailbox refuses. A refusal returns fmt.Errorf("%w: %s", ErrMailboxFull, address), so errors.Is matches it and the text names the target.
var ErrNoEffectHandler = errors.New("lockstep: no effect handler")
ErrNoEffectHandler is the error that fails a simulation seed when an actor starts an effect and the simulation has no handler for the effect's name. The failure wraps it, so errors.Is(err, ErrNoEffectHandler) holds, and its text names the effect and the actor.
var ErrOverloaded = engine.ErrOverloaded
ErrOverloaded is returned when the runtime refuses work: a spawn or a message while admission sheds load, or an effect when the effect queue is full. It is the same value in a System built by New and in simulation, so errors.Is(err, ErrOverloaded) holds for both.
Functions ¶
func NewReply ¶
NewReply creates an ask without a target: a pending reply on from's system with Ask's timeout and exactly-once resolution, and the ReplyTo that answers it. The caller builds and sends the request itself with Send. An error from that Send (for example ErrMailboxFull) does not resolve the reply; the caller resolves it, for example with rt.Fail(from, err). A request that a DropNewest or DropOldest mailbox drops is not reported, so the reply resolves at its timeout. An invalid option value returns the zero ReplyTo and a reply resolved with an error wrapping ErrInvalidOption. NewReply panics if from is nil, an option is nil, or from is a Context outside its turn.
func Pipe ¶
Pipe delivers the resolution of r to the actor of ctx as one of its own messages: when r resolves, to(value, err) is called during a turn of the same incarnation and its result is passed to Receive in that turn. If the incarnation has ended by then, nothing is delivered. Pipe panics if ctx, r or to is nil, if ctx is outside its turn, or if r was created on another system.
func Send ¶
Send delivers msg to the actor to. It returns nil when msg was enqueued, became a dead letter, or was accepted by the transport for another node. Errors: ErrNotFound for the zero PID, ErrStopped when the sender's system is stopped, the target mailbox's policy error (ErrMailboxFull), the admission error (ErrOverloaded), and a synchronous transport error, wrapped. Send panics if from is nil or is a Context outside its turn.
func SendContext ¶
SendContext is Send, except that when from is a *System, the target's policy is BlockSender and the message cannot be enqueued yet, it waits until the message is enqueued or ctx ends, and then returns ctx.Err(). Called from an actor's turn it never waits: BlockSender acts as FailSend. Send with a *System sender is SendContext with context.Background().
func Stop ¶
Stop asks the actor at addr to stop with reason Normal and returns without waiting. A parent may stop its child this way; any Sender may stop any local actor. A path address stops the actor registered at its path. Errors: ErrNotFound if addr is not alive or names a guardian, ErrNotLocal for another node's address (there is no remote stop). Stop panics if from is nil.
func Unwatch ¶
Unwatch cancels Watch. After it returns, the incarnation receives no Terminated for addr.
func Watch ¶
Watch asks for a Terminated signal when the actor at addr stops for good. If addr is not alive, the signal is delivered with reason NoActor. A path address (PIDAt) watches the actor registered at its path at the time of the call. Errors: ErrNotFound for the zero address and guardian addresses; ErrNotLocal for an address on another node; ErrStopped when the calling actor is stopping or restarting (inside a restart's Stopped too).
Types ¶
type Actor ¶
type Actor[M any] interface { // Receive handles one user message. It runs during a turn and must not block. Receive(ctx *Context[M], msg M) }
Actor is implemented by actor values. M is the actor's message type.
type ActorEstimate ¶
ActorEstimate is the memory estimate of one live actor.
type Address ¶
type Address struct {
// contains filtered or unexported fields
}
Address is the untyped address of one actor: node, path and uid. It is comparable; two addresses are equal when all three parts are equal. The zero Address names no actor.
type AskOption ¶
type AskOption func(*askConfig)
AskOption configures one Ask.
func WithTimeout ¶
WithTimeout sets the ask's timeout. The default is 5 s. It never panics: Ask and NewReply resolve the reply with an error wrapping ErrInvalidOption when d <= 0.
type ChildFailed ¶
type ChildFailed struct {
Child Address
Err error
Directive Directive // the directive applied; 0 when Cleanup is true
Cleanup bool // the failure happened in Stopped; no directive was applied
}
ChildFailed reports a child's failure after the supervisor's strategy has handled it.
type Context ¶
type Context[M any] struct { // contains filtered or unexported fields }
Context is an incarnation's handle to the runtime. The runtime passes the same *Context to every turn of one incarnation and a new one to the next incarnation. Self and Parent may be called at any time; every other method panics when called outside a turn of its incarnation. Call its methods only from the goroutine that runs the handler. Every method panics on a nil *Context.
func (*Context[M]) After ¶
After delivers msg to the actor itself once, d from now on the engine clock. A negative d is treated as 0. The timer is cancelled if the incarnation ends first. Called from Stopped it schedules nothing and returns 0.
func (*Context[M]) CancelEffect ¶
CancelEffect cancels the pending effect id of this incarnation and reports whether it was pending; done is then never called for it. It returns false for 0, for an ID of another incarnation, and for an effect that already has an outcome.
func (*Context[M]) CancelTimer ¶
CancelTimer stops timer id. It reports whether the timer was active. Once it returns, the actor receives no message from that timer, even one already in the mailbox.
func (*Context[M]) Every ¶
Every delivers msg to the actor itself every d, starting d from now, until the timer is cancelled or the incarnation ends. While a previous message of the timer is still in the mailbox, a firing delivers nothing. It panics if d <= 0. Called from Stopped it schedules nothing and returns 0.
func (*Context[M]) Fail ¶
Fail marks the actor failed with err when the current handler returns. Only the first call per handler counts. It panics if err is nil.
func (*Context[M]) Logf ¶
Logf logs fmt.Sprintf(format, args...) at slog.LevelInfo through Logger, with the caller of Logf as the record's source. When the tracer is enabled it also records lockstep.log with the line. It formats the line once, and not at all when Logger is not enabled for slog.LevelInfo and the tracer is off.
func (*Context[M]) Logger ¶ added in v0.2.0
Logger returns the actor logger: the system logger with the attributes node, path and uid of this actor. It returns the same *slog.Logger for the actor's lifetime, across restarts. In simulation it writes to the node's kernel log instead: each record at slog.LevelInfo or above becomes a kernel.log record of the node, and records below Info are dropped.
func (*Context[M]) Now ¶
Now returns the engine clock: wall time in production, the node's local clock in simulation, where faultline's clock drift and jumps apply.
func (*Context[M]) Own ¶
Own registers r to be closed after the incarnation's Stopped hook returns. Resources are closed in reverse registration order. It panics if r is nil.
func (*Context[M]) Parent ¶
Parent returns the parent's address: the user guardian's for actors spawned from a System.
func (*Context[M]) Rand ¶
Rand returns the random source of the current incarnation. Every call in one incarnation returns the same *rand.Rand; a restart gets a new one. Use it only inside the actor's handlers.
func (*Context[M]) Self ¶
Self returns the actor's PID. It is the same for every incarnation of the actor.
func (*Context[M]) Stop ¶
func (c *Context[M]) Stop()
Stop stops the actor with reason Normal when the current handler returns. No further user message is processed by this incarnation.
func (*Context[M]) SubscribeOverload ¶
func (c *Context[M]) SubscribeOverload()
SubscribeOverload subscribes the current incarnation to Overloaded signals. Subscribing again has no effect. If the System is shedding, an Overloaded{Active: true} signal is queued at once.
func (*Context[M]) UnsubscribeOverload ¶
func (c *Context[M]) UnsubscribeOverload()
UnsubscribeOverload cancels the subscription of the current incarnation.
type Counter ¶ added in v0.2.0
type Counter interface {
// Add increases the counter by n. lockstep passes n >= 1.
Add(n uint64)
}
Counter is one counter series.
type Effect ¶
type Effect[T any] interface { // Name returns the effect's stable name: 1 to 64 characters from [A-Za-z0-9._-], starting // with a letter or a digit. Simulation finds the effect's handler by this name, and traces // show it. Names starting with "lockstep." are reserved for lockstep's built-in effects. Name() string // Run performs the operation and returns its result. A System built by New calls it on a // goroutine of its own; simulation never calls it. Run should return soon after ctx is done, // which happens when the effect times out or is cancelled. Run(ctx context.Context) (T, error) }
Effect is an operation that waits: a network call, file I/O, a database query, CPU work over a budget. An actor starts it with Do and gets its outcome back as a message.
type EffectID ¶
type EffectID uint64
EffectID identifies one Do call within a System. IDs start at 1 and increase by one per Do call that starts an effect. 0 means that no effect was started.
func Do ¶
func Do[T, M any](ctx *Context[M], e Effect[T], done func(T, error) M, opts ...EffectOption) EffectID
Do starts effect e for the actor of ctx and returns its ID. When the effect's outcome is delivered, a later turn of the actor calls done with the result, or with the zero T and ErrTimeout or ErrOverloaded, and passes done's return value to Receive. done runs on the actor's turn, never concurrently with Receive. If the incarnation ends or CancelEffect cancels the effect first, done is never called.
Do panics if e is nil, e.Name() is invalid, done is nil, or an option is invalid.
type EffectOption ¶
type EffectOption func(*effectConfig)
EffectOption configures one Do call.
func WithEffectTimeout ¶
func WithEffectTimeout(d time.Duration) EffectOption
WithEffectTimeout sets the effect's timeout, measured on the engine's clock from the Do call. Do panics if d is not positive.
type MailboxPolicy ¶
type MailboxPolicy uint8
MailboxPolicy selects what happens when a message is sent to a full mailbox. A mailbox is full when it holds as many sent messages as its capacity. Timer messages, piped replies and effect results are never refused and never count toward capacity.
const ( // FailSend refuses the message: Send returns an error wrapping ErrMailboxFull. The default. FailSend MailboxPolicy = iota // DropNewest drops the message being sent and records the drop. Send returns nil. DropNewest // DropOldest drops the oldest sent message in the mailbox, records the drop and enqueues the // new one. Send returns nil. DropOldest // BlockSender makes an outside sender (a *System) wait until the mailbox has room, the System // admits messages again (see WithAdmission) and the actor is not stuck (see WithWatchdog), or // until its context ends. For a sender inside an actor, and in simulation, which cannot make a // caller wait, it behaves as FailSend. BlockSender )
func (MailboxPolicy) String ¶
func (p MailboxPolicy) String() string
String returns "fail-send", "drop-newest", "drop-oldest", "block-sender", or "MailboxPolicy(<n>)" for other values. Records, logs and metrics use these spellings.
type MemSizer ¶
type MemSizer interface{ MemSize() int }
MemSizer is implemented by actors that report the size of their state in bytes.
type MetricLabel ¶ added in v0.2.0
MetricLabel is one label of a series.
type Metrics ¶ added in v0.2.0
type Metrics interface {
// Counter returns the counter for one series. lockstep keeps the result and calls its Add
// method from any goroutine, including inside actor turns.
Counter(name, help string, labels ...MetricLabel) Counter
// GaugeFunc registers fn for one series. The backend calls fn whenever it collects, from any
// goroutine. fn returns quickly and never blocks a Send. Once the system has shut down, fn
// returns 0 and no longer refers to the system, except the goroutine count's, which keeps
// reporting.
GaugeFunc(name, help string, fn func() int64, labels ...MetricLabel)
}
Metrics is the backend lockstep reports to. Implementations must be safe for concurrent use. lockstep calls Counter and GaugeFunc once per series: from New, in a fixed order, and may call them later, from any goroutine, when a package enabled on the system after New registers series of its own (late registration). Duplicate registration (two systems with the same node name on one backend) is the implementation's to handle.
lockstep does not recover panics from Metrics, Counter or a gauge function.
type Option ¶
type Option func(*systemConfig)
Option configures New. Options are applied in argument order; a later option overrides an earlier one for the same setting. Option values are validated by New, not by the option constructors: no option constructor panics.
func WithAdmission ¶
WithAdmission sets absolute admission thresholds in bytes: admission stops when the live heap reaches high and resumes when it falls to low. WithAdmission(0, 0) turns admission off even when a Go memory limit is set. Any other pair needs 0 < low < high, or New panics with an error wrapping ErrInvalidOption. Simulation ignores it; its byte budgets are sim.Options.Budget.
func WithDefaultMailbox ¶
func WithDefaultMailbox(capacity int, policy MailboxPolicy) Option
WithDefaultMailbox sets the capacity and policy of every actor spawned in the system without WithMailbox. Without it the default is 1024 and FailSend. It never panics: an invalid capacity or policy makes New panic with an error wrapping ErrInvalidOption.
func WithEffectPool ¶ added in v0.2.0
WithEffectPool sets the effect pool: at most workers effects run at once, and at most queue more wait for a free slot. A Do call that finds both full resolves with ErrOverloaded. The defaults are 64 and 1024. New panics with an error wrapping ErrInvalidOption if workers < 1 or queue < 0.
func WithGoroutineWarning ¶
WithGoroutineWarning sets the goroutine count at which a production System logs a warning: one warning each time the process-wide count reaches the bound, with a summary of the functions that hold the most goroutines. The count is checked once per second, and the warning is armed again when it falls below the bound less a fifth of it. 0 turns the warning off. The count is also exported as the lockstep_goroutines gauge of WithMetrics. A bound < 0 makes New panic with an error wrapping ErrInvalidOption. Simulation ignores it.
func WithGuardianStrategy ¶
WithGuardianStrategy sets the user guardian's strategy for top-level actors. Without it, the strategy is OneForOne(nil). New panics with an error wrapping ErrInvalidOption if s is invalid.
func WithLogger ¶ added in v0.2.0
WithLogger sets the logger the system writes its log events to and that Context.Logger and Context.Logf build on. The default, and the value used when l is nil, is slog.Default() as it is when New runs. To discard logs, pass slog.New(slog.DiscardHandler).
func WithMaxActors ¶
WithMaxActors sets the maximum number of live actors of the System to n. n/100 of them (at least 1 when n >= 2) are reserved for restart rebuilds and system actors. An n < 1 makes New panic with an error wrapping ErrInvalidOption.
func WithMemoryEstimates ¶
func WithMemoryEstimates() Option
WithMemoryEstimates turns on per-actor memory estimates (Sizer, MemSizer, System.Estimates).
func WithMetrics ¶ added in v0.2.0
WithMetrics sets the metrics backend. New registers every counter and gauge function with m before it returns. nil, the default, disables metrics: nothing is registered or counted.
Example ¶
ExampleWithMetrics reports a system's counters to a map. New registers every series with the backend before it returns, each with the node label first, and the system adds to a series each time its event happens.
package main
import (
"context"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/hmdsefi/lockstep"
)
// mapMetrics adapts Metrics to a set of counters kept in a map, keyed by the series in the form
// name{label="value",...}. A real backend registers each series with its metrics library instead.
type mapMetrics struct {
mu sync.Mutex
counters map[string]*atomic.Uint64
}
// seriesKey returns the key of a series: its name and its labels, in order.
func seriesKey(name string, labels []lockstep.MetricLabel) string {
parts := make([]string, len(labels))
for i, l := range labels {
parts[i] = fmt.Sprintf("%s=%q", l.Name, l.Value)
}
return name + "{" + strings.Join(parts, ",") + "}"
}
// mapCounter is one counter of a mapMetrics.
type mapCounter struct{ n *atomic.Uint64 }
func (c mapCounter) Add(n uint64) { c.n.Add(n) }
// Counter registers the counter of one series and returns it.
func (m *mapMetrics) Counter(name, _ string, labels ...lockstep.MetricLabel) lockstep.Counter {
m.mu.Lock()
defer m.mu.Unlock()
n := new(atomic.Uint64)
m.counters[seriesKey(name, labels)] = n
return mapCounter{n}
}
// value returns the count of the series key, or 0 for a series that is not registered.
func (m *mapMetrics) value(key string) uint64 {
m.mu.Lock()
defer m.mu.Unlock()
if n := m.counters[key]; n != nil {
return n.Load()
}
return 0
}
// GaugeFunc ignores the gauge: this backend keeps counters only.
func (m *mapMetrics) GaugeFunc(string, string, func() int64, ...lockstep.MetricLabel) {}
type hello struct{}
type greeter struct{}
func (greeter) Receive(*lockstep.Context[hello], hello) {}
// ExampleWithMetrics reports a system's counters to a map. New registers every series with the
// backend before it returns, each with the node label first, and the system adds to a series each
// time its event happens.
func main() {
m := &mapMetrics{counters: make(map[string]*atomic.Uint64)}
sys := lockstep.New(lockstep.WithNodeName("example"), lockstep.WithMetrics(m))
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = sys.Shutdown(ctx)
}()
if _, err := lockstep.Spawn(sys, "greeter", func() lockstep.Actor[hello] { return greeter{} }); err != nil {
fmt.Println(err)
return
}
const spawned = `lockstep_actors_spawned_total{node="example"}`
fmt.Println(spawned, m.value(spawned))
}
Output: lockstep_actors_spawned_total{node="example"} 1
func WithNodeName ¶
WithNodeName sets the system's node name. The name must be 1-64 bytes from [A-Za-z0-9._-], starting with a letter or digit (the faultline node-name rule). WithNodeName never panics; New panics with an error wrapping ErrInvalidOption on an invalid name. The default is "local". It is ignored on a system built on an engine (simulation), which takes the engine's node name.
func WithThroughput ¶ added in v0.2.0
WithThroughput sets how many user messages one actor may process in a turn before it yields its worker to other actors. The default is 32. New panics with an error wrapping ErrInvalidOption if n < 1. It applies to the production engine only; simulation always uses 1.
func WithWatchdog ¶
WithWatchdog sets the stuck-turn timeout. A turn that runs at least d is logged and its actor is stuck until the turn returns: messages sent to it meanwhile meet its mailbox's policy as if the mailbox were full, so with FailSend, Send returns an error matching ErrMailboxFull. When a Started, Receive or Signal returns during the stuck turn, the actor fails with an error matching ErrTimeout, which its supervisor handles, unless that handler failed itself. A stuck Stopped, MemSize or factory only ends the stuck state. 0 turns the watchdog off. A d < 0 makes New panic with an error wrapping ErrInvalidOption. Simulation ignores it.
func WithWorkers ¶ added in v0.2.0
WithWorkers sets the number of worker goroutines that run actor turns. The default is runtime.GOMAXPROCS(0), read once when New runs. New panics with an error wrapping ErrInvalidOption if n < 1. The pool size never changes after New, even when GOMAXPROCS changes.
type OverloadReason ¶
type OverloadReason uint8
OverloadReason is the source of an admission change. Its values equal those of engine.AdmissionSource.
const ( OverloadMemory OverloadReason = iota + 1 // production live heap OverloadBudget // simulation count budget (sim.Options.Budget) OverloadPressure // simulation memory-pressure fault )
func (OverloadReason) String ¶
func (r OverloadReason) String() string
String returns "memory", "budget", "pressure", or "OverloadReason(<n>)".
type Overloaded ¶
type Overloaded struct {
Active bool // true: admission stopped; false: admission resumed
Reason OverloadReason // the source whose change caused this transition
Live uint64 // measured bytes at the change (OverloadBudget: the estimate); 0 for OverloadPressure
High uint64 // thresholds in force (OverloadBudget: the byte budget and its resume mark); 0 for OverloadPressure
Low uint64
}
Overloaded is the signal sent to subscribed actors when their System starts or stops shedding. It implements Signal.
type PID ¶
type PID[M any] struct { // contains filtered or unexported fields }
PID is the typed address of an actor whose message type is M. It is comparable and contains only its Address. The zero PID names no actor.
func Lookup ¶
Lookup returns the PID of the actor registered at path on sys, if it was spawned with exactly message type M. It finds an actor whose stop has begun and none whose stop has completed. It returns ErrInvalidName if path is not well-formed, ErrNotFound if no actor is registered there (the guardians /user and /system are not), and ErrWrongType if the actor's message type is not M. Lookup panics if sys is nil.
func PIDAt ¶
PIDAt returns a PID with uid 0 that addresses whichever actor is registered at path on node when the PID is used. A message to it when no actor of type M is registered at path goes to dead letters on that node. path has the form Lookup accepts. PIDAt panics if node is not a valid node name or path is malformed.
func Spawn ¶
func Spawn[M any](parent Spawner, name string, factory func() Actor[M], opts ...SpawnOption) (PID[M], error)
Spawn creates a child of parent named name and returns its PID. name "" asks for a generated name "$<n>". factory is called at every start and restart, never by Spawn itself.
Errors: ErrInvalidName, ErrInvalidOption, ErrStopped, ErrNameTaken, ErrActorCap and ErrOverloaded, each wrapped so that errors.Is matches it. Spawn panics if parent, factory or an option is nil. A spawn refused for any of these reasons registers nothing and takes no generated name.
type PanicError ¶
type PanicError struct {
Value any // the value passed to panic
Stack []byte // runtime/debug.Stack() at recovery; never recorded in traces
}
PanicError is the error of a failure caused by a panic. errors.Unwrap returns Value when it is an error.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
Error returns "lockstep: panic: " followed by a description of Value: Value.Error() for an error, Value.String() for a fmt.Stringer, the value itself for a string, integer or bool kind, and otherwise the type name ("nil" for a nil Value). The text never depends on a %v verb, so it is the same on every run.
func (*PanicError) Unwrap ¶
func (e *PanicError) Unwrap() error
Unwrap returns Value if it is an error, otherwise nil.
type Reply ¶
type Reply[R any] struct { // contains filtered or unexported fields }
Reply is the typed future of one ask.
func Ask ¶
Ask sends the message build returns to the actor to and returns the future of its answer. build receives the ask's ReplyTo and is called once, synchronously, before Ask returns, unless the ask fails before sending. With a *System sender outside any actor's turn, a full BlockSender mailbox in a System built by New makes Ask wait until the request is enqueued or the ask resolves, at its timeout at the latest. An invalid option value resolves the reply with an error wrapping ErrInvalidOption. Ask panics if from or build is nil, an option is nil, or from is a Context outside its turn.
func (*Reply[R]) Await ¶
Await waits for the ask to resolve and returns its value or error. If ctx ends first it returns the zero R and ctx.Err(), and the ask stays pending. Called during a turn it returns ErrAwaitInActor at once. In simulation Await never waits and never drives the run: on a pending reply it fails the seed and returns an error wrapping ErrAwaitInActor, from a turn or from the test body (test bodies use sim.Ask). Await may be called any number of times from any goroutine; every call that sees the resolution returns the same result.
type ReplyTo ¶
type ReplyTo[R any] struct { // contains filtered or unexported fields }
ReplyTo is the address an answer goes to. Ask creates it and passes it to build; the message carries it to whoever answers. It is a comparable value without pointers, so messages that hold it stay safe. The zero ReplyTo answers nothing.
type Sender ¶
type Sender interface {
// contains filtered or unexported methods
}
Sender is the origin of a send, an ask, a reply or a stop. It is implemented only by *System and *Context[X] for any X.
type Signal ¶
type Signal interface {
// contains filtered or unexported methods
}
Signal is a notification from lockstep itself, delivered through SignalReceiver[M].Signal and never as an M. It is implemented only by Terminated, ChildFailed and Overloaded.
type SignalReceiver ¶
SignalReceiver is implemented by actors that receive lockstep's notifications. An actor without it ignores signals; its supervisor strategy still handles its children's failures.
type Sizer ¶
type Sizer interface{ Size() int }
Sizer is implemented by messages that report their size in bytes for memory estimates.
type SpawnOption ¶
type SpawnOption func(*spawnConfig)
SpawnOption configures one Spawn. WithMailbox, WithStrategy and WithActorThroughput are SpawnOptions.
func WithActorThroughput ¶ added in v0.2.0
func WithActorThroughput(n int) SpawnOption
WithActorThroughput overrides WithThroughput for one actor: the actor processes at most n user messages per turn. Use a small n for an actor whose messages are expensive, a large one for a router that must drain quickly. Spawn returns an error wrapping ErrInvalidOption if n < 1. It has no effect in simulation, where every turn processes one message.
func WithMailbox ¶
func WithMailbox(capacity int, policy MailboxPolicy) SpawnOption
WithMailbox sets the spawned actor's mailbox capacity and full-mailbox policy. It never panics: if capacity < 1 or policy is not one of the four constants, Spawn returns an error wrapping ErrInvalidOption.
func WithStrategy ¶
func WithStrategy(s Strategy) SpawnOption
WithStrategy sets the strategy for the children of the spawned actor. Without it, the strategy is OneForOne(nil). Spawn returns an error wrapping ErrInvalidOption if s is invalid.
type Spawner ¶
type Spawner interface {
// contains filtered or unexported methods
}
Spawner is a parent for Spawn. It is implemented only by *System (the user guardian is the parent) and *Context[X] for any X (that actor is the parent).
type Starter ¶
Starter is implemented by actors that need a hook when an incarnation starts. Started runs after the factory and before the first message of the incarnation.
type StopKind ¶
type StopKind uint8
StopKind classifies a StopReason.
const ( StopNormal StopKind = iota + 1 // Stop(from, addr) or ctx.Stop StopFailed // the supervisor's directive was to stop a failed actor StopParentStopping // the parent is stopping or restarting StopShutdown // System.Shutdown StopRestarting // the incarnation ends for a restart; only Stopped sees it StopNoActor // Terminated only: the watched address was not alive )
type StopReason ¶
StopReason tells why an incarnation ended, or, in Terminated, why a watched actor is gone. Err is non-nil exactly for StopFailed and StopRestarting.
func Failed ¶
func Failed(err error) StopReason
Failed returns a StopFailed reason carrying err: the reason of an actor stopped by its supervisor after failing with err. It panics if err is nil.
func Restarting ¶
func Restarting(err error) StopReason
Restarting returns the reason Stopped receives when the incarnation ends for a restart caused by err. It panics if err is nil.
func (StopReason) String ¶
func (r StopReason) String() string
String returns Kind.String(), followed by ": " and Err.Error() when Err is non-nil.
type Stopper ¶
type Stopper[M any] interface { Stopped(ctx *Context[M], reason StopReason) }
Stopper is implemented by actors that release resources. Stopped runs exactly once per incarnation: when the actor stops, before every restart, and after a failure, but not when a simulated node crashes. Children have already stopped when it runs.
type Strategy ¶
type Strategy struct {
// Decider maps a child's failure to a directive. nil means DirectiveRestart for every error.
Decider func(err error) Directive
// MaxRestarts is how many restarts are allowed within Window; one more becomes
// DirectiveEscalate. 0 allows none.
MaxRestarts int
// Window is the sliding period over which restarts are counted, on the engine clock.
Window time.Duration
// MinBackoff is the delay before the first restart in the window; it doubles for each further
// restart in the window, up to MaxBackoff. 0 restarts without delay.
MinBackoff time.Duration
MaxBackoff time.Duration
// contains filtered or unexported fields
}
Strategy is a supervision strategy for the children of one actor. Build it with OneForOne or AllForOne and adjust the exported fields. A Strategy is valid when one of those built it, MaxRestarts and MinBackoff are not negative, Window is positive, and MaxBackoff is at least MinBackoff; a zero Strategy is invalid.
type System ¶
type System struct {
// contains filtered or unexported fields
}
System is one runtime instance. All of its exported functions and methods are safe for concurrent use when *System is the Spawner or Sender.
func New ¶
New builds a system on a pool of worker goroutines that run actor turns, creates its two guardians and returns it. Turns of different actors can run in parallel on different workers; the turns of one actor never overlap. WithWorkers, WithThroughput and WithEffectPool configure the pools. The system also samples the live heap for memory admission (WithAdmission) and logs each turn that runs for at least the watchdog timeout at level Error, with the stack of the goroutine running it. The watchdog is on by default, with DefaultWatchdogTimeout; WithWatchdog sets the timeout or turns it off. New panics with an error wrapping ErrInvalidOption if an option is nil or invalid.
func (*System) Estimates ¶
func (s *System) Estimates(n int) []ActorEstimate
Estimates returns up to n live actors with the largest estimates, largest first, ties in byte order of Addr.String(). It returns nil when n <= 0 or estimates are off.
func (*System) Incarnation ¶
Incarnation returns the epoch the system started with: the faultline node incarnation in simulation, a random value otherwise. It is never 0 and does not change for the system's life, even when uid allocation moves to a new epoch.
func (*System) Node ¶
Node returns the system's node name: the node part of every address it allocates.
func (*System) Shutdown ¶
Shutdown stops every actor, user actors first, then system actors, and waits until every Stopped hook has returned. It returns nil when the system is stopped, or ctx.Err() if ctx ends first; shutdown then continues and a later call waits again. Called during a turn (an actor may shut down its own system) or on an engine that cannot block, such as the simulation engine, it starts the shutdown and returns nil at once without waiting.
type Terminated ¶
type Terminated struct {
Addr Address
Reason StopReason
}
Terminated reports that a watched actor stopped for good, or that the watched address was not alive (Reason NoActor). Reason is the actor's final stop reason, or NoActor.
Source Files
¶
- actor.go
- address.go
- admission.go
- ask.go
- asktable.go
- cell.go
- context.go
- context_log.go
- control.go
- discard.go
- doc.go
- effect.go
- envelope.go
- errors.go
- estimates.go
- group.go
- guard.go
- guardian.go
- hold.go
- hooks.go
- host.go
- lifecycle.go
- lookup.go
- mailbox.go
- names.go
- obs.go
- obs_log.go
- options.go
- overload.go
- overload_prod.go
- overloaded.go
- pipe.go
- rand.go
- reply.go
- restart.go
- send.go
- signal.go
- signals_sup.go
- spawn.go
- stop.go
- stopreason.go
- strategy.go
- stuck.go
- supervision.go
- supervisor.go
- system.go
- timers.go
- turn.go
- uid.go
- watch.go
Directories
¶
| Path | Synopsis |
|---|---|
|
analysis
module
|
|
|
Package dedup holds the state that exactly-once protocols keep in an actor: a window of request keys and tombstones (Window), the reply to each handled request (Replies), and the highest version seen per entity (Versions).
|
Package dedup holds the state that exactly-once protocols keep in an actor: a window of request keys and tombstones (Window), the reply to each handled request (Replies), and the highest version seen per entity (Versions). |
|
internal
|
|
|
dettest
Package dettest is lockstep's determinism lint, import-boundary check, exported-API snapshot generator, run-twice switch and golden-hash file format.
|
Package dettest is lockstep's determinism lint, import-boundary check, exported-API snapshot generator, run-twice switch and golden-hash file format. |
|
engine
Package engine is the seam between the lockstep runtime and the engines that run it (architecture §12, RFC-0001).
|
Package engine is the seam between the lockstep runtime and the engines that run it (architecture §12, RFC-0001). |
|
engine/enginetest
Package enginetest is a deterministic manual engine and a recording tracer for unit tests of the runtime.
|
Package enginetest is a deterministic manual engine and a recording tracer for unit tests of the runtime. |
|
engine/local
Package local is the single-goroutine engine behind lockstep.New in Phase 1 (architecture §12.3): one loop goroutine runs every turn, timer function and effect completion.
|
Package local is the single-goroutine engine behind lockstep.New in Phase 1 (architecture §12.3): one loop goroutine runs every turn, timer function and effect completion. |
|
hooks
Package hooks connects packages that must not import each other.
|
Package hooks connects packages that must not import each other. |
|
mailbox
Package mailbox holds the two queues of an actor: the bounded user Queue with its full-mailbox policy, and the unbounded SystemQueue for lifecycle commands and signals (MBX-030 to MBX-080).
|
Package mailbox holds the two queues of an actor: the bounded user Queue with its full-mailbox policy, and the unbounded SystemQueue for lifecycle commands and signals (MBX-030 to MBX-080). |
|
overload
Package overload holds the parts of overload protection every engine shares: the actor cap, the admission gate and the hysteresis switch (OVL §4.2).
|
Package overload holds the parts of overload protection every engine shares: the actor cap, the admission gate and the hysteresis switch (OVL §4.2). |
|
prodengine
Package prodengine is the production engine behind lockstep.New from Phase 2 (architecture §13): a fixed pool of workers runs every turn and callback.
|
Package prodengine is the production engine behind lockstep.New from Phase 2 (architecture §13): a fixed pool of workers runs every turn and callback. |
|
registry
Package registry holds a system's actor names, child lists, generated-name counters and watch tables (ACT-500).
|
Package registry holds a system's actor names, child lists, generated-name counters and watch tables (ACT-500). |
|
supervise
Package supervise holds the pure logic of actor lifecycle and supervision: the cell state machine, directives, restart limits, sliding-window restart intensity and capped exponential backoff (SUP-010, SUP-180, SUP-270, SUP-280, SUP-370).
|
Package supervise holds the pure logic of actor lifecycle and supervision: the cell state machine, directives, restart limits, sliding-window restart intensity and capped exponential backoff (SUP-010, SUP-180, SUP-270, SUP-280, SUP-370). |
|
toys
Package toys holds the toy actor systems of the golden suite: small actors with fixed behavior whose simulation runs have the same trace on every machine (DET-200).
|
Package toys holds the toy actor systems of the golden suite: small actors with fixed behavior whose simulation runs have the same trace on every machine (DET-200). |
|
Package persist provides event-sourced actors.
|
Package persist provides event-sourced actors. |
|
filestore
Package filestore stores the events and snapshots of persistent actors in files.
|
Package filestore stores the events and snapshots of persistent actors in files. |
|
Package sim runs lockstep actor systems as seeded faultline simulations inside go test.
|
Package sim runs lockstep actor systems as seeded faultline simulations inside go test. |