#![forbid(unsafe_code)]
use kevy_resp::{encode_error, parse_command};
use kevy_rt::Runtime;
use kevy_store::Store;
use kevy_sys::Socket;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
mod cmd;
mod cmd_block;
mod cmd_block_serve;
mod cmd_class;
mod cmd_command;
mod cmd_data;
mod cmd_digest;
mod cmd_failover;
mod cmd_hash_ttl;
mod cmd_hello;
mod cmd_index;
mod cmd_index_advise;
mod cmd_index_query;
mod cmd_index_reduce;
mod cmd_lua;
mod cmd_repl;
mod cmd_resolve;
mod cmd_table;
mod cmd_table_verify;
mod cmd_view;
mod cmd_view_reduce;
mod cmd_zadd;
mod commands;
mod dispatch;
mod dispatch_bitmap;
mod dispatch_collections;
mod dispatch_collections_v127;
mod dispatch_geo;
mod dispatch_replay;
mod dispatch_resp3;
mod dispatch_stream;
mod dispatch_strings;
mod elect_persist;
mod index_runtime;
mod metrics_http;
mod ops;
mod replica_runner;
mod replica_runner_events;
mod replica_runner_routed;
mod replica_trace;
mod replication;
mod state;
mod table_runtime;
mod tier_read;
pub mod verb_meta;
mod view_runtime;
pub use kevy_rt::Argv;
pub use kevy_store::Store as KeyspaceStore;
pub use state::{KevyCommands, RuntimeState};
#[derive(Debug)]
pub enum AfterDrain {
KeepOpen,
Close,
}
pub(crate) fn map_eviction_policy(p: kevy_config::EvictionPolicy) -> kevy_store::EvictionPolicy {
use kevy_config::EvictionPolicy as C;
use kevy_store::EvictionPolicy as S;
match p {
C::NoEviction => S::NoEviction,
C::AllKeysLru => S::AllKeysLru,
C::AllKeysLfu => S::AllKeysLfu,
C::AllKeysRandom => S::AllKeysRandom,
C::VolatileLru => S::VolatileLru,
C::VolatileLfu => S::VolatileLfu,
C::VolatileRandom => S::VolatileRandom,
C::VolatileTtl => S::VolatileTtl,
}
}
#[cfg(unix)]
static SIGNAL_RECEIVED: AtomicBool = AtomicBool::new(false);
static STOP_FLAGS: std::sync::Mutex<Vec<std::sync::Weak<AtomicBool>>> =
std::sync::Mutex::new(Vec::new());
#[cfg(unix)]
fn install_signal_handlers(stop: Arc<AtomicBool>) {
extern "C" fn handler(_: std::ffi::c_int) {
SIGNAL_RECEIVED.store(true, std::sync::atomic::Ordering::SeqCst);
}
kevy_sys::install_signal_handler(kevy_sys::SIGTERM, handler);
kevy_sys::install_signal_handler(kevy_sys::SIGINT, handler);
extern "C" fn xfsz_noop(_: std::ffi::c_int) {}
kevy_sys::install_signal_handler(kevy_sys::SIGXFSZ, xfsz_noop);
let mut flags = STOP_FLAGS.lock().expect("STOP_FLAGS poisoned");
let first = flags.is_empty();
SIGNAL_RECEIVED.store(false, std::sync::atomic::Ordering::SeqCst);
flags.push(Arc::downgrade(&stop));
drop(flags);
if first {
std::thread::spawn(|| {
loop {
if SIGNAL_RECEIVED.load(std::sync::atomic::Ordering::SeqCst) {
let flags = STOP_FLAGS.lock().expect("STOP_FLAGS poisoned");
for f in flags.iter() {
if let Some(stop) = f.upgrade() {
stop.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
return;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
});
}
}
#[cfg(not(unix))]
fn install_signal_handlers(_stop: Arc<AtomicBool>) {
}
pub fn serve(cfg: Arc<kevy_config::Config>) -> ! {
let state = boot_state(&cfg);
let runtime = build_runtime(&cfg, KevyCommands::with_state(Arc::clone(&state)));
state.election.maybe_start(&cfg, &state.replication);
let stop = Arc::new(AtomicBool::new(false));
install_signal_handlers(Arc::clone(&stop));
state.register_stop_flag(Arc::clone(&stop));
metrics_http::spawn_if_enabled(&state);
let run_result = runtime.run(stop);
state.election.shutdown();
if let Err(e) = run_result {
eprintln!("kevy: runtime error: {e}");
std::process::exit(1);
}
std::process::exit(0);
}
fn boot_state(cfg: &Arc<kevy_config::Config>) -> Arc<RuntimeState> {
let data_dir = cfg.server.data_dir.clone();
let nshards = cfg.server.threads.max(1);
if let Err(e) = std::fs::create_dir_all(&data_dir) {
eprintln!("kevy: cannot create data dir {}: {e}", data_dir.display());
std::process::exit(1);
}
let state = match RuntimeState::new(Arc::clone(cfg), data_dir, nshards) {
Ok(s) => Arc::new(s),
Err(msg) => {
eprintln!("kevy: bad [cluster] scopes config: {msg}");
std::process::exit(1);
}
};
cmd_index::boot(&state);
cmd_view::boot(&state);
cmd_table::boot(&state);
state
}
fn build_runtime(cfg: &kevy_config::Config, commands: KevyCommands) -> Runtime<KevyCommands> {
let state = Arc::clone(commands.state());
let nshards = state.nshards();
let fsync = map_appendfsync(cfg.persistence.appendfsync);
let mut runtime = Runtime::builder(commands)
.bind(cfg.server.bind, cfg.server.port)
.shards(nshards)
.with_data_dir(cfg.server.data_dir.clone())
.with_accept_shards(cfg.server.accept_shards)
.with_max_clients(cfg.server.max_clients)
.with_aof(cfg.persistence.aof)
.with_appendfsync(fsync)
.with_auto_aof_rewrite(
cfg.persistence.auto_aof_rewrite_percentage,
cfg.persistence.auto_aof_rewrite_min_size,
)
.with_auto_rewrite_bytes(cfg.persistence.auto_aof_rewrite_bytes)
.with_auto_rewrite_interval_secs(cfg.persistence.auto_aof_rewrite_interval_secs)
.with_replay_resync(cfg.persistence.replay_resync)
.with_advanced(
cfg.advanced.spin_limit,
cfg.advanced.park_timeout_ms,
cfg.advanced.tick_check_every,
cfg.advanced.ring_capacity,
)
.with_slowlog(cfg.slowlog.slower_than_micros, cfg.slowlog.max_len);
if cfg.cluster.enabled {
runtime = runtime.with_cluster(cluster_port_base(cfg));
}
if cfg.feed.enabled {
runtime = runtime.with_feed(true, cfg.feed.feed_buffer_size);
}
runtime = wire_tiering(runtime, cfg);
if let Ok(path) = std::env::var("KEVY_UNIX_SOCKET")
&& !path.is_empty()
{
runtime = runtime.with_unix_socket(PathBuf::from(path));
}
replication::apply(runtime, cfg, &state)
}
fn wire_tiering(
runtime: Runtime<KevyCommands>,
cfg: &kevy_config::Config,
) -> Runtime<KevyCommands> {
match resolve_tier_budget(cfg) {
Ok(budget) => {
runtime.with_tier_budget(budget).with_tier_spill_dir(cfg.tiering.spill_dir.clone())
}
Err(msg) => {
eprintln!("kevy: {msg}");
std::process::exit(1);
}
}
}
pub(crate) fn resolve_tier_budget(cfg: &kevy_config::Config) -> Result<Option<u64>, String> {
match cfg.tiering.budget {
None => Ok(None),
Some(spec) => {
spec.resolve_with(kevy_sys::detected_memory_bound()).map(Some).ok_or_else(|| {
format!(
"[tiering] budget = \"{}\": no memory bound detected on this host \
(cgroup v2 memory.max / /proc/meminfo MemAvailable / hw.memsize all \
unavailable) — use an absolute budget (\"4gb\")",
spec.as_config_string()
)
})
}
}
}
pub(crate) fn cluster_port_base(cfg: &kevy_config::Config) -> u16 {
match cfg.cluster.port_base {
0 => cfg.server.port.saturating_add(1),
base => base,
}
}
pub(crate) fn map_appendfsync(p: kevy_config::AppendFsync) -> kevy_persist::Fsync {
use kevy_config::AppendFsync as C;
use kevy_persist::Fsync as P;
match p {
C::Always => P::Always,
C::EverySec => P::EverySec,
C::No => P::No,
}
}
pub fn drain_commands(
kevy: &KevyCommands,
store: &mut Store,
input: &mut Vec<u8>,
output: &mut Vec<u8>,
) -> AfterDrain {
loop {
match parse_command(input) {
Ok(Some((args, consumed))) => {
let reply = kevy.dispatch(store, &args);
kevy_rt::propagation::discard_override();
output.extend_from_slice(&reply);
input.drain(..consumed);
if args.first().is_some_and(|c| c.eq_ignore_ascii_case(b"QUIT")) {
return AfterDrain::Close;
}
}
Ok(None) => return AfterDrain::KeepOpen,
Err(_) => {
encode_error(output, "ERR Protocol error");
return AfterDrain::Close;
}
}
}
}
pub fn handle_conn(kevy: &KevyCommands, conn: &Socket, store: &mut Store) -> io::Result<()> {
let mut input: Vec<u8> = Vec::with_capacity(4096);
let mut output: Vec<u8> = Vec::new();
let mut chunk = [0u8; 4096];
loop {
let after = drain_commands(kevy, store, &mut input, &mut output);
if !output.is_empty() {
conn.write_all(&output)?;
output.clear();
}
if matches!(after, AfterDrain::Close) {
return Ok(());
}
let n = conn.read(&mut chunk)?;
if n == 0 {
return Ok(());
}
input.extend_from_slice(&chunk[..n]);
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_op_table;
#[cfg(test)]
mod tests_verb_meta;
pub(crate) fn kevy_rt_push_tick_frame(seg_file: &str) {
let argv = kevy_persist::segmented_argv(seg_file.as_bytes());
kevy_rt::propagation::push_tick_frame(argv.iter().map(|a| a.to_vec()).collect());
}