nexus-async-rt 0.7.0

Experimental single-threaded async executor (reference implementation; tokio is the supported path for production use)
Documentation
# Cancellation

Two cancellation primitives:

- **`CancellationToken`** — hierarchical, user-triggered, multi-waiter.
  The building block for graceful shutdown of specific subsystems.
- **`ShutdownSignal`** — single global signal tied to SIGTERM/SIGINT.
  The top-level "time to stop" event.

Use `ShutdownSignal` at the root of your application. Use
`CancellationToken` for everything below it.

## `CancellationToken`

```rust
impl CancellationToken {
    pub fn new() -> Self;
    pub fn child(&self) -> Self;
    pub fn cancel(&self);
    pub fn is_cancelled(&self) -> bool;
    pub fn cancelled(&self) -> Cancelled;  // await-able
    pub fn drop_guard(self) -> DropGuard;
}
```

`.clone()` produces another reference to the **same** token. `.child()`
produces a token that is cancelled when its parent is cancelled (but the
parent is **not** cancelled when the child is). This is hierarchical
cancellation: cancel the root to cancel everything; cancel a child to
cancel just that subtree.

### Basic Pattern: Cancel a Task From Outside

```rust
use nexus_async_rt::{CancellationToken, Runtime, spawn_boxed, sleep};
use nexus_rt::WorldBuilder;
use std::time::Duration;

fn main() {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::new(&mut world);
    rt.block_on(async {
        let token = CancellationToken::new();

        let t = token.clone();
        let worker = spawn_boxed(async move {
            loop {
                if t.is_cancelled() {
                    break;
                }
                sleep(Duration::from_millis(10)).await;
            }
            "clean exit"
        });

        sleep(Duration::from_millis(50)).await;
        token.cancel();
        let result = worker.await;
        assert_eq!(result, "clean exit");
    });
}
```

### `cancelled().await` — Race Against Cancellation

Prefer `cancelled()` over `is_cancelled()` polling when the task is
already waiting on something else. Combine both in a select-style loop.

Since nexus-async-rt has no built-in `select!`, the common pattern is to
split concerns across two spawned tasks joined by a channel, or to poll
both via `Future::poll` manually.

```rust
use nexus_async_rt::{CancellationToken, Runtime, sleep, spawn_boxed};
use nexus_rt::WorldBuilder;
use std::time::Duration;

async fn session_loop(token: CancellationToken) {
    loop {
        if token.is_cancelled() {
            break;
        }
        // Do one unit of work.
        sleep(Duration::from_millis(100)).await;
    }
}

fn main() {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::new(&mut world);
    rt.block_on(async {
        let root = CancellationToken::new();
        let _h = spawn_boxed(session_loop(root.child()));
        sleep(Duration::from_secs(1)).await;
        root.cancel(); // cancels the child too
    });
}
```

### Hierarchical: Parent + Child Tokens

```rust
use nexus_async_rt::{CancellationToken, Runtime, spawn_boxed};
use nexus_rt::WorldBuilder;

fn main() {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::new(&mut world);
    rt.block_on(async {
        let root = CancellationToken::new();

        // Each session gets a child — we can cancel one without touching
        // the others, or cancel root to kill everything.
        for i in 0..4 {
            let child = root.child();
            spawn_boxed(async move {
                child.cancelled().await;
                println!("session {i} shutting down");
            });
        }

        // Cancel everything at once.
        root.cancel();
    });
}
```

### `DropGuard` — RAII Cancellation

```rust
impl CancellationToken {
    pub fn drop_guard(self) -> DropGuard;
}

impl DropGuard {
    pub fn disarm(self) -> CancellationToken;
}
```

A `DropGuard` cancels its token on drop unless disarmed. Use it when a
task's lifetime should bound the work of its children.

