ractor-supervisor 0.1.9

Supervisor module for ractor framework.
Documentation
# ractor-supervisor

An **OTP-style supervisor** for the [`ractor`](https://docs.rs/ractor) framework—helping you build **supervision trees** in a straightforward, Rust-centric way.

Inspired by the Elixir/Erlang supervision concept, `ractor-supervisor` provides a robust mechanism for overseeing **one or more child actors** and automatically restarting them under configurable policies. If too many restarts happen in a brief time window—a "meltdown"—the supervisor itself shuts down abnormally, preventing errant restart loops.

## Supervisor Types

This crate provides three types of supervisors, each designed for specific use cases:

### 1. Static Supervisor (`Supervisor`)
- Manages a fixed set of children defined at startup
- Supports all supervision strategies (OneForOne, OneForAll, RestForOne)
- Best for static actor hierarchies where child actors are known at startup
- Example: A web server with predefined worker pools, cache managers, and connection handlers
- [See test examples]src/supervisor.rs#L756-L1205

### 2. Dynamic Supervisor (`DynamicSupervisor`)
- Allows adding/removing children at runtime
- Uses OneForOne strategy only (each child managed independently)
- Optional `max_children` limit
- Best for dynamic workloads where children are spawned/terminated on demand
- Example: A job queue processor that spawns worker actors based on load
- [See test examples]src/dynamic.rs#L606-L1020

### 3. Task Supervisor (`TaskSupervisor`)
- Specialized version of DynamicSupervisor for managing async tasks
- Wraps futures in actor tasks that can be supervised
- Simpler API focused on task execution rather than actor management
- Best for background jobs, periodic tasks, or any async work needing supervision
- Example: Scheduled jobs, background data processing, or cleanup tasks
- [See test examples]src/task.rs#L265-L299

## Supervision Strategies

The strategy defines what happens when a child fails:

- **OneForOne**: Only the failing child is restarted.
- **OneForAll**: If any child fails, all children are stopped and restarted.
- **RestForOne**: The failing child and all subsequent children (in definition order) are stopped and restarted.

Strategies apply to **all failure scenarios**, including:
- Spawn errors (failures in `pre_start`/`post_start`)
- Runtime panics
- Normal and abnormal exits

Example: If spawning a child fails during pre_start, it will count as a restart and trigger strategy logic.

## Common Features

### Restart Policies
- **Permanent**: Always restart, no matter how the child exited.
- **Transient**: Restart only if the child exited abnormally (panic or error).
- **Temporary**: Never restart, regardless of exit reason.

### Meltdown Logic
- **`max_restarts`** and **`max_window`**: The "time window" for meltdown counting, expressed as a `Duration`. If more than `max_restarts` occur within `max_window`, the supervisor shuts down abnormally (meltdown).
- **`reset_after`**: If the supervisor sees no failures for the specified duration, it clears its meltdown log and effectively resets the meltdown counters.

### Child-Level Features
- **`reset_after`** (per child): If a specific child remains up for the given duration, its own failure count is reset to zero on the next failure.
- **`backoff_fn`**: An optional function to delay a child's restart. For instance, you might implement exponential backoff to prevent immediate thrashing restarts.

## Important Requirements

1. **Actor Names**: Both supervisors and their child actors **must** have names set. These names are used for:
   - Unique identification in the supervision tree
   - Meltdown tracking and logging
   - Global actor registry

2. **Proper Spawning**: When spawning supervisors or child actors, always use:
   - `Supervisor::spawn_linked` or `Supervisor::spawn` for static supervisors
   - `DynamicSupervisor::spawn_linked` or `DynamicSupervisor::spawn` for dynamic supervisors
   - Do NOT use the generic `Actor::spawn_linked` directly

## Multi-Level Supervision Trees

Supervisors can manage other **supervisors** as children, forming a **hierarchical** or **tree** structure. This way, different subsystems can each have their own meltdown thresholds or strategies. A meltdown in one subtree doesn't necessarily mean the entire application must go down, unless the top-level supervisor is triggered.

For example:
```text
Root Supervisor (Static, OneForOne)
├── API Supervisor (Static, OneForAll)
│   ├── HTTP Server
│   └── WebSocket Server
├── Worker Supervisor (Dynamic)
│   └── [Dynamic Worker Pool]
└── Task Supervisor
    └── [Background Jobs]
```

## Example Usage

Here's a complete example using a static supervisor:

```rust
use ractor::Actor;
use ractor_supervisor::*;
use ractor::concurrency::Duration;
use tokio::time::Instant;
use futures_util::FutureExt;

// A minimal child actor that simply does some work in `handle`.
struct MyWorker;

#[ractor::async_trait]
impl Actor for MyWorker {
    type Msg = ();
    type State = ();
    type Arguments = ();

    // Called before the actor fully starts. We can set up the actor's internal state here.
    async fn pre_start(
        &self,
        _myself: ractor::ActorRef<Self::Msg>,
        _args: Self::Arguments,
    ) -> Result<Self::State, ractor::ActorProcessingErr> {
        Ok(())
    }

    // The main message handler. This is where you implement your actor's behavior.
    async fn handle(
        &self,
        _myself: ractor::ActorRef<Self::Msg>,
        _msg: Self::Msg,
        _state: &mut Self::State
    ) -> Result<(), ractor::ActorProcessingErr> {
        // do some work...
        Ok(())
    }
}

// A function to spawn the child actor. This will be used in ChildSpec::spawn_fn.
async fn spawn_my_worker(
    supervisor_cell: ractor::ActorCell,
    child_id: String
) -> Result<ractor::ActorCell, ractor::SpawnErr> {
    // We name the child actor using `child_spec.id` (though naming is optional).
    let (child_ref, _join) = Supervisor::spawn_linked(
        Some(child_id), // actor name
        MyWorker,       // actor instance
        (),             // arguments
        supervisor_cell // link to the supervisor
    ).await?;
    Ok(child_ref.get_cell())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // A child-level backoff function that implements exponential backoff after the second failure.
    // Return Some(delay) to make the supervisor wait before restarting this child.
    let my_backoff: ChildBackoffFn = Arc::new(
        |_child_id: &str, restart_count: usize, last_fail: Instant, child_reset_after: Option<u64>| {
            // On the first failure, restart immediately (None).
            // After the second failure, double the delay each time (exponential).
            if restart_count <= 1 {
                None
            } else {
                Some(Duration::from_secs(1 << restart_count))
            }
        }
    );

    // This specification describes exactly how to manage our single child actor.
    let child_spec = ChildSpec {
        id: "myworker".into(),  // Unique identifier for meltdown logs and debugging.
        restart: Restart::Transient, // Only restart if the child fails abnormally.
        spawn_fn: SpawnFn::new(|cell, id| spawn_my_worker(cell, id).boxed()),
        backoff_fn: Some(my_backoff), // Apply our custom exponential backoff on restarts.
        // If the child remains up for 60s, its individual failure counter resets on the next failure.
        reset_after: Some(Duration::from_secs(60)),
    };

    // Supervisor-level meltdown configuration. If more than 5 restarts occur within a 10s window, meltdown is triggered.
    // Also, if we stay quiet for 30s (no restarts), the meltdown log resets.
    let options = SupervisorOptions {
        strategy: SupervisorStrategy::OneForOne,         // If one child fails, only that child is restarted.
        max_restarts: 5,                                   // Permit up to 5 restarts in the meltdown window.
        max_window: Duration::from_secs(10),               // The meltdown window.
        reset_after: Some(Duration::from_secs(30)),        // If no failures for 30s, meltdown log is cleared.
    };

    // Group all child specs and meltdown options together:
    let args = SupervisorArguments {
        child_specs: vec![child_spec], // We only have one child in this example
        options,
    };

    // Spawn the supervisor with our arguments.
    let (sup_ref, sup_handle) = Supervisor::spawn(
        "root".into(), // name for the supervisor
        args
    ).await?;

    let _ = sup_ref.kill();
    let _ = sup_handle.await;

    Ok(())
}
```

For more examples, see the test files:
- [Static Supervisor Tests]src/supervisor.rs#L756-L1205
- [Dynamic Supervisor Tests]src/dynamic.rs#L606-L1020
- [Task Supervisor Tests]src/task.rs#L265-L299