ergon-io
Network I/O for exchange clients on one busy-spinning thread: WebSocket and HTTP/1.1 over TLS 1.3.
- One non-blocking
pollmoves bytes between your loop and the kernel. - The kernel interface (epoll or
io_uring, with or without NIC polling) is configuration, chosen at run time. - TLS records are decrypted in place and messages parsed in place: one copy per byte.
AI assistance. Like the rest of ergon, this crate was written with heavy AI assistance. See AI assistance disclosure. It is not production-ready.
Which features to pick: see Features, below.
When to use it
| Use it for | Use something else for |
|---|---|
| A Linux thread that spins on its own core and owns 1 to a few hundred exchange connections (pick the backend by count: below) | An async application: tokio or mio |
| A loop that must never sleep in the kernel | Production on macOS or Windows: the driver is Linux-only |
| Fixed endpoints: HTTP/1.1 keep-alive, TLS 1.3 | HTTP/2 or 3, proxies, redirects, cookies, compression: the curl feature or a general client |
Parts
| Part | What it does |
|---|---|
Driver |
Owns the connections. Each poll makes one pass (receive, decrypt, decode, deliver, send) and returns. |
WsConn |
WebSocket client. WsHandler::on_message gets a slice of the receive buffer. |
WsOptions |
WebSocket limits and timers. |
Reconnect |
Optional. Reopens closed connections with backoff; you resubscribe. |
HttpClient |
A pool of warm HTTP connections behind one API: native (fast) or libcurl (feature curl). |
HttpConn |
One HTTP/1.1 keep-alive connection. |
Signer |
Query-parameter request signing (HMAC-SHA256 or Ed25519), no allocation. |
Example: a WebSocket feed
Connect, subscribe, handle each message, and reconnect and subscribe again
after a drop (Linux, feature webpki-roots):
use Retry;
use ;
/// The application: `Driver::poll` calls it.
- The full example, with a clean close: io/examples/websocket.rs. A test runs it against a local server on every backend.
- Run it from a checkout of the repository:
cargo run -p ergon-io --features webpki-roots --example websocket -- 20 recv-busy HOST 443 /updates.
Defaults
TCP_NODELAY on, keepalive and TCP_USER_TIMEOUT set, a 4 MiB receive
buffer, WebSocket Pings after 10 s quiet, reconnect off. Every default and
how to change it: the book's
defaults.
Guarantees and limits
- Never sleeps with
epoll-spin,recv-busy,uring-spinoruring-sqpoll.epoll-busyanduring-napimay wait up towait_us. - One failure closes one connection. It goes to
Handler::on_closed; the others keep running.pollfails only when the backend fails. - Connecting does not block.
Driver::connecttakes a step per poll, but the TLS handshake makes one poll long;Driver::connect_backgroundruns it on a helper thread. - Timers stay off the hot path. Pings and timeouts run on the driver's deadline check (every 250 µs), never on the read or send path.
- TLS 1.3 only. TLS 1.2 is refused with a clear error.
- Client side only. No listener.
- WebSocket, not supported: compression (
permessage-deflate), other extensions, proxies, sending fragmented messages. - Linux for the driver.
DriverandHttpClientare Linux-only; the protocol code builds everywhere. - Kernel.
uring-spinneeds Linux 6.1+;uring-sqpoll,uring-napiandepoll-busyneed 6.9+. Theio_uringbackends need theuringfeature (on by default).prefer_busy_pollonrecv-busyand a raisedbusy_poll_budgetneedCAP_NET_ADMIN. - Host checks at start-up (ergon-tune):
the loop's CPU, the receive buffer, each route's quick ACK and each egress
NIC. Each finding goes to stderr once, with its fix, from a warning thread.
ERGON_HOST_CHECKS=offsilences those warning lines (requiremakes a warning refuse the start); the results stay onDriver::host_report(). - Measured on x86 (AWS
c7i, AzureD8ds_v6) and Graviton (AWSc8g): io/benches/BENCHMARK.md.
Choosing a backend
Config::parse("backend[,key=value...]"). Count every socket the loop
thread polls (feeds, order connections, HttpClient pool connections):
- 1–4 sockets:
recv-busy. - More:
uring-sqpollwith a spare CPU, orrecv-busy,per_queue=trueon one CPU. - The interrupt backends (
epoll-spin,uring-spin) were level withuring-sqpollfrom 4 sockets with node-level NIC settings: moderation off and the NIC's interrupts on a core of their own.
The full table, the evidence and the node settings: the book's choosing a backend.
Rate limits and errors
limit::Budgetchecks each request against the venue's limits before it goes out: up to 8 buckets, all or none. It never sleeps or queues.- A failed request says whether it left (
outcome::Sent); a response maps to oneoutcome::Verdict. - A ban halts its scope until a person calls
Budget::clear_halt. - Send again only what did not run, inside its deadline
(
Budget::try_resend); look up an unknown order by its client order id. - One sending process per account and IP: the budget cannot see others.
- Configure limits and classify application-specific errors in your adapter. The generic mechanisms are described in the book's rate limits and errors.
Features
| Feature | Default | Adds |
|---|---|---|
uring |
on | the io_uring backends (Linux); without it, choosing one returns Error::Unsupported |
webpki-roots |
off | Mozilla's root store, tls::webpki_roots (CDLA-Permissive-2.0, so opt-in) |
curl |
off | libcurl as an HttpClient implementation (the system libcurl, through pkg-config). It refuses every method but GET, HEAD, OPTIONS and DELETE unless curl_allow_non_idempotent_methods is set: libcurl can send a request twice |
Install with the features you need:
More
- The book chapter: ergon-io (backends, WebSocket, HTTP client, measured results).
- Terms (busy polling,
io_uring, quick ACK, p50 / p99 / max): glossary. - Every measurement, with hosts and run ids: io/benches/BENCHMARK.md.
- API reference: docs.rs/ergon-io.
Licensed under Apache-2.0.