The project is in a healthy, maintained state
A supervision tree that restarts what died the way OTP would: one_for_one, one_for_all or rest_for_one, bounded by a restart intensity that escalates instead of thrashing. The decisions come from the no_std supervisor-machines crate; Ruby owns the threads, the restart backoff comes from chrono_machines and the shutdown budget from deadline_machines.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

SupervisorMachines 🔁

OTP-style supervision trees over Ruby threads. The command vessel that notices a station has gone quiet and decides who returns to duty.

A child died. Which children restart, and has that happened too often to keep trying? Those answers come from the no_std supervisor-machines crate. Everything the core refuses to touch, spawning, joining, killing, ordered shutdown, is this gem. AMS Armstrong is the bridge around that decision.


🚀 Launch Sequence

# Add to your ship's Gemfile
gem 'supervisor_machines'

Requires Ruby 4.0+ and a Rust toolchain, or a platform gem: the core is a native extension, not an option.


🎯 Mission Brief

SM = SupervisorMachines

tree = SM::Supervisor.new(strategy: :one_for_all, restarts: 3, window: 60.0)
tree.supervise(:cache)  { Thread.new { cache.run } }
tree.supervise(:warmup, policy: :transient) { Thread.new { warm_the_cache } }
tree.start

tree.children    # => [:cache, :warmup]
tree.restarts    # => 0
tree.terminated? # => false

tree.stop(grace: 5.0)

A starter block returns the Thread that runs the child, and is called again for every restart. A child that stops is noticed, handed to the core, and whatever the core decides is carried out.


🔄 Lifecycle

A tree registers children, runs them, and ends either stopped or failed. Both endings are final: start raises SupervisorMachines::Error for a tree that is already running, has been stopped, or gave up. There is one generation of children per tree, and starting a second one is a bug, not a refresh. supervise is refused the same way once start has fixed the shape of the tree: a tree past registering cannot take on a child.

A start that cannot finish, because a starter raises, takes the children it already brought up back down in reverse start order, leaves the tree failed, and raises what the starter raised. Nothing is left running and nothing is left unsupervised. stop then raises Escalated, like any other tree that gave up, every time it is called, and a tree that was never started has nothing to take down.


🎛️ Strategies

:one_for_one restarts only what died. :one_for_all restarts every child, for a set that only works together. :rest_for_one restarts the child and everything started after it, for a chain where later children depend on earlier ones.

One restart is recorded per failure handled, not per child brought back, so the strategy does not change how fast the restart intensity is spent. That is OTP's accounting.


📋 Standing Orders

A child's policy is its standing order. :permanent always comes back, even from a clean exit. :transient comes back only from a bad one, because a clean exit means it finished. :temporary never comes back, and never spends the restart budget.

tree.supervise(:migration, policy: :temporary) { Thread.new { migrate } }

🔥 Restart Intensity

restarts within window seconds is all the thrashing a tree will do. Past it, the tree stops its remaining children in reverse start order and raises SupervisorMachines::Escalated, and the supervisor's own supervisor now owns the problem. That decision is sticky: terminated? stays true and every later stop raises again.


🚫 Rules of Engagement

  • The delay between a restart decision and the respawn comes from chrono_machines. Pass your own backoff: to change the curve.
  • The shutdown budget comes from deadline_machines: stop(grace:) bounds the whole ordered teardown, and a child that outstays it is left behind rather than waited on forever.
  • Restart intensity, strategy spans and policy decisions come from the core crate. None of it is reimplemented here.

📐 Shape

The core carries its child count in its type, so a tree is dispatched to one of SupervisorMachines::Decider::SUPPORTED_CHILDREN monomorphised shapes: 1 through 16, and 32. Any other count raises ArgumentError from #start.

Threads are the only supervised unit.


📡 Credits

AMS Armstrong, NCC-3186, Armstrong-class command vessel.

Named for Joe Armstrong's OTP supervision trees, which settled that the way to keep a system up is to let a part of it die and have something above it decide what comes back.


License

MIT.