```rust
use nexus_async_rt::{CancellationToken, Runtime, spawn_boxed, sleep};
use nexus_rt::WorldBuilder;
use std::time::Duration;

fn main() {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::new(&mut world);
    rt.block_on(async {
        let supervisor = async {
            let token = CancellationToken::new();
            let _guard = token.clone().drop_guard();

            // Spawn children tied to the token.
            for _ in 0..3 {
                let t = token.clone();
                spawn_boxed(async move {
                    t.cancelled().await;
                    // cleanup
                });
            }

            // Supervisor does its work...
            sleep(Duration::from_millis(100)).await;

            // Returning normally drops the guard, which cancels the token,
            // which wakes all the child tasks.
        };

        supervisor.await;
    });
}
```

## `ShutdownSignal`

Top-level shutdown driven by SIGTERM/SIGINT. Installed automatically when
`RuntimeBuilder::signal_handlers(true)` (the default).

```rust
impl ShutdownSignal {
    pub fn current() -> ShutdownSignal;
}
```

`ShutdownSignal` is single-waiter — exactly one task can `.await` it. For
broadcast shutdown, convert the signal into a `CancellationToken::cancel()`
call and distribute the token.

```rust
use nexus_async_rt::{CancellationToken, Runtime, ShutdownSignal, spawn_boxed};
use nexus_rt::WorldBuilder;

fn main() {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::builder(&mut world)
        .signal_handlers(true)
        .build();

    rt.block_on(async {
        let root = CancellationToken::new();

        // Distribute children.
        for _ in 0..4 {
            let child = root.child();
            spawn_boxed(async move {
                child.cancelled().await;
                // drain + close
            });
        }

        // Single-waiter: wait for SIGTERM, then fan out.
        ShutdownSignal::current().await;
        root.cancel();

        // Give children a grace period — in real code, join them on a
        // timeout here.
    });
}
```

## When to Use Which

| Problem                                  | Use                              |
|------------------------------------------|----------------------------------|
| Stop one spawned task                    | `JoinHandle::abort()`            |
| Stop a group of related tasks            | `CancellationToken` + `.child()` |
| Propagate "server is shutting down"      | `ShutdownSignal` → Token         |
| Tie child lifetimes to a scope           | `DropGuard`                      |
| Cancellation that survives clone         | `CancellationToken`              |
| Cancellation that kills all descendants  | Parent `CancellationToken`       |

`JoinHandle::abort()` is the most forceful — it drops the future on the
next poll, no chance to clean up. `CancellationToken::cancel()` is
cooperative — the task observes cancellation and runs its own shutdown
logic (flush buffers, close sockets, emit a final log). For anything
touching IO or external state, prefer the token.

## Example: Per-Session Tokens With Parent Cancellation

A gateway spawns one task per accepted connection. Each session has its
own child token so the supervisor can cancel individual sessions (e.g. on
auth failure) without affecting the rest. A global parent token is tied
to `ShutdownSignal` for server shutdown.

```rust
use nexus_async_rt::{
    CancellationToken, Runtime, ShutdownSignal, TcpListener, TcpStream,
    spawn_boxed,
};
use nexus_rt::WorldBuilder;

async fn session(stream: TcpStream, token: CancellationToken) {
    loop {
        if token.is_cancelled() {
            break;
        }
        // read + dispatch...
        let _ = &stream;
        break;
    }
}

fn main() -> std::io::Result<()> {
    let mut world = WorldBuilder::new().build();
    let mut rt = Runtime::builder(&mut world)
        .signal_handlers(true)
        .build();

    rt.block_on(async {
        let listener = TcpListener::bind("0.0.0.0:8080".parse().unwrap())?;
        let root = CancellationToken::new();

        let accept_token = root.clone();
        let _accept = spawn_boxed(async move {
            loop {
                if accept_token.is_cancelled() {
                    break;
                }
                let (stream, _) = listener.accept().await?;
                let child = accept_token.child();
                spawn_boxed(session(stream, child));
            }
            #[allow(unreachable_code)]
            Ok::<_, std::io::Error>(())
        });

        ShutdownSignal::current().await;
        root.cancel();
        // optionally wait for accept + sessions to drain
        Ok(())
    })
}
```

## See Also

- [Task Spawning]task-spawning.md`JoinHandle::abort`
- [Timers and Time]timers-and-time.md — combining cancellation with
  deadlines
- [Patterns]patterns.md — graceful shutdown cookbook