mod catalogs;
mod election;
mod obs;
mod progress;
mod replication;
mod scope;
mod shard;
pub(crate) use catalogs::CatalogState;
pub(crate) use election::ElectionState;
pub(crate) use obs::{ObsState, ReplShardView, ShardStats, Totals};
pub(crate) use progress::ReplicaProgress;
pub(crate) use replication::ReplicationState;
pub(crate) use scope::{ScopeState, WriteRedirect, encode_misdirected, encode_quiesced};
pub(crate) use shard::{
IDX_NONEMPTY, READ_GATED, SCOPE_ACTIVE, ShardCtx, TABLE_NONEMPTY, VIEW_NONEMPTY, WRITE_GATED,
};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use kevy_config::Config;
#[derive(Debug)]
pub struct RuntimeState {
config: RwLock<Arc<Config>>,
config_explicit: AtomicBool,
data_dir: PathBuf,
nshards: usize,
pub(crate) election: ElectionState,
pub(crate) scope: ScopeState,
pub(crate) catalogs: CatalogState,
pub(crate) obs: ObsState,
pub(crate) replication: Arc<ReplicationState>,
control_epoch: Arc<AtomicU64>,
shutdown_stop: Mutex<Option<Arc<AtomicBool>>>,
shutdown_save: AtomicBool,
}
impl RuntimeState {
pub fn new(cfg: Arc<Config>, data_dir: PathBuf, nshards: usize) -> Result<Self, String> {
let mut state = Self::build(cfg, data_dir, nshards)?;
*state.config_explicit.get_mut() = true;
Ok(state)
}
fn embedded(nshards: usize) -> Self {
Self::build(Arc::new(Config::default()), PathBuf::new(), nshards)
.expect("Config::default() declares no scopes")
}
fn build(cfg: Arc<Config>, data_dir: PathBuf, nshards: usize) -> Result<Self, String> {
let replication = Arc::new(ReplicationState::new(
nshards,
cfg.replication.single_source,
cfg.server.port,
));
Ok(Self {
scope: ScopeState::from_config(&cfg)?,
election: ElectionState::new(nshards),
catalogs: CatalogState::new(),
obs: ObsState::new(&cfg.audit.log_path, nshards),
control_epoch: replication.control_epoch_handle(),
replication,
config: RwLock::new(cfg),
config_explicit: AtomicBool::new(false),
data_dir,
nshards,
shutdown_stop: Mutex::new(None),
shutdown_save: AtomicBool::new(false),
})
}
pub fn register_stop_flag(&self, stop: Arc<AtomicBool>) {
*self.shutdown_stop.lock().expect("shutdown_stop poisoned") = Some(stop);
}
pub(crate) fn request_shutdown(&self, save: bool) -> bool {
self.shutdown_save.store(save, Ordering::Release);
match &*self.shutdown_stop.lock().expect("shutdown_stop poisoned") {
Some(stop) => {
stop.store(true, Ordering::Release);
true
}
None => false,
}
}
pub(crate) fn shutdown_save_requested(&self) -> bool {
self.shutdown_save.load(Ordering::Acquire)
}
pub fn config(&self) -> Arc<Config> {
self.config.read().expect("config poisoned").clone()
}
pub fn config_replace(&self, cfg: Arc<Config>) {
*self.config.write().expect("config poisoned") = cfg;
self.config_explicit.store(true, Ordering::Release);
}
pub(crate) fn set_data_dir(&self, dir: std::path::PathBuf) {
let mut w = self.config.write().expect("config poisoned");
if w.server.data_dir == dir {
return; }
let mut next = (**w).clone();
next.server.data_dir = dir;
*w = Arc::new(next);
}
pub(crate) fn config_is_explicit(&self) -> bool {
self.config_explicit.load(Ordering::Acquire)
}
pub(crate) fn sidecar_dir(&self) -> Option<&Path> {
if self.data_dir.as_os_str().is_empty() { None } else { Some(&self.data_dir) }
}
pub(crate) fn nshards(&self) -> usize {
self.nshards
}
pub fn take_replica_inboxes(&self) -> Option<Vec<kevy_rt::ReplicaInboxReceiver>> {
self.replication.take_inboxes()
}
pub(crate) fn control_epoch(&self) -> &AtomicU64 {
&self.control_epoch
}
pub(crate) fn bump_control_epoch(&self) {
self.control_epoch.fetch_add(1, Ordering::Release);
}
}
pub(crate) struct Ctx<'a> {
pub(crate) state: &'a Arc<RuntimeState>,
pub(crate) shard: &'a ShardCtx,
}
#[derive(Debug)]
pub struct KevyCommands {
state: Arc<RuntimeState>,
shard: ShardCtx,
}
impl Clone for KevyCommands {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
shard: ShardCtx::default(),
}
}
}
impl Default for KevyCommands {
fn default() -> Self {
Self::new()
}
}
impl KevyCommands {
pub fn new() -> Self {
Self::sharded(1)
}
pub fn sharded(nshards: usize) -> Self {
let mut state = RuntimeState::embedded(nshards);
if nshards > 1 {
let mut cfg = Config::default();
cfg.server.threads = nshards;
*state.config.get_mut().expect("config poisoned") = Arc::new(cfg);
}
Self::with_state(Arc::new(state))
}
pub fn with_state(state: Arc<RuntimeState>) -> Self {
Self { state, shard: ShardCtx::default() }
}
pub fn state(&self) -> &Arc<RuntimeState> {
&self.state
}
pub fn dispatch<A: kevy_resp::ArgvView + ?Sized>(
&self,
store: &mut kevy_store::Store,
args: &A,
) -> Vec<u8> {
crate::dispatch::dispatch(&self.ctx(), store, args)
}
pub fn dispatch_into<A: kevy_resp::ArgvView + ?Sized>(
&self,
store: &mut kevy_store::Store,
args: &A,
out: &mut Vec<u8>,
) {
crate::dispatch::dispatch_into(&self.ctx(), store, args, out);
}
pub(crate) fn shard_ctx(&self) -> &ShardCtx {
&self.shard
}
#[inline]
pub(crate) fn gate_bits(&self) -> u32 {
self.shard.gate_bits(&self.state)
}
pub(crate) fn ctx(&self) -> Ctx<'_> {
Ctx { state: &self.state, shard: &self.shard }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clone_shares_state_but_not_shard_zone() {
let c = KevyCommands::new();
c.shard.set_shard_id(3);
let c2 = c.clone();
assert!(Arc::ptr_eq(&c.state, &c2.state));
assert_eq!(c.shard.shard_id(), 3);
assert_eq!(c2.shard.shard_id(), 0, "clone must start with a fresh shard zone");
}
#[test]
fn embedded_state_has_no_sidecar_dir_and_no_explicit_config() {
let c = KevyCommands::new();
assert!(c.state.sidecar_dir().is_none());
assert!(!c.state.config_is_explicit());
}
#[test]
fn explicit_state_reports_config_and_sidecar_dir() {
let cfg = Arc::new(Config::default());
let state =
RuntimeState::new(cfg, PathBuf::from("/tmp/kevy-x"), 2).expect("default scopes");
assert!(state.config_is_explicit());
assert_eq!(state.sidecar_dir(), Some(Path::new("/tmp/kevy-x")));
assert_eq!(state.nshards(), 2);
}
#[test]
fn config_replace_flips_explicit_and_swaps_value() {
let c = KevyCommands::new();
let mut cfg = Config::default();
cfg.memory.maxmemory = 12345;
c.state.config_replace(Arc::new(cfg));
assert!(c.state.config_is_explicit());
assert_eq!(c.state.config().memory.maxmemory, 12345);
}
#[test]
fn sharded_state_mirrors_nshards_into_config_threads() {
let c = KevyCommands::sharded(4);
assert_eq!(c.state.config().server.threads, 4);
assert!(!c.state.config_is_explicit());
}
}