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
70mod bio;
71mod block_xshard;
72mod block_xshard_confirm;
73#[cfg(debug_assertions)]
74pub use block_xshard_confirm::counters as serve_counters;
75mod block_xshard_registry;
76mod block_xshard_target;
77mod blocked;
78mod cache_padded;
79mod client_ops;
80mod cluster;
81mod conn;
82mod exec;
83mod exec_build;
84mod exec_client_intercept;
85mod exec_crossslot;
86mod exec_dispatch;
87mod exec_feed;
88mod exec_fold;
89mod exec_geostore;
90mod exec_listmove;
91mod exec_mutated;
92mod exec_notify;
93mod exec_op;
94mod exec_pubsub;
95mod exec_pubsub_pattern;
96mod exec_rename;
97mod exec_replwait;
98mod exec_scan;
99mod exec_slowlog;
100mod exec_txn;
101mod exec_watch;
102mod exec_zalgebra;
103mod inbox;
104mod lua_wake_bridge;
105mod message;
106mod message_agg;
107mod message_kinds;
108mod persist_jobs;
109mod persist_rewrite;
110mod persist_worker;
111pub mod propagation;
112mod reduce;
113mod replica_inbox;
114mod repl_trace;
115mod replication;
116mod replication_apply;
117mod replication_gate;
118mod replication_io;
119mod replication_pump;
120mod replication_trace;
121mod reshard;
122mod route;
123mod runtime;
124mod runtime_builders;
125mod runtime_run;
126mod shard;
127mod shard_flush;
128mod shard_lifecycle;
129mod shard_run;
130mod shard_tick;
131mod slow_iter;
132mod types;
133mod aof_writer;
134#[cfg(target_os = "linux")]
135mod uring_aof;
136#[cfg(target_os = "linux")]
137mod uring_arm;
138#[cfg(target_os = "linux")]
139mod uring_bigbulk;
140#[cfg(target_os = "linux")]
141mod uring_bigbulk_b2alt;
142#[cfg(target_os = "linux")]
143mod uring_bigbulk_probe;
144#[cfg(target_os = "linux")]
145mod uring_conn;
146#[cfg(target_os = "linux")]
147mod uring_inbox;
148#[cfg(target_os = "linux")]
149mod uring_io;
150#[cfg(target_os = "linux")]
151mod uring_io_write;
152#[cfg(target_os = "linux")]
153mod uring_ops;
154#[cfg(target_os = "linux")]
155mod uring_park;
156#[cfg(target_os = "linux")]
157mod uring_reactor;
158#[cfg(target_os = "linux")]
159mod uring_setup;
160#[cfg(target_os = "linux")]
161mod uring_stalldump;
162#[cfg(any(target_os = "linux", test))] // `test` too: pure, tested everywhere
163mod uring_write_linearize;
164
165/// Hard cap on a single connection's accumulated unflushed reply
166/// bytes. A client that stops reading (or a slow pub/sub subscriber)
167/// lets its per-conn output buffer grow without bound; past this it is
168/// disconnected so it can't OOM the shard. Deliberately generous
169/// (512 MiB, one max bulk's order of magnitude): a legitimate large
170/// reply drains progressively and never accumulates near it — only a
171/// non-draining reader does. Enforced out-of-band per tick by
172/// `Shard::enforce_output_limit` / `uring_enforce_output_limit`.
173pub(crate) const CLIENT_OUTPUT_HARD_LIMIT: usize = 512 * 1024 * 1024;
174
175/// Cap on a conn's ACCUMULATED unparsed input (Redis's
176/// client-query-buffer-limit, same 1GB default): a client streaming an
177/// incomplete-but-valid giant frame (1M declared args × 512MB bulks)
178/// would otherwise grow `conn.input` without bound and OOM the shard.
179/// Overridable via `KEVY_DEBUG_INPUT_LIMIT` (a debug surface, like
180/// `KEVY_DEBUG_STALL_MS`) so the guard is e2e-testable without
181/// streaming a real gigabyte.
182pub(crate) const CLIENT_INPUT_HARD_LIMIT: usize = 1024 * 1024 * 1024;
183
184pub use blocked::{BlockHint, BlockKind};
185pub use client_ops::ClientKillFilter;
186pub use cluster::shard_slot_range;
187pub use exec_geostore::GeoHits;
188pub use exec_slowlog::{SlowlogSub, parse_slowlog_sub};
189pub use kevy_config::NotificationFlags;
190pub use kevy_persist::Fsync;
191pub use kevy_resp::{Argv, ArgvBorrowed, ArgvView, RespVersion};
192pub use kevy_store::Store;
193pub use lua_wake_bridge::push_lua_wake_key;
194pub use repl_trace::{repl_trace, repl_trace_line};
195pub use message::{MultiOp, ZCombine};
196pub use reduce::shard_of as shard_of_key;
197pub use replica_inbox::{
198 ReplicaApply, ReplicaInboxReceiver, ReplicaInboxSender, SnapshotGate, replica_inbox_pair,
199};
200pub use replication_gate::ReplicatedApplyGuard;
201pub use route::{Route, ScanArgs, XGroupCtx};
202pub use runtime::Runtime;
203pub use types::{
204 ExtensionReduced, LiveRuntimeConfig, NotifyClass, ReplicaAck, ReplicaViewRow, ResolvedCmd,
205 TxnKind,
206};
207
208pub use crate::commands_trait::Commands;
209mod commands_trait;