feat(serve): registry names + docs (v0.2 chunk 4)
Registration:
- Each listener registers "urus.listener.{i}" from inside its ChildSpec
factory. On a restart the stale binding points at a dead pid; smarm's
registry evicts those lazily on register/whereis, so re-registering
the same name is safe. Result ignored — a registry hiccup must not
take the listener down.
- "urus.server" -> supervisor pid, registered via the JoinHandle's
.pid() immediately after spawn rather than inside the closure: the
binding exists before the supervisor runs an instruction, so an early
whereis can't observe None.
Test: server_and_listeners_are_registered probes whereis from a route
handler — whereis must run inside the runtime, and the test thread is a
foreign OS thread with no runtime in its TLS.
Docs:
- README: Config documented in full (timeout semantics: keep_alive =
idle-before-first-byte, request = Instant deadline first-byte ->
head+body, slowloris-proof), Handle/shutdown_handle/serve_with_shutdown
section with the 4-step shutdown sequence, named-actors note, test
coverage list refreshed, Roadmap section now points at ROADMAP.md.
- ROADMAP: v0.2 chunks 1-4 marked landed; design detail stays in the
chunk commit bodies.
Verified: cargo build --features smarm-trace clean; full suite (32
tests) green 3x; debug-grep clean.
This closes v0.2. Exit criteria all verified across chunks 1-3: crud
drains on stdin-Enter (~100ms), idle keep-alive reaped at
keep_alive_timeout, panicking listener restarts under load.
This commit is contained in:
@@ -126,17 +126,72 @@ Handlers are closures taking `(Conn, Next) -> Conn`. Call `Next::call(c)` to con
|
||||
|
||||
```rust
|
||||
use urus::{serve_with, Config};
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
let cfg = Config {
|
||||
listener_pool: 2, // Number of OS threads handling accept()
|
||||
scheduler_threads: Some(2), // Number of smarm worker threads
|
||||
listener_pool: 2, // Supervised accept-loop actors
|
||||
scheduler_threads: Some(2), // smarm worker threads (None = one per CPU)
|
||||
keep_alive_timeout: Duration::from_secs(60), // Idle budget between requests
|
||||
request_timeout: Duration::from_secs(30), // Whole-request read deadline
|
||||
max_header_count: 64,
|
||||
read_buf_size: 8 * 1024,
|
||||
max_body_bytes: 1 << 20,
|
||||
drain_timeout: Duration::from_secs(30), // Graceful-shutdown drain budget
|
||||
..Config::new("127.0.0.1:8080".parse().unwrap())
|
||||
};
|
||||
|
||||
serve_with(cfg, pipeline).unwrap();
|
||||
```
|
||||
|
||||
**Timeout semantics:**
|
||||
|
||||
- `keep_alive_timeout` — how long a connection may sit idle waiting for the
|
||||
*first byte* of a request. Expiry closes the socket silently (nothing was
|
||||
in flight). Pipelined leftover bytes count as a started request, not idle.
|
||||
- `request_timeout` — a wall-clock deadline from a request's first byte
|
||||
until its head and body are fully read. Expiry mid-head gets a
|
||||
best-effort `408 Request Timeout`; expiry mid-body just closes. Deadlines
|
||||
are absolute instants, so a trickling (slowloris-style) client can't
|
||||
reset its budget by sending one byte at a time.
|
||||
|
||||
### Graceful Shutdown
|
||||
|
||||
`serve_with_shutdown` takes a `ShutdownSignal`; the paired `Handle` can be
|
||||
triggered from anywhere (another thread, a signal handler):
|
||||
|
||||
```rust
|
||||
use urus::{serve_with_shutdown, shutdown_handle, Config};
|
||||
|
||||
let (handle, signal) = shutdown_handle();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// e.g. wait for SIGTERM / stdin / an admin endpoint...
|
||||
handle.shutdown();
|
||||
});
|
||||
|
||||
serve_with_shutdown(cfg, pipeline, signal).unwrap();
|
||||
// Returns once the runtime has fully wound down.
|
||||
```
|
||||
|
||||
`Handle::shutdown()` is idempotent and performs, in order:
|
||||
|
||||
1. Stop accepting — every listener exits; no new connections.
|
||||
2. Close idle keep-alive connections immediately.
|
||||
3. Drain in-flight requests for up to `Config.drain_timeout`.
|
||||
4. Force-stop any stragglers past the deadline (sockets close cleanly on
|
||||
unwind via `OwnedFd::drop`).
|
||||
|
||||
If every `Handle` is dropped, shutdown can never be signalled and the
|
||||
server runs forever — exactly `serve_with`'s semantics (it does this
|
||||
internally).
|
||||
|
||||
### Named Actors
|
||||
|
||||
For introspection and debugging, the server registers itself in smarm's
|
||||
process registry: `urus.server` (the listener-pool supervisor) and
|
||||
`urus.listener.{i}` (each accept loop). `smarm::whereis(name)` resolves
|
||||
them from any actor inside the runtime.
|
||||
|
||||
## Examples
|
||||
|
||||
### CRUD with Actor Ownership
|
||||
@@ -165,11 +220,12 @@ cargo test
|
||||
```
|
||||
|
||||
Tests in `tests/integration.rs` cover:
|
||||
- Basic routing (GET, POST, PUT, DELETE)
|
||||
- Request body echoing
|
||||
- HTTP header parsing
|
||||
- Path parameter extraction
|
||||
- Status code responses
|
||||
- Basic routing, request bodies, headers, path parameters, status codes
|
||||
- Keep-alive and pipelining
|
||||
- Listener supervision (a panicking listener restarts without dropping a pending accept)
|
||||
- Graceful shutdown (idle close, in-flight drain, force-stop at the drain deadline)
|
||||
- Timeouts (idle keep-alive reaping, slowloris-style slow headers/body)
|
||||
- Registry names (`urus.server`, `urus.listener.{i}` resolve via `whereis`)
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -205,11 +261,10 @@ This avoids the complexity of async ecosystems while maintaining full concurrenc
|
||||
|
||||
## Roadmap
|
||||
|
||||
v1 covers HTTP/1.1, the plug pipeline, and a built-in router. Future versions may include:
|
||||
- Middleware ecosystem (auth, logging, compression, etc.).
|
||||
- WebSocket support.
|
||||
- Benchmarking suite (see `urus-bench-spec.md`).
|
||||
- Additional utilities and examples.
|
||||
See [`ROADMAP.md`](ROADMAP.md). Summary: v0.2 (supervised listener pool,
|
||||
graceful shutdown, enforced timeouts, registry names) is done; next up are
|
||||
streaming bodies + SSE (v0.3), WebSocket (v0.4), PubSub (v0.5), and
|
||||
Phoenix-style channels (v0.6).
|
||||
|
||||
Refer to `urus-spec.md` and `urus-v1-build-notes.md` in the artifact persistence for the original design and implementation notes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user