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
| State | Description |
|---|---|
starting | Pre-start hooks running, process is being launched |
running | Process is executing and has a PID |
stopping | Stop signal sent, waiting for process to exit |
stopped | Process has exited cleanly or was stopped by user |
crashed | Process exited unexpectedly with a non-zero exit code |
errored | Supervisor encountered an error managing the process |
waiting | Process 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 State | Display |
|---|---|
starting | starting |
running | running |
stopping | stopping |
stopped | stopped |
crashed | crashed |
errored | error |
waiting | waiting |
State and Restart Policy
The state machine interacts with the restart policy:
| Restart Policy | On Exit Code 0 | On Exit Code ≠ 0 |
|---|---|---|
always | stopped → starting | crashed → waiting → starting |
on-failure | stopped (no restart) | crashed → waiting → starting |
never | stopped (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:
- Exact ID match —
"1"→ process with NumericID 1 - Exact name match —
"api"→ process named "api" - 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
- Supervisor — How the supervisor uses the state machine
- Restart Policies — Configurable restart behavior and backoff
- Process Lifecycle — Full lifecycle with hooks