Documentation
¶
Overview ¶
Package rt is a cooperative, single-stepping scheduler for real Go goroutines. Every spawned goroutine is a genuine go statement, but a turnstile only ever lets one run at a time, so an entire program's interleaving becomes seeded and replayable.
Example ¶
package main
import (
"fmt"
"github.com/arshnah/detsim/rt"
)
func main() {
sched := rt.NewSched(1)
ch := rt.NewChan[int](sched, 0)
sched.Go(func() {
for i := 1; i <= 3; i++ {
ch.Send(i * 10)
}
ch.Close()
})
sched.Go(func() {
for {
v, ok := ch.RecvOK()
if !ok {
return
}
fmt.Println("received:", v)
}
})
sched.Run()
}
Output:
Index ¶
- Variables
- func DecisionLimitFromEnv(envVar string) int
- func DumpTraceOnFailure(t TestingT, sched *Sched, path string)
- func DumpTraceToEnvPath(t TestingT, sched *Sched, envVar string)
- func SaveTrace(path string, trace Trace) error
- func SeedFromEnv(name string, fallback int64) int64
- type Addr
- type Chan
- type Cond
- type Conn
- type DeadlockError
- type DeadlockGoroutine
- type File
- func (f *File) Close() error
- func (f *File) Name() string
- func (f *File) Read(p []byte) (int, error)
- func (f *File) ReadAt(p []byte, off int64) (int, error)
- func (f *File) Seek(offset int64, whence int) (int64, error)
- func (f *File) Sync() error
- func (f *File) Write(p []byte) (int, error)
- func (f *File) WriteAt(p []byte, off int64) (int, error)
- type FileInfo
- type FileSystem
- func (fs *FileSystem) Create(name string) (*File, error)
- func (fs *FileSystem) Open(name string) (*File, error)
- func (fs *FileSystem) ReadFile(name string) ([]byte, error)
- func (fs *FileSystem) Remove(name string) error
- func (fs *FileSystem) Rename(oldName, newName string) error
- func (fs *FileSystem) Stat(name string) (FileInfo, error)
- func (fs *FileSystem) WriteFile(name string, data []byte) error
- type Listener
- type Mutex
- type Network
- func (n *Network) Dial(addr string) (*Conn, error)
- func (n *Network) DialFrom(from, to string) (*Conn, error)
- func (n *Network) HealAddr(addr string)
- func (n *Network) HealAll()
- func (n *Network) Listen(addr string) (*Listener, error)
- func (n *Network) Partition(groupA, groupB []string)
- func (n *Network) PartitionAddr(addr string)
- func (n *Network) SetDelayRange(min, max VirtualTime)
- func (n *Network) SetDropRate(rate float64)
- type NoveltySearchConfig
- type NoveltySearchResult
- type Once
- type PanicError
- type RWMutex
- type Sched
- func (s *Sched) After(d VirtualTime) *Chan[VirtualTime]
- func (s *Sched) Go(fn func())
- func (s *Sched) GoNamed(name string, fn func())
- func (s *Sched) Now() VirtualTime
- func (s *Sched) Run() error
- func (s *Sched) Select(cases ...SelectCase)
- func (s *Sched) SetDecisionLimit(n int)
- func (s *Sched) Shutdown()
- func (s *Sched) Sleep(d VirtualTime)
- func (s *Sched) Trace() Trace
- type SelectCase
- type Step
- type TestingT
- type Trace
- type VirtualTime
- type WaitGroup
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrTraceExhausted = errors.New("detsim/rt: replay trace ended before the scheduler quiesced, the code under test has changed") ErrTraceMismatch = errors.New("detsim/rt: replay trace expected a goroutine that was never ready, the code under test has changed") )
ErrTraceExhausted and ErrTraceMismatch mean the code under test has changed since the trace was recorded.
var ErrDecisionLimit = errors.New("detsim/rt: scheduler stopped after reaching its decision limit (peek mode)")
ErrDecisionLimit is returned by Run when SetDecisionLimit cuts a run short.
Functions ¶
func DecisionLimitFromEnv ¶
DecisionLimitFromEnv reads a decision limit from envVar. 0 means no limit.
func DumpTraceOnFailure ¶
DumpTraceOnFailure saves sched's trace to path only if t ends up failed.
func DumpTraceToEnvPath ¶
DumpTraceToEnvPath saves sched's trace to the path named by envVar, regardless of pass/fail.
func SeedFromEnv ¶
SeedFromEnv reads an int64 seed from the named environment variable, or returns fallback.
Types ¶
type Addr ¶
type Addr struct {
// contains filtered or unexported fields
}
Addr is a network address, matching the shape of net.Addr.
type Chan ¶
type Chan[T any] struct { // contains filtered or unexported fields }
Chan is a generic channel that blocks through the scheduler instead of the Go runtime.
func (*Chan[T]) Close ¶
func (c *Chan[T]) Close()
Close closes the channel, panicking if already closed.
func (*Chan[T]) Recv ¶
func (c *Chan[T]) Recv() T
Recv blocks until a value is available, returning the zero value if closed and drained.
type Cond ¶
type Cond struct {
L *Mutex
// contains filtered or unexported fields
}
Cond is a deterministic counterpart to sync.Cond.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a connected pair of endpoints, one side's Write delivering to the other's Read.
func (*Conn) RemoteAddr ¶
RemoteAddr returns the connection's remote address.
type DeadlockError ¶
type DeadlockError struct {
Goroutines []DeadlockGoroutine
}
DeadlockError is returned by Run when nothing is runnable and something is unfinished.
func (*DeadlockError) Error ¶
func (e *DeadlockError) Error() string
Error renders one block per stuck goroutine, its ID, blocking reason, and stack trace.
type DeadlockGoroutine ¶
DeadlockGoroutine describes one goroutine stuck when a DeadlockError was returned.
type File ¶
type File struct {
// contains filtered or unexported fields
}
File implements Read, Write, Sync, Close against a backing FaultyStorage.
func (*File) Read ¶
Read returns io.EOF at end of file, matching os.File: a short read due to EOF reports nil and the following read returns (0, io.EOF).
func (*File) Seek ¶
Seek repositions the file offset, supporting io.SeekStart, io.SeekCurrent, and io.SeekEnd relative to the materialized size.
type FileInfo ¶
type FileInfo struct {
// contains filtered or unexported fields
}
FileInfo is a minimal io/fs.FileInfo-shaped value.
type FileSystem ¶
type FileSystem struct {
// contains filtered or unexported fields
}
FileSystem is a deterministic stand-in for os.Open/os.Create, backed by FaultyStorage.
func NewFileSystem ¶
func NewFileSystem(s *Sched, profile detsim.FaultProfile) *FileSystem
NewFileSystem builds a FileSystem where every created file shares profile.
func (*FileSystem) Create ¶
func (fs *FileSystem) Create(name string) (*File, error)
Create creates or truncates a file backed by a fresh FaultyStorage.
func (*FileSystem) Open ¶
func (fs *FileSystem) Open(name string) (*File, error)
Open opens an existing file, failing with fs.ErrNotExist if it was never created.
func (*FileSystem) ReadFile ¶
func (fs *FileSystem) ReadFile(name string) ([]byte, error)
ReadFile is a whole-file convenience helper, equivalent to os.ReadFile.
func (*FileSystem) Remove ¶
func (fs *FileSystem) Remove(name string) error
Remove deletes a file, failing with fs.ErrNotExist if it doesn't exist.
func (*FileSystem) Rename ¶
func (fs *FileSystem) Rename(oldName, newName string) error
Rename moves a file, failing with fs.ErrNotExist if the source doesn't exist.
type Listener ¶
type Listener struct {
// contains filtered or unexported fields
}
Listener accepts incoming connections at a registered address.
type Mutex ¶
type Mutex struct {
// contains filtered or unexported fields
}
Mutex is a deterministic counterpart to sync.Mutex.
type Network ¶
type Network struct {
// contains filtered or unexported fields
}
Network is a deterministic, in-process stand-in for net.Dial/net.Listen.
func NewNetwork ¶
NewNetwork builds a Network on s's clock with a default 1-5 delivery delay range.
func (*Network) DialFrom ¶
DialFrom connects to to from local address from, so partition checks are address-pair-aware.
func (*Network) HealAll ¶
func (n *Network) HealAll()
HealAll clears every mesh partition set by Partition, not PartitionAddr isolation.
func (*Network) Partition ¶
Partition cuts off every address in groupA from every address in groupB, both directions.
func (*Network) PartitionAddr ¶
PartitionAddr fully isolates addr from every other address, in both directions.
func (*Network) SetDelayRange ¶
func (n *Network) SetDelayRange(min, max VirtualTime)
SetDelayRange sets the random delay range applied to delivered writes.
func (*Network) SetDropRate ¶
SetDropRate sets the probability a write is silently dropped.
type NoveltySearchConfig ¶
NoveltySearchConfig configures NoveltySearch. Zero values fall back to defaults.
type NoveltySearchResult ¶
type NoveltySearchResult struct {
TrialsRun int
DistinctTraces int
StoppedDry bool
FailedSeed int64
FailedErr error
}
NoveltySearchResult reports how a NoveltySearch run went.
func NoveltySearch ¶
func NoveltySearch(cfg NoveltySearchConfig, trial func(seed int64) (Trace, error)) NoveltySearchResult
NoveltySearch tries increasing seeds until DryLimit consecutive trials produce no new schedule shape, calling trial once per seed.
type Once ¶
type Once struct {
// contains filtered or unexported fields
}
Once is a deterministic counterpart to sync.Once.
type PanicError ¶
PanicError is returned by Run when a scheduled goroutine panics.
func (*PanicError) Error ¶
func (e *PanicError) Error() string
Error reports the recovered panic value and its stack trace.
type RWMutex ¶
type RWMutex struct {
// contains filtered or unexported fields
}
RWMutex is a deterministic counterpart to sync.RWMutex.
func (*RWMutex) Lock ¶
func (m *RWMutex) Lock()
Lock blocks for exclusive access, excluding all readers and other writers.
func (*RWMutex) RLock ¶
func (m *RWMutex) RLock()
RLock blocks for shared read access, excluded only by a current writer.
type Sched ¶
Sched is the deterministic goroutine scheduler.
Example (Deadlock) ¶
package main
import (
"fmt"
"github.com/arshnah/detsim/rt"
)
func main() {
sched := rt.NewSched(1)
a := rt.NewChan[int](sched, 0)
b := rt.NewChan[int](sched, 0)
sched.Go(func() {
a.Send(1)
b.Recv()
})
sched.Go(func() {
b.Send(1)
a.Recv()
})
err := sched.Run()
if _, ok := err.(*rt.DeadlockError); ok {
fmt.Println("deadlock detected")
}
}
Output:
func NewSched ¶
NewSched builds a Sched that picks among ready goroutines uniformly at random, seeded.
func NewSchedFromTrace ¶
NewSchedFromTrace builds a Sched that replays trace exactly, erroring on any mismatch.
func NewSchedFromTraceLenient ¶
NewSchedFromTraceLenient replays trace but falls back to a random pick on mismatch instead of erroring.
func SchedFromEnv ¶
SchedFromEnv builds a Sched from traceEnv's trace file if set, otherwise from seedEnv's seed.
func (*Sched) After ¶
func (s *Sched) After(d VirtualTime) *Chan[VirtualTime]
After is the rt equivalent of time.After: sends the wakeup time on a fresh channel.
func (*Sched) Go ¶
func (s *Sched) Go(fn func())
Go spawns fn as a new scheduled goroutine, eligible to run once Run picks it.
func (*Sched) GoNamed ¶
GoNamed is Go with a human-readable name for the goroutine. Names show up in traces (and therefore in detsim-trace output), which is the difference between reading "g7 ran" and "g7 sender ran" when staring at a minimized failure.
func (*Sched) Now ¶
func (s *Sched) Now() VirtualTime
Now returns the scheduler's current virtual time.
func (*Sched) Run ¶
Run drives the scheduler until every goroutine finishes, a deadlock is detected, a goroutine panics, or the decision limit is hit.
func (*Sched) Select ¶
func (s *Sched) Select(cases ...SelectCase)
Select evaluates every case, picking uniformly at random among the ready ones.
func (*Sched) SetDecisionLimit ¶
SetDecisionLimit caps Run to n scheduling decisions. n <= 0 means unlimited.
func (*Sched) Shutdown ¶
func (s *Sched) Shutdown()
Shutdown releases every goroutine the scheduler spawned that hasn't finished, so a run that ended in deadlock, a panic, or a decision limit doesn't leak them for the life of the process. Call it after Run returns, never while it's running; it's safe to call repeatedly and on an already-quiesced scheduler. Shutdown is terminal: none of the released goroutines resume their code under test.
func (*Sched) Sleep ¶
func (s *Sched) Sleep(d VirtualTime)
Sleep blocks the calling goroutine until virtual time has advanced by at least d.
type SelectCase ¶
type SelectCase struct {
// contains filtered or unexported fields
}
SelectCase is one case in a Select, built by RecvCase, SendCase, or DefaultCase.
func DefaultCase ¶
func DefaultCase(commit func()) SelectCase
DefaultCase fires if no other case is ready when Select is first evaluated.
func RecvCase ¶
func RecvCase[T any](ch *Chan[T], commit func()) SelectCase
RecvCase is ready when ch has a value or is closed. commit performs the actual receive.
func SendCase ¶
func SendCase[T any](ch *Chan[T], commit func()) SelectCase
SendCase is ready when ch has room and isn't closed. commit performs the actual send.
type Step ¶
type Step struct {
At VirtualTime `json:"at"`
Goroutine uint64 `json:"goroutine"`
After string `json:"after,omitempty"`
}
Step describes one scheduling decision: the scheduler picked a goroutine at virtual time At, gave it a turn, and when the turn ended that goroutine either finished or parked with the reason recorded in After ("chan send", "mutex lock", "sleep", ...).
type TestingT ¶
TestingT is the minimal subset of *testing.T DumpTraceOnFailure/DumpTraceToEnvPath need.
type Trace ¶
type Trace struct {
Seed int64 `json:"seed"`
Decisions []uint64 `json:"decisions"`
Labels []string `json:"labels,omitempty"`
Steps []Step `json:"steps,omitempty"`
}
Trace is the exact sequence of scheduling decisions a Run made. Decisions alone are enough to replay a run byte-for-byte. Labels and Steps are optional enrichment for human readers: Labels maps goroutine id to the name it was spawned with (empty for unnamed goroutines, and absent entirely for traces recorded before labels existed), Steps records when each pick happened and what the goroutine blocked on afterward.
type VirtualTime ¶
VirtualTime is simulated time, an alias for time.Duration.