Skip to main content

Process State Machine

Every process managed by Runix transitions through a well-defined state machine. The implementation uses lock-free atomic operations for maximum concurrency.

The 7 States​

StateDescription
startingPre-start hooks running, process is being launched
runningProcess is executing and has a PID
stoppingStop signal sent, waiting for process to exit
stoppedProcess has exited cleanly or was stopped by user
crashedProcess exited unexpectedly with a non-zero exit code
erroredSupervisor encountered an error managing the process
waitingProcess is in backoff wait before next restart attempt

State Transitions​

Implementation​

The state machine is defined in pkg/types/process.go:

type ProcessState string

const (
StateStarting ProcessState = "starting"
StateRunning ProcessState = "running"
StateStopping ProcessState = "stopping"
StateStopped ProcessState = "stopped"
StateCrashed ProcessState = "crashed"
StateErrored ProcessState = "errored"
StateWaiting ProcessState = "waiting"
)

Valid Transitions Map​

var ValidTransitions = map[ProcessState][]ProcessState{
StateStarting: {StateRunning, StateErrored},
StateRunning: {StateStopping, StateCrashed},
StateStopping: {StateStopped},
StateStopped: {StateStarting},
StateCrashed: {StateWaiting, StateStopped, StateStarting},
StateWaiting: {StateStarting, StateStopped},
StateErrored: {StateStopped, StateStarting},
}

Atomic Compare-and-Swap​

State transitions use an atomic CAS loop in internal/supervisor/process.go:

func (p *Process) transitionTo(target ProcessState) error {
for {
current := p.state.Load().(ProcessState)
if !isValidTransition(current, target) {
return fmt.Errorf("invalid transition: %s → %s", current, target)
}
if p.state.CompareAndSwap(current, target) {
return nil
}
// CAS failed — another goroutine changed the state; retry
}
}

This ensures:

  • No two goroutines can transition a process to the same state simultaneously
  • Invalid transitions are rejected atomically
  • No mutex contention on the hot path

Display States​

The CLI maps internal states to user-friendly display strings:

Internal StateDisplay
startingstarting
runningrunning
stoppingstopping
stoppedstopped
crashedcrashed
errorederror
waitingwaiting

State and Restart Policy​

The state machine interacts with the restart policy:

Restart PolicyOn Exit Code 0On Exit Code ≠ 0
alwaysstopped → startingcrashed → waiting → starting
on-failurestopped (no restart)crashed → waiting → starting
neverstopped (no restart)crashed (no restart)

When max_restarts is reached, the process stays in crashed regardless of policy.

Process Lookup​

Process lookup by target string supports three modes:

  1. Exact ID match — "1" → process with NumericID 1
  2. Exact name match — "api" → process named "api"
  3. Unique ID prefix — "abc" → process whose UUID starts with "abc"

If a prefix matches multiple processes, an error is returned to prevent ambiguity.

What's Next​