webrtc 0.20.0

Async-friendly WebRTC implementation in Rust
Documentation
# custom-runtime

custom-runtime demonstrates that WebRTC.rs is **genuinely runtime-agnostic**: it implements the `webrtc::runtime::Runtime` trait over [`async-executor`](https://crates.io/crates/async-executor) and [`async-io`](https://crates.io/crates/async-io) — neither Tokio nor smol — and drives every runtime capability the WebRTC stack needs through it.

It doubles as the acceptance test for the runtime abstraction. Building it with **neither** `runtime-tokio` nor `runtime-smol` proves nothing in the library is hard-wired to a built-in runtime.

## Instructions

### Build custom-runtime

```shell
cargo build --example custom-runtime
```

### Run custom-runtime

The interesting invocation disables both built-in runtimes, so the only `Runtime` in the process is the one the example defines:

```shell
cargo run --no-default-features --example custom-runtime
```

Expected output:

```text
running on a custom runtime: my-runtime
sleep(50ms) took 50.304959ms
tick 0
tick 1
tick 2
timeout on a pending future => Err(Elapsed)
hello from a spawned task
udp: received "ping" from 127.0.0.1:54321
resolved localhost:3478 -> [[::1]:3478, 127.0.0.1:3478]
```

Socket binding may be denied in restricted environments (sandboxes, some CI); the UDP step then reports `udp: skipped` rather than failing.

## What it implements

Eight required `Runtime` methods, plus the three socket traits:

| Method                                        | Backed by                                    |
|-----------------------------------------------|----------------------------------------------|
| `spawn`                                       | `async_executor::Executor` on a 2-thread pool |
| `sleep`, `interval`                           | `async_io::Timer`                            |
| `wrap_udp_socket`, `wrap_tcp_listener`, `connect_tcp` | `async_io::Async<T>`                 |
| `resolve_host`                                | `std`'s resolver, offloaded to a thread      |
| `block_on`                                    | `futures_lite::future::block_on`             |

`spawn_reactor`, `yield_now` and `name` have working defaults, so they are optional. `timeout` needs no implementation at all — it is derived generically from `sleep`.

The socket wrapper sends and receives one datagram per syscall: it ignores `Transmit::segment_size` and leaves `max_gso_segments` / `max_gro_segments` at their default of 1, so the driver never asks it to batch. A production runtime would enable UDP GSO/GRO the way the built-in ones do — construct a `webrtc::runtime::UdpSocketState` at wrap time, report its `max_gso_segments()` / `gro_segments()`, pass the whole `Transmit` to its `send`, and adapt its scatter/gather `recv` to the single-datagram shape `poll_recv` returns (see `src/runtime/tokio.rs` or `smol.rs`).

## Using it for a real connection

The runtime is injected **per connection**, so a single process can run some connections on a custom runtime and others on a built-in one:

```rust
let runtime: Arc<dyn Runtime> = Arc::new(MyRuntime::new());

let pc = PeerConnectionBuilder::new()
    .with_runtime(runtime.clone())
    .with_handler(Arc::new(MyHandler))
    .with_udp_addrs(vec!["0.0.0.0:0"])
    .build()
    .await?;
```

There is no global registry to install into and nothing to override — which is what keeps per-connection runtime choice possible.

## Writing your own

1. Implement `Runtime` (the 8 required methods above).
2. Implement `AsyncUdpSocket`, `AsyncTcpListener` and `AsyncTcpStream` for your socket types. `AsyncUdpSocket` is poll-based: supply `poll_send` and `poll_recv`, and the four `async` convenience methods come from defaults.
3. Return your task handle from `spawn` via `JoinHandle::new(Box::new(..))`, implementing `JoinHandleInner` for it.
4. Pass it to `with_runtime`.

No `#[cfg]` edits, no fork, and no changes to library internals.

One thing to get right, and the reason this example is worth reading: **`poll_send`/`poll_recv` must be ordered to match your reactor's readiness semantics.** Event- or ticket-based reactors (async-io, and this example) must attempt the syscall *first* and consult readiness only on `WouldBlock` — otherwise datagrams already queued in the socket buffer generate no fresh readiness event and a burst stalls. Reactors with cached, level-triggered readiness (Tokio) instead need the operation to report `WouldBlock` back through their own wrapper (`try_io`) so the cache is invalidated.