nexus-async-rt 0.3.0

Single-threaded async executor with pre-allocated task storage
Documentation
# nexus-async-rt

Single-threaded async runtime for latency-sensitive systems. Built on mio.

Not a tokio replacement — a purpose-built alternative for single-threaded
event loops where predictable latency matters more than multi-threaded
throughput.

## When to use this vs tokio

**Use nexus-async-rt when:**
- Single-threaded event loop (one core, no work-stealing)
- Predictable tail latency (no scheduler jitter)
- Zero-alloc task spawning on the hot path (slab allocation)
- Futures are `!Send` — and that's fine
- You're building on the nexus ecosystem (nexus-net, nexus-rt)

**Use tokio when:**
- Multi-threaded execution or work-stealing
- tokio ecosystem (tower, hyper, tonic, etc.)
- Futures need to be `Send` across threads
- Broad community support matters

Both can coexist in the same process. Use nexus-async-rt for the
latency-critical event loop and tokio for everything else.

## Quick start

```rust
use nexus_async_rt::*;
use nexus_rt::WorldBuilder;

let mut world = WorldBuilder::new().build();
let mut rt = Runtime::new(&mut world);

rt.block_on(async {
    let handle = spawn_boxed(async { 42 });
    let result = handle.await;
    assert_eq!(result, 42);
});
```

## What you get

### Task spawning

Two strategies, same API:

```rust
// Box-allocated — default, no setup needed
let handle = spawn_boxed(async { compute() });

// Slab-allocated — pre-allocated, zero-alloc hot path
let handle = spawn_slab(async { compute() });
```

Both return `JoinHandle<T>` — await for the result, drop to detach,
or call `abort()` to cancel (consumes the handle).

### Slab allocation (zero-alloc spawn)

For hot-path tasks where allocation jitter is unacceptable:

```rust
// SAFETY: single-threaded runtime owns the slab.
let slab = unsafe { Slab::<256>::with_chunk_capacity(64) };
let mut rt = Runtime::builder(&mut world)
    .slab_unbounded(slab)
    .build();

rt.block_on(async {
    // Pre-allocated — no Box, no allocator, zero syscalls
    let handle = spawn_slab(async { fast_path() });
    handle.await
});
```

Or claim a slot first, spawn later:

```rust
if let Some(claim) = try_claim_slab() {
    let handle = claim.spawn(async { work() });
    // ...
}
```

### Timers

```rust
use std::time::Duration;

// Sleep
sleep(Duration::from_millis(100)).await;

// Timeout
let result = timeout(Duration::from_secs(5), some_future).await;

// Interval
let mut tick = interval(Duration::from_millis(10));
loop {
    tick.tick().await;
    poll_market_data();
}
```

### I/O (mio-based)

```rust
use nexus_async_rt::{TcpStream, TcpListener, io};

// Client
let stream = TcpStream::connect(addr, io())?;

// Server
let listener = TcpListener::bind(addr, io())?;
let (stream, peer) = listener.accept().await?;
```

### Channels

Three flavors for different use cases:

```rust
use nexus_async_rt::channel;

// Local MPSC — !Send, zero atomics, single-threaded
let (tx, rx) = channel::local::channel(64);

// Cross-thread MPSC — Sender: Clone + Send
let (tx, rx) = channel::mpsc::channel(64);

// Cross-thread SPSC — fastest cross-thread path
let (tx, rx) = channel::spsc::channel(64);
```

### World access

Access nexus-rt `World` resources from async tasks:

```rust
with_world(|world| {
    let config = world.resource::<Config>();
    // ...
});
```

### Graceful shutdown

```rust
rt.block_on(async {
    // ... spawn tasks ...
    shutdown_signal().await; // waits for Ctrl+C
});
```

### Cancellation

```rust
let token = CancellationToken::new();
let child = token.child_token();

spawn_boxed(async move {
    while !child.is_cancelled() {
        do_work().await;
    }
    // cleanup
});

token.cancel(); // cancels all children
```

## JoinHandle

`spawn_boxed` and `spawn_slab` return `JoinHandle<T>`:

- **Await** — get the result: `let val = handle.await;`
- **Detach** — drop the handle, task continues, output dropped on completion
- **Abort**`handle.abort()` consumes the handle, future dropped on next poll
- **Check**`handle.is_finished()` for non-blocking status

`JoinHandle` is `!Send` and `!Sync` — stays on the executor thread.

## Performance

| Path | p50 |
|------|-----|
| Task dispatch (poll cycle) | 55-64 cycles |
| Local channel try_send+try_recv | 13 ns |
| MPSC channel try_send+try_recv | 22 ns |
| SPSC channel try_send+try_recv | 15 ns |
| Cross-thread channel (busy spin) | 15 ns |
| Cross-thread channel (park/epoll) | 1.7 us |
| Tokio-compat waker bridge | 76 ns |

## Features

| Feature | Default | Description |
|---------|---------|-------------|
| `tokio-compat` | No | Adapters for bridging tokio and nexus-async-rt in the same process |

## Dependencies

- **mio** — I/O event loop (epoll/kqueue)
- **nexus-rt** — World/WorldBuilder for typed resource storage
- **nexus-slab** — Optional pre-allocated task storage
- **nexus-timer** — Hierarchical timer wheel
- **nexus-queue** / **nexus-logbuf** — Lock-free internal queues

## Platform support

Unix only (`#![cfg(unix)]`). Linux is the primary target, macOS supported.