#tree #worker #erlang-otp #supervision #tree-root #supervisor #restart

supertrees

Supervision trees for Tokio-based services inspired by Erlang/OTP

3 releases

0.1.2 Apr 9, 2024
0.1.1 Apr 9, 2024
0.1.0 Apr 9, 2024

#2 in #erlang-otp

Download history 263/week @ 2024-04-07 10/week @ 2024-04-14 1/week @ 2024-05-19 134/week @ 2024-06-09 6/week @ 2024-06-16

140 downloads per month

LGPL-3.0-only

2MB
525 lines

Docs Crates.io Build & test

supertrees – Rusty supervision trees

Trees with little gremlins and the moon

An experimental Rust crate that implements supervision trees in the spirit of Erlang/OTP.

For details, please refer to the documentation.


lib.rs:

Supervision trees for async Rust, inspired by Erlang/OTP

This crate provides a supervision tree implementation for async Rust, inspired by Erlang/OTP. It provides a Supertree struct that can be used to create a supervision tree, and a Worker trait that can be implemented by workers that can be added to the supervision tree.

This crate is designed to be used with async Rust and the Tokio runtime, but it could theoretically be used with other async runtimes as well.

In its current state, this crate is considered experimental and should not be used for production services, unless you are very excited about the idea and would be willing to contribute to the development of the crate. Notably, this crate lacks a lot of the features that are present in Erlang/OTP, such as monitoring, tracing, and distributed messaging, although Tokio provides a tracing and metrics system that could be used in conjunction with this crate (it has just not been tested yet).

For detailed examples, refer to the integration tests in the tests directory.

Features

  • Supervision trees: Create a supervision tree with a root supervisor
  • Workers: Add workers to the supervision tree
  • Async workers: Workers are async and can use async/await syntax
  • Process isolation: The tree is constructed by forking processes, providing additional isolation (IPC not currently implemented)
  • Hierarchical structure: Create a hierarchy of workers and supervisors
  • Restart policies: Define restart policies for workers
  • Backoff policies: Define backoff policies for workers

Comparison to Erlang/OTP

  • Unlike Erlang/OTP, this crate does not provide a distributed messaging. Another crate (courtesy chez moi) to look at is genserver, however it does not provide IPC.
  • This crate does not provide monitoring or tracing, but Tokio itself includes a tracing and metrics system that could be used in conjunction with this crate.
  • Erlang/OTP uses a preemptive green-threads scheduler, while this crate uses the Tokio runtime, which is a cooperative multitasking runtime.
  • Each separate supervisor within the tree is a separate thread, and there's no shared memory or IPC between them (yet, PRs welcome). This is in contrast to Erlang/OTP, which uses a shared-nothing architecture.
  • Erlang/OTP is battle-tested and has been used in production for decades, whereas this crate is not.

Example

A basic example of using this crate to create a supervision tree with a root supervisor, two sub-supervisors, and three workers:

use supertrees::{Restartable, Supertree, Worker};

#[derive(Debug)]
struct MyWorker {
    num: i32,
}

impl MyWorker {
    fn new(num: i32) -> Self {
        Self { num }
    }
}

impl Worker for MyWorker {
    // init() is called before the worker is started, and will be called
    // after each subsequent restart, so it should be safe to call this
    // repeatedly and  any state that needs to be reset should be reset here.
    fn init(
        &self,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'static>> {
        let num = self.num;
        Box::pin(async move {
            println!("hi, I'm worker num={num} :)");
        })
    }
}

// We must provide a restart and backoff policy for the worker, but we can
// use the default policies.
impl Restartable for MyWorker {}

let root = Supertree::new()
    .add_worker(MyWorker::new(1))
    .add_supervisor(|s| {
        s.add_worker(MyWorker::new(2))
            .add_supervisor(|s| s.add_worker(MyWorker::new(3)))
    });
dbg!(&root);

// Now you can start the supervision tree, which will run forever.
// Uncomment the line below to run the supervision tree.
// root.start();

Dependencies