kevy_rt/lib.rs
1//! kevy-rt — shared-nothing, thread-per-core runtime.
2//!
3//! Each core runs its own reactor (kqueue/epoll) and owns one **shard** of the
4//! keyspace (`hash(key) % nshards`). There is no shared mutable state and no
5//! lock on the hot path — cores communicate only by message passing over
6//! channels, woken via a self-pipe ([`kevy_sys::Waker`]). Connections are spread
7//! across cores by `SO_REUSEPORT`; a command whose key lives on another core is
8//! forwarded to that core, executed there, and the reply routed back to the
9//! originating connection.
10//!
11//! Per-connection reply ordering is preserved (RESP is pipelined): each command
12//! gets a monotonic seq; replies are emitted only in contiguous seq order, so an
13//! async cross-core reply never overtakes an earlier one.
14//!
15//! The cross-core channel currently uses `std::sync::mpsc` (pure Rust, zero
16//! deps); swapping in a lock-free SPSC/MPSC ring is a perf-polish item.
17//! Command semantics are injected via the [`Commands`] trait, keeping the
18//! runtime independent of the concrete command set. Part of the [kevy] server.
19//!
20//! [kevy]: https://crates.io/crates/kevy
21//!
22//! # Module map
23//!
24//! - [`Runtime`] (in `runtime`) — public entry point; spawns one `shard` per core.
25//! - `shard` — the per-core reactor: sockets, the inbound queue, reply flushing.
26//! - `exec` — command semantics: routing, execution, and result reduction.
27//! - `message` — internal cross-core work/result types.
28//! - `conn` — per-connection state (input/output, seq ring, subscriptions).
29//! - `reduce` — reply reduction (`materialize`) and pure helpers (set algebra,
30//! shard hashing, pub/sub framing).
31//!
32//! # Example
33//!
34//! Implement [`Commands`] for your command set and run it. ([`Store`] is
35//! re-exported so you don't need a separate dependency.)
36//!
37//! ```no_run
38//! use kevy_rt::{ArgvView, Commands, Route, Runtime, Store, TxnKind};
39//! use std::sync::Arc;
40//! use std::sync::atomic::AtomicBool;
41//!
42//! #[derive(Clone)]
43//! struct MyCommands;
44//! impl Commands for MyCommands {
45//! fn route<A: ArgvView + ?Sized>(&self, args: &A) -> Route {
46//! if args.len() >= 2 { Route::Single(1) } else { Route::Local }
47//! }
48//! fn dispatch<A: ArgvView + ?Sized>(&self, _store: &mut Store, _args: &A) -> Vec<u8> {
49//! b"+OK\r\n".to_vec()
50//! }
51//! fn is_quit<A: ArgvView + ?Sized>(&self, args: &A) -> bool {
52//! args.first().is_some_and(|c| c.eq_ignore_ascii_case(b"QUIT"))
53//! }
54//! fn is_write<A: ArgvView + ?Sized>(&self, _args: &A) -> bool { false }
55//! fn txn_kind<A: ArgvView + ?Sized>(&self, _args: &A) -> TxnKind { TxnKind::Other }
56//! }
57//!
58//! // One shard per core, listening on 127.0.0.1:6379, until `stop` is set.
59//! let rt = Runtime::builder(MyCommands).bind([127, 0, 0, 1], 6379).shards(4);
60//! rt.run(Arc::new(AtomicBool::new(false))).unwrap();
61//! ```
62// Almost entirely safe: the only `unsafe` is in `uring_reactor` (Linux io_uring),
63// which needs raw buffer pointers for zero-allocation completion I/O — on the hot
64// path toward kevy's disk-I/O-ceiling goal, where a buffer-ownership safe wrapper
65// would add per-op cost. Each such block documents its invariant; the
66// epoll/kqueue path and every other module stay safe, and all libc lives in
67// kevy-sys.
68#![deny(unsafe_op_in_unsafe_fn)]
69
70//! Every public item here is documented, and the lint keeps it that
71//! way: kevy-rt is the reactor, and `warnings = "deny"` turns a new
72//! gap into a compile error rather than a number that drifts. Closed
73//! from 35 sites in v6 — all of them fields inside well-documented
74//! variants, which is where prose review does not look.
75#![warn(missing_docs)]
76mod bio;
77mod block_xshard;
78mod block_xshard_confirm;
79#[cfg(debug_assertions)]
80pub use block_xshard_confirm::counters as serve_counters;
81mod aof_writer;
82mod block_xshard_registry;
83mod block_xshard_target;
84mod blocked;
85mod cache_padded;
86mod client_ops;
87mod cluster;
88mod conn;
89mod exec;
90mod exec_bitop;
91mod exec_build;
92mod exec_client_intercept;
93mod exec_copy;
94mod exec_crossslot;
95mod exec_dispatch;
96mod exec_feed;
97mod exec_fold;
98mod exec_geostore;
99mod exec_listmove;
100mod exec_mutated;
101mod exec_notify;
102mod exec_op;
103mod exec_pubsub;
104mod exec_pubsub_pattern;
105mod exec_rename;
106mod exec_replwait;
107mod exec_scan;
108mod exec_slowlog;
109mod exec_txn;
110mod exec_watch;
111mod exec_zalgebra;
112mod inbox;
113mod lua_wake_bridge;
114mod message;
115mod message_agg;
116mod message_kinds;
117mod message_part;
118mod persist_jobs;
119mod persist_rewrite;
120mod persist_worker;
121pub mod propagation;
122mod reduce;
123mod repl_trace;
124mod replica_inbox;
125mod replication;
126mod replication_apply;
127mod replication_gate;
128mod replication_io;
129mod replication_pump;
130mod replication_trace;
131mod reshard;
132mod route;
133mod runtime;
134mod runtime_builders;
135mod runtime_run;
136mod shard;
137mod shard_flush;
138mod shard_lifecycle;
139mod shard_run;
140mod shard_tick;
141mod slow_iter;
142mod types;
143#[cfg(target_os = "linux")]
144mod uring_aof;
145#[cfg(target_os = "linux")]
146mod uring_arm;
147#[cfg(target_os = "linux")]
148mod uring_bigbulk;
149#[cfg(target_os = "linux")]
150mod uring_bigbulk_b2alt;
151#[cfg(target_os = "linux")]
152mod uring_bigbulk_probe;
153#[cfg(target_os = "linux")]
154mod uring_conn;
155#[cfg(target_os = "linux")]
156mod uring_inbox;
157#[cfg(target_os = "linux")]
158mod uring_io;
159#[cfg(target_os = "linux")]
160mod uring_io_write;
161#[cfg(target_os = "linux")]
162mod uring_ops;
163#[cfg(target_os = "linux")]
164mod uring_park;
165#[cfg(target_os = "linux")]
166mod uring_reactor;
167#[cfg(target_os = "linux")]
168mod uring_setup;
169#[cfg(target_os = "linux")]
170mod uring_stalldump;
171#[cfg(any(target_os = "linux", test))] // `test` too: pure, tested everywhere
172mod uring_write_linearize;
173
174/// Hard cap on a single connection's accumulated unflushed reply
175/// bytes. A client that stops reading (or a slow pub/sub subscriber)
176/// lets its per-conn output buffer grow without bound; past this it is
177/// disconnected so it can't OOM the shard. Deliberately generous
178/// (512 MiB, one max bulk's order of magnitude): a legitimate large
179/// reply drains progressively and never accumulates near it — only a
180/// non-draining reader does. Enforced out-of-band per tick by
181/// `Shard::enforce_output_limit` / `uring_enforce_output_limit`.
182pub(crate) const CLIENT_OUTPUT_HARD_LIMIT: usize = 512 * 1024 * 1024;
183
184/// Cap on a conn's ACCUMULATED unparsed input (Redis's
185/// client-query-buffer-limit, same 1GB default): a client streaming an
186/// incomplete-but-valid giant frame (1M declared args × 512MB bulks)
187/// would otherwise grow `conn.input` without bound and OOM the shard.
188/// Overridable via `KEVY_DEBUG_INPUT_LIMIT` (a debug surface, like
189/// `KEVY_DEBUG_STALL_MS`) so the guard is e2e-testable without
190/// streaming a real gigabyte.
191pub(crate) const CLIENT_INPUT_HARD_LIMIT: usize = 1024 * 1024 * 1024;
192
193pub use blocked::{BlockHint, BlockKind};
194pub use client_ops::ClientKillFilter;
195pub use cluster::shard_slot_range;
196pub use exec_geostore::GeoHits;
197pub use exec_slowlog::{SlowlogSub, parse_slowlog_sub};
198pub use kevy_config::NotificationFlags;
199pub use kevy_persist::Fsync;
200pub use kevy_resp::{Argv, ArgvBorrowed, ArgvView, RespVersion};
201pub use kevy_store::Store;
202pub use lua_wake_bridge::push_lua_wake_key;
203pub use message::{MultiOp, ZCombine};
204pub use reduce::shard_of as shard_of_key;
205pub use repl_trace::{repl_trace, repl_trace_line};
206pub use replica_inbox::{
207 ReplicaApply, ReplicaInboxReceiver, ReplicaInboxSender, SnapshotGate, replica_inbox_pair,
208};
209pub use replication_gate::ReplicatedApplyGuard;
210pub use route::{Route, ScanArgs, XGroupCtx};
211pub use runtime::Runtime;
212pub use types::{
213 ExtensionReduced, LiveRuntimeConfig, NotifyClass, ReplicaAck, ReplicaViewRow, ResolvedCmd,
214 TxnKind,
215};
216
217pub use crate::commands_trait::Commands;
218mod commands_trait;
219#[cfg(test)]
220mod commands_trait_tests;