use std::{
sync::{
Arc, LazyLock,
atomic::{AtomicI64, Ordering},
},
time::Duration,
};
use super::{
config_kind::ConfigKind,
config_meta::{ConfigMeta, ConfigUpdateAction, ConfigUpdateOwner, EnumMeta},
config_name_comparer::ConfigNameComparer,
config_time_unit::ConfigTimeUnit,
error::ConfigError,
log_compaction_type::LogCompactionType,
runtime_server_options::RuntimeServerOptions,
server_config_type::ServerConfigType,
};
pub struct RuntimeServerConfig {
values: [AtomicI64; Self::TABLE_SIZE],
options: RuntimeServerOptions,
owner: Option<Arc<dyn ConfigUpdateOwner>>,
}
static META: LazyLock<Box<[ConfigMeta]>> = LazyLock::new(RuntimeServerConfig::build_meta);
static NAME_LOOKUP: LazyLock<Vec<(&'static [u8], ServerConfigType)>> =
LazyLock::new(RuntimeServerConfig::build_name_lookup);
static RUNTIME_TYPES: LazyLock<Vec<ServerConfigType>> =
LazyLock::new(RuntimeServerConfig::build_runtime_types);
impl RuntimeServerConfig {
#[inline]
pub const fn compute_table_size() -> usize {
(ServerConfigType::AofNullDevice as u16 + 1) as usize
}
const TABLE_SIZE: usize = Self::compute_table_size();
pub fn new(options: RuntimeServerOptions, owner: Option<Arc<dyn ConfigUpdateOwner>>) -> Self {
let config = Self {
values: [const { AtomicI64::new(0) }; Self::TABLE_SIZE],
options,
owner,
};
config.init(&config.options);
config
}
pub fn with_defaults() -> Self {
Self::new(RuntimeServerOptions::default(), None)
}
#[inline]
pub fn runtime_types() -> &'static [ServerConfigType] {
&RUNTIME_TYPES
}
fn init(&self, o: &RuntimeServerOptions) {
let seed = |t: ServerConfigType, v: i64| {
self.values[t as usize].store(v, Ordering::Release);
};
seed(
ServerConfigType::ClusterNodeTimeout,
i64::from(o.cluster_timeout),
);
seed(
ServerConfigType::ReplicaSyncDelay,
i64::from(o.replica_sync_delay_ms),
);
seed(
ServerConfigType::AofReplayMaxLagBytes,
i64::from(o.aof_replay_max_lag_bytes),
);
seed(
ServerConfigType::AofTailWitnessFreq,
i64::from(o.aof_tail_witness_freq_ms),
);
seed(
ServerConfigType::AofSyncMaxLagBytes,
o.aof_sync_max_lag_bytes,
);
seed(
ServerConfigType::ReplDisklessSyncDelay,
i64::from(o.replica_diskless_sync_delay),
);
seed(
ServerConfigType::ReplAttachTimeout,
Self::seconds_from_time_span(o.replica_attach_timeout_secs),
);
seed(
ServerConfigType::ClusterReplicationReestablishmentTimeout,
i64::from(o.cluster_replication_reestablishment_timeout),
);
seed(
ServerConfigType::CompactionMaxSegments,
i64::from(o.compaction_max_segments),
);
seed(
ServerConfigType::CompactionForceDelete,
i64::from(o.compaction_force_delete),
);
seed(
ServerConfigType::CompactionType,
i64::from(o.compaction_type as u8),
);
seed(
ServerConfigType::SlowlogLogSlowerThan,
i64::from(o.slow_log_threshold),
);
seed(
ServerConfigType::ObjectScanCountLimit,
i64::from(o.object_scan_count_limit),
);
seed(
ServerConfigType::SgGet,
i64::from(o.enable_scatter_gather_get),
);
seed(
ServerConfigType::AofSizeLimitEnforceFrequency,
i64::from(o.aof_size_limit_enforce_frequency_secs),
);
seed(
ServerConfigType::AofCommitFreq,
i64::from(o.commit_frequency_ms),
);
seed(
ServerConfigType::ExpiredObjectCollectionFreq,
i64::from(o.expired_object_collection_frequency_secs),
);
seed(
ServerConfigType::ExpiredKeyDeletionScanFreq,
i64::from(o.expired_key_deletion_scan_frequency_secs),
);
}
fn build_meta() -> Box<[ConfigMeta]> {
let mut m = vec![ConfigMeta::EMPTY; Self::TABLE_SIZE];
let set = |m: &mut [ConfigMeta],
t: ServerConfigType,
name: &'static str,
kind: ConfigKind,
min: i64,
max: i64,
enum_type: Option<EnumMeta>,
time_unit: ConfigTimeUnit,
update_action: Option<ConfigUpdateAction>| {
if (kind & ConfigKind::ENUM) != ConfigKind::NONE {
debug_assert!(
Self::ensure_supported_enum(enum_type).is_ok(),
"运行时配置选项声明的枚举类别不受支持"
);
}
debug_assert!(Self::ensure_valid_kind(name, kind, time_unit).is_ok());
m[t as usize] = ConfigMeta {
name,
kind,
min,
max,
enum_type,
is_runtime: true,
read_only: false,
time_unit,
read_only_formatter: None,
update_action,
};
};
let set_read_only = |m: &mut [ConfigMeta],
t: ServerConfigType,
name: &'static str,
kind: ConfigKind,
formatter: fn(&RuntimeServerOptions) -> String,
time_unit: ConfigTimeUnit| {
debug_assert!(Self::ensure_valid_kind(name, kind, time_unit).is_ok());
m[t as usize] = ConfigMeta {
name,
kind,
min: 0,
max: 0,
enum_type: None,
is_runtime: true,
read_only: true,
time_unit,
read_only_formatter: Some(formatter),
update_action: None,
};
};
set_read_only(
&mut m,
ServerConfigType::Timeout,
"timeout",
ConfigKind::INT32 | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
|_| "0".into(),
ConfigTimeUnit::Seconds,
);
set_read_only(
&mut m,
ServerConfigType::Save,
"save",
ConfigKind::STRING,
|_| String::new(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AppendOnly,
"appendonly",
ConfigKind::BOOL,
|o| {
if o.enable_aof {
"yes".into()
} else {
"no".into()
}
},
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::Databases,
"databases",
ConfigKind::INT32,
|o| o.max_databases.to_string(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::Dir,
"dir",
ConfigKind::STRING,
|o| o.checkpoint_base_directory.clone(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::Logdir,
"logdir",
ConfigKind::STRING,
|o| o.log_dir.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::UnixSocket,
"unixsocket",
ConfigKind::STRING,
|o| o.unix_socket_path.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::ClusterEnabled,
"cluster-enabled",
ConfigKind::BOOL,
|o| {
if o.enable_cluster {
"yes".into()
} else {
"no".into()
}
},
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofMemory,
"aof-memory",
ConfigKind::STRING,
|o| o.aof_memory_size.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofPageSize,
"aof-page-size",
ConfigKind::STRING,
|o| o.aof_page_size.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofSegmentSize,
"aof-segment-size",
ConfigKind::STRING,
|o| o.aof_segment_size.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofPhysicalSublogCount,
"aof-physical-sublog-count",
ConfigKind::INT32,
|o| o.aof_physical_sublog_count.to_string(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofReplayTaskCount,
"aof-replay-task-count",
ConfigKind::INT32,
|o| o.aof_replay_task_count.to_string(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofCommitWait,
"aof-commit-wait",
ConfigKind::BOOL,
|o| {
if o.wait_for_commit {
"yes".into()
} else {
"no".into()
}
},
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofSizeLimit,
"aof-size-limit",
ConfigKind::STRING,
|o| o.aof_size_limit.clone().unwrap_or_default(),
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::FastAofTruncate,
"fast-aof-truncate",
ConfigKind::BOOL,
|o| {
if o.fast_aof_truncate {
"yes".into()
} else {
"no".into()
}
},
ConfigTimeUnit::None,
);
set_read_only(
&mut m,
ServerConfigType::AofNullDevice,
"aof-null-device",
ConfigKind::BOOL,
|o| {
if o.use_aof_null_device {
"yes".into()
} else {
"no".into()
}
},
ConfigTimeUnit::None,
);
set(
&mut m,
ServerConfigType::ClusterNodeTimeout,
"cluster-node-timeout",
ConfigKind::INT32 | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Seconds,
None,
);
set(
&mut m,
ServerConfigType::ReplicaSyncDelay,
"replica-sync-delay",
ConfigKind::INT32 | ConfigKind::MILLISECONDS | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Milliseconds,
None,
);
set(
&mut m,
ServerConfigType::AofReplayMaxLagBytes,
"aof-replay-max-lag-bytes",
ConfigKind::INT32,
-1,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::AofSyncMaxLagBytes,
"aof-sync-max-lag-bytes",
ConfigKind::INT64,
-1,
i64::MAX,
None,
ConfigTimeUnit::None,
Some(Self::apply_aof_sync_max_lag_update),
);
set(
&mut m,
ServerConfigType::AofTailWitnessFreq,
"aof-tail-witness-freq",
ConfigKind::INT32 | ConfigKind::MILLISECONDS | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Milliseconds,
None,
);
set(
&mut m,
ServerConfigType::ReplDisklessSyncDelay,
"repl-diskless-sync-delay",
ConfigKind::INT32 | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Seconds,
None,
);
set(
&mut m,
ServerConfigType::ReplAttachTimeout,
"repl-attach-timeout",
ConfigKind::INT32 | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Seconds,
None,
);
set(
&mut m,
ServerConfigType::ClusterReplicationReestablishmentTimeout,
"cluster-replication-reestablishment-timeout",
ConfigKind::INT32 | ConfigKind::SECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Seconds,
None,
);
set(
&mut m,
ServerConfigType::CompactionMaxSegments,
"compaction-max-segments",
ConfigKind::INT32,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::CompactionForceDelete,
"compaction-force-delete",
ConfigKind::BOOL,
0,
1,
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::CompactionType,
"compaction-type",
ConfigKind::ENUM,
0,
0,
Some(EnumMeta::LogCompactionType),
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::SlowlogLogSlowerThan,
"slowlog-log-slower-than",
ConfigKind::INT32 | ConfigKind::MICROSECONDS | ConfigKind::TIME_SPAN,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::Microseconds,
None,
);
set(
&mut m,
ServerConfigType::ObjectScanCountLimit,
"object-scan-count-limit",
ConfigKind::INT32,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::SgGet,
"sg-get",
ConfigKind::BOOL,
0,
1,
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::AofSizeLimitEnforceFrequency,
"aof-size-limit-enforce-frequency",
ConfigKind::INT32,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
None,
);
set(
&mut m,
ServerConfigType::AofCommitFreq,
"aof-commit-freq",
ConfigKind::INT32,
-1,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
Some(Self::apply_commit_frequency_update),
);
set(
&mut m,
ServerConfigType::ExpiredObjectCollectionFreq,
"expired-object-collection-freq",
ConfigKind::INT32,
0,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
Some(Self::apply_expired_object_collection_update),
);
set(
&mut m,
ServerConfigType::ExpiredKeyDeletionScanFreq,
"expired-key-deletion-scan-freq",
ConfigKind::INT32,
-1,
i64::from(i32::MAX),
None,
ConfigTimeUnit::None,
Some(Self::apply_expired_key_deletion_update),
);
m.into()
}
fn build_name_lookup() -> Vec<(&'static [u8], ServerConfigType)> {
let mut d = Vec::with_capacity(Self::TABLE_SIZE + 1);
for (i, meta) in META.iter().enumerate() {
if meta.is_runtime {
d.push((meta.name.as_bytes(), ServerConfigType::ALL_MEMBERS[i]));
}
}
d.push((b"cluster-timeout", ServerConfigType::ClusterNodeTimeout));
d
}
fn build_runtime_types() -> Vec<ServerConfigType> {
META
.iter()
.enumerate()
.filter(|(_, meta)| meta.is_runtime)
.map(|(i, _)| ServerConfigType::ALL_MEMBERS[i])
.collect()
}
#[inline]
pub fn get_int(&self, type_: ServerConfigType) -> i32 {
Self::assert_kind(type_, ConfigKind::INT32);
self.values[type_ as usize].load(Ordering::Acquire) as i32
}
#[inline]
pub fn get_long(&self, type_: ServerConfigType) -> i64 {
Self::assert_kind(type_, ConfigKind::INT64);
self.values[type_ as usize].load(Ordering::Acquire)
}
#[inline]
pub fn get_bool(&self, type_: ServerConfigType) -> bool {
Self::assert_kind(type_, ConfigKind::BOOL);
self.values[type_ as usize].load(Ordering::Acquire) != 0
}
#[inline]
pub fn get_microseconds(&self, type_: ServerConfigType) -> i64 {
self.convert_duration(
type_,
ConfigKind::MICROSECONDS,
ConfigTimeUnit::Microseconds,
)
}
#[inline]
pub fn get_milliseconds(&self, type_: ServerConfigType) -> i64 {
self.convert_duration(
type_,
ConfigKind::MILLISECONDS,
ConfigTimeUnit::Milliseconds,
)
}
#[inline]
pub fn get_seconds(&self, type_: ServerConfigType) -> i64 {
self.convert_duration(type_, ConfigKind::SECONDS, ConfigTimeUnit::Seconds)
}
#[inline]
pub fn get_time_span(&self, type_: ServerConfigType) -> Option<Duration> {
Self::assert_kind(type_, ConfigKind::TIME_SPAN);
let meta = &META[type_ as usize];
let raw = self.values[type_ as usize].load(Ordering::Acquire);
if raw <= 0 {
return None;
}
Some(match meta.time_unit {
ConfigTimeUnit::Microseconds => Duration::from_micros(raw as u64),
ConfigTimeUnit::Milliseconds => Duration::from_millis(raw as u64),
_ => Duration::from_secs(raw as u64),
})
}
#[inline]
pub fn get_enum(&self, type_: ServerConfigType) -> Result<LogCompactionType, ConfigError> {
Self::assert_kind(type_, ConfigKind::ENUM);
let raw = self.values[type_ as usize].load(Ordering::Acquire);
LogCompactionType::from_raw(raw).ok_or(ConfigError::EnumOutOfRange { raw })
}
pub fn try_set(&self, type_: ServerConfigType, value: &str) -> Result<(), ConfigError> {
let meta = &META[type_ as usize];
if meta.read_only {
return Err(ConfigError::ReadOnly {
name: meta.name.into(),
});
}
let parsed: i64 = match meta.kind & ConfigKind::STORAGE_MASK {
ConfigKind::INT32 => {
let Ok(i32_value) = value.parse::<i32>() else {
return Err(ConfigError::InvalidInteger {
name: meta.name.into(),
});
};
let v = i64::from(i32_value);
if v < meta.min || v > meta.max {
return Err(ConfigError::OutOfRange {
name: meta.name.into(),
min: meta.min,
max: meta.max,
});
}
v
}
ConfigKind::INT64 => {
let Ok(v) = value.parse::<i64>() else {
return Err(ConfigError::InvalidInteger {
name: meta.name.into(),
});
};
if v < meta.min || v > meta.max {
return Err(ConfigError::OutOfRange {
name: meta.name.into(),
min: meta.min,
max: meta.max,
});
}
v
}
ConfigKind::BOOL => {
if value.eq_ignore_ascii_case("yes") || value.eq_ignore_ascii_case("true") || value == "1" {
1
} else if value.eq_ignore_ascii_case("no")
|| value.eq_ignore_ascii_case("false")
|| value == "0"
{
0
} else {
return Err(ConfigError::InvalidBool {
name: meta.name.into(),
});
}
}
ConfigKind::ENUM => {
let Some(v) = meta.enum_type.and_then(|e| e.try_parse_to_long(value)) else {
return Err(ConfigError::InvalidEnum {
name: meta.name.into(),
value: value.into(),
});
};
v
}
_ => {
return Err(ConfigError::NotRuntimeAdjustable {
name: meta.name.into(),
});
}
};
let old_value = self.values[type_ as usize].load(Ordering::Acquire);
self.values[type_ as usize].store(parsed, Ordering::Release);
if let Some(Err(error)) = meta
.update_action
.map(|action| action(self, old_value, parsed))
{
self.values[type_ as usize].store(old_value, Ordering::Release);
return Err(error);
}
Ok(())
}
fn apply_commit_frequency_update(
&self,
_old_value: i64,
new_value: i64,
) -> Result<(), ConfigError> {
if new_value == 0 {
return Err(ConfigError::CommitFreqZero);
}
if self.options.commit_frequency_ms == 0 {
return Err(ConfigError::CommitFreqAutoCommitStart);
}
if let Some(owner) = &self.owner {
owner.reconcile_commit_task();
}
Ok(())
}
fn apply_aof_sync_max_lag_update(
&self,
_old_value: i64,
new_value: i64,
) -> Result<(), ConfigError> {
if let Some(owner) = &self.owner {
owner.apply_aof_sync_max_lag_bytes(new_value);
}
Ok(())
}
fn apply_expired_object_collection_update(
&self,
_old_value: i64,
_new_value: i64,
) -> Result<(), ConfigError> {
if let Some(owner) = &self.owner {
owner.reconcile_object_collect_task();
}
Ok(())
}
fn apply_expired_key_deletion_update(
&self,
_old_value: i64,
_new_value: i64,
) -> Result<(), ConfigError> {
if let Some(owner) = &self.owner {
owner.reconcile_expired_key_deletion_task();
}
Ok(())
}
#[inline]
pub fn name(type_: ServerConfigType) -> &'static str {
META[type_ as usize].name
}
#[inline]
pub fn try_get_type(name: &[u8]) -> Option<ServerConfigType> {
NAME_LOOKUP
.iter()
.find(|(key, _)| ConfigNameComparer::equals(name, key))
.map(|(_, t)| *t)
}
#[inline]
fn seconds_from_time_span(ts_secs: i64) -> i64 {
if ts_secs <= 0 { 0 } else { ts_secs }
}
#[inline]
fn assert_kind(type_: ServerConfigType, requested_kind: ConfigKind) {
debug_assert!(
(META[type_ as usize].kind & requested_kind) != ConfigKind::NONE,
"配置 {type_:?} 声明为 {:?},不能按 {requested_kind:?} 读取",
META[type_ as usize].kind
);
}
#[inline]
fn convert_duration(
&self,
type_: ServerConfigType,
requested_kind: ConfigKind,
requested_unit: ConfigTimeUnit,
) -> i64 {
Self::assert_kind(type_, requested_kind);
let meta = &META[type_ as usize];
let raw = self.values[type_ as usize].load(Ordering::Acquire);
if meta.time_unit == requested_unit {
return raw;
}
let stored_micros = match meta.time_unit {
ConfigTimeUnit::Microseconds => raw,
ConfigTimeUnit::Milliseconds => raw * 1000,
_ => raw * 1_000_000,
};
match requested_unit {
ConfigTimeUnit::Microseconds => stored_micros,
ConfigTimeUnit::Milliseconds => stored_micros / 1000,
_ => stored_micros / 1_000_000,
}
}
fn ensure_valid_kind(
name: &'static str,
kind: ConfigKind,
time_unit: ConfigTimeUnit,
) -> Result<(), &'static str> {
let storage_kind = kind & ConfigKind::STORAGE_MASK;
let single = storage_kind.bits() != 0 && (storage_kind.bits() & (storage_kind.bits() - 1)) == 0;
if !single {
return Err("必须声明恰好一个 storage 类别");
}
if (kind & ConfigKind::DURATION_MASK) != ConfigKind::NONE && time_unit == ConfigTimeUnit::None {
let _ = name;
return Err("声明了 duration 视图但未声明时间单位");
}
if (kind & ConfigKind::DURATION_MASK) == ConfigKind::NONE && time_unit != ConfigTimeUnit::None {
let _ = name;
return Err("声明了时间单位但没有 duration 视图");
}
Ok(())
}
fn ensure_supported_enum(enum_type: Option<EnumMeta>) -> Result<(), &'static str> {
if enum_type.is_none() {
return Err("运行时配置选项未声明枚举类别");
}
Ok(())
}
pub fn resp_format(&self, type_: ServerConfigType) -> String {
let meta = &META[type_ as usize];
if meta.read_only {
return meta
.read_only_formatter
.map_or_else(String::new, |f| f(&self.options));
}
let raw = self.values[type_ as usize].load(Ordering::Acquire);
match meta.kind & ConfigKind::STORAGE_MASK {
ConfigKind::INT32 => (raw as i32).to_string(),
ConfigKind::INT64 => raw.to_string(),
ConfigKind::BOOL => if raw != 0 { "yes" } else { "no" }.into(),
ConfigKind::ENUM => meta
.enum_type
.and_then(|e| e.name_of(raw))
.map_or_else(|| raw.to_string(), str::to_owned),
_ => raw.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use super::*;
use crate::config::error::ConfigError;
struct TestOwner {
commit: AtomicUsize,
collect: AtomicUsize,
expiry: AtomicUsize,
lag: AtomicI64,
}
impl TestOwner {
fn new() -> Arc<Self> {
Arc::new(Self {
commit: AtomicUsize::new(0),
collect: AtomicUsize::new(0),
expiry: AtomicUsize::new(0),
lag: AtomicI64::new(0),
})
}
}
impl ConfigUpdateOwner for TestOwner {
fn reconcile_commit_task(&self) {
self.commit.fetch_add(1, AtomicOrdering::Relaxed);
}
fn reconcile_object_collect_task(&self) {
self.collect.fetch_add(1, AtomicOrdering::Relaxed);
}
fn reconcile_expired_key_deletion_task(&self) {
self.expiry.fetch_add(1, AtomicOrdering::Relaxed);
}
fn apply_aof_sync_max_lag_bytes(&self, max_lag_bytes: i64) {
self.lag.store(max_lag_bytes, AtomicOrdering::Relaxed);
}
}
#[test]
fn table_size() {
assert_eq!(RuntimeServerConfig::compute_table_size(), 38);
assert_eq!(META.len(), 38);
}
#[test]
fn meta_static_validity() {
for (i, meta) in META.iter().enumerate() {
if !meta.is_runtime {
assert_eq!(meta.name, "");
continue;
}
assert!(
RuntimeServerConfig::ensure_valid_kind(meta.name, meta.kind, meta.time_unit).is_ok(),
"槽位 {i} 元数据非法"
);
if (meta.kind & ConfigKind::ENUM) != ConfigKind::NONE {
assert!(RuntimeServerConfig::ensure_supported_enum(meta.enum_type).is_ok());
}
}
}
#[test]
fn name_lookup_contains_alias_and_case_insensitive() {
assert_eq!(
RuntimeServerConfig::try_get_type(b"cluster-node-timeout"),
Some(ServerConfigType::ClusterNodeTimeout)
);
assert_eq!(
RuntimeServerConfig::try_get_type(b"CLUSTER-TIMEOUT"),
Some(ServerConfigType::ClusterNodeTimeout)
);
assert_eq!(
RuntimeServerConfig::try_get_type(b"slowlog-log-slower-than"),
Some(ServerConfigType::SlowlogLogSlowerThan)
);
assert_eq!(RuntimeServerConfig::try_get_type(b"nonexistent"), None);
assert_eq!(RuntimeServerConfig::name(ServerConfigType::SgGet), "sg-get");
}
#[test]
fn runtime_types_cover_settable_and_readonly() {
let types = RuntimeServerConfig::runtime_types();
assert!(!types.contains(&ServerConfigType::None));
assert!(!types.contains(&ServerConfigType::SlaveReadOnly));
assert!(types.contains(&ServerConfigType::ClusterNodeTimeout));
assert!(types.contains(&ServerConfigType::Dir));
assert!(types.contains(&ServerConfigType::AofNullDevice));
}
#[test]
fn init_seeds_slots_from_options() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(config.get_int(ServerConfigType::ClusterNodeTimeout), 60);
assert_eq!(config.get_int(ServerConfigType::ReplicaSyncDelay), 5);
assert_eq!(config.get_long(ServerConfigType::AofSyncMaxLagBytes), -1);
assert_eq!(config.get_int(ServerConfigType::AofReplayMaxLagBytes), -1);
assert!(!config.get_bool(ServerConfigType::CompactionForceDelete));
assert!(config.get_bool(ServerConfigType::SgGet));
assert_eq!(
config.get_enum(ServerConfigType::CompactionType),
Ok(LogCompactionType::None)
);
assert_eq!(config.get_int(ServerConfigType::ReplAttachTimeout), 60);
assert_eq!(
config.get_int(ServerConfigType::ExpiredKeyDeletionScanFreq),
-1
);
}
#[test]
fn duration_unit_conversions() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.get_microseconds(ServerConfigType::SlowlogLogSlowerThan),
0
);
assert_eq!(
config.get_milliseconds(ServerConfigType::ReplicaSyncDelay),
5
);
assert_eq!(config.get_seconds(ServerConfigType::ReplicaSyncDelay), 0);
assert_eq!(config.get_seconds(ServerConfigType::ClusterNodeTimeout), 60);
assert_eq!(
config.get_milliseconds(ServerConfigType::AofTailWitnessFreq),
100
);
}
#[test]
fn time_span_non_positive_means_infinite() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.get_time_span(ServerConfigType::ClusterNodeTimeout),
Some(Duration::from_secs(60))
);
assert_eq!(
config.try_set(ServerConfigType::ClusterNodeTimeout, "0"),
Ok(())
);
assert_eq!(
config.get_time_span(ServerConfigType::ClusterNodeTimeout),
None
);
}
#[test]
fn try_set_int_range_and_errors() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.try_set(ServerConfigType::ObjectScanCountLimit, "2000"),
Ok(())
);
assert_eq!(config.get_int(ServerConfigType::ObjectScanCountLimit), 2000);
assert_eq!(
config.try_set(ServerConfigType::ObjectScanCountLimit, "abc"),
Err(ConfigError::InvalidInteger {
name: "object-scan-count-limit".into()
})
);
assert_eq!(
config.try_set(ServerConfigType::ObjectScanCountLimit, "-1"),
Err(ConfigError::OutOfRange {
name: "object-scan-count-limit".into(),
min: 0,
max: i64::from(i32::MAX)
})
);
assert_eq!(config.get_int(ServerConfigType::ObjectScanCountLimit), 2000);
assert_eq!(
config.try_set(ServerConfigType::AofReplayMaxLagBytes, "-1"),
Ok(())
);
assert_eq!(
config.try_set(ServerConfigType::AofReplayMaxLagBytes, "-2"),
Err(ConfigError::OutOfRange {
name: "aof-replay-max-lag-bytes".into(),
min: -1,
max: i64::from(i32::MAX)
})
);
}
#[test]
fn try_set_bool_forms() {
let config = RuntimeServerConfig::with_defaults();
for yes in ["yes", "YES", "true", "1"] {
assert_eq!(config.try_set(ServerConfigType::SgGet, yes), Ok(()));
assert!(config.get_bool(ServerConfigType::SgGet));
}
for no in ["no", "False", "0"] {
assert_eq!(config.try_set(ServerConfigType::SgGet, no), Ok(()));
assert!(!config.get_bool(ServerConfigType::SgGet));
}
assert!(matches!(
config.try_set(ServerConfigType::SgGet, "maybe"),
Err(ConfigError::InvalidBool { .. })
));
}
#[test]
fn try_set_enum_by_name_and_number() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.try_set(ServerConfigType::CompactionType, "lookup"),
Ok(())
);
assert_eq!(
config.get_enum(ServerConfigType::CompactionType),
Ok(LogCompactionType::Lookup)
);
assert_eq!(
config.try_set(ServerConfigType::CompactionType, "3"),
Ok(())
);
assert_eq!(
config.get_enum(ServerConfigType::CompactionType),
Ok(LogCompactionType::Scan)
);
assert!(matches!(
config.try_set(ServerConfigType::CompactionType, "9"),
Err(ConfigError::InvalidEnum { .. })
));
assert!(matches!(
config.try_set(ServerConfigType::CompactionType, "bogus"),
Err(ConfigError::InvalidEnum { .. })
));
}
#[test]
fn read_only_rejects_set_and_falls_through_options() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.try_set(ServerConfigType::AppendOnly, "yes"),
Err(ConfigError::ReadOnly {
name: "appendonly".into()
})
);
assert_eq!(config.resp_format(ServerConfigType::AppendOnly), "no");
assert_eq!(config.resp_format(ServerConfigType::Timeout), "0");
assert_eq!(config.resp_format(ServerConfigType::Save), "");
assert_eq!(config.resp_format(ServerConfigType::Databases), "16");
assert_eq!(config.resp_format(ServerConfigType::ClusterEnabled), "no");
assert_eq!(config.resp_format(ServerConfigType::AofMemory), "128m");
assert_eq!(
config.resp_format(ServerConfigType::AofPhysicalSublogCount),
"1"
);
assert_eq!(config.resp_format(ServerConfigType::UnixSocket), "");
}
#[test]
fn update_actions_invoke_owner_and_rollback_on_reject() {
let owner = TestOwner::new();
let options = RuntimeServerOptions {
commit_frequency_ms: -1,
..RuntimeServerOptions::default()
};
let config = RuntimeServerConfig::new(options, Some(owner.clone()));
assert_eq!(
config.try_set(ServerConfigType::AofCommitFreq, "5000"),
Ok(())
);
assert_eq!(config.get_int(ServerConfigType::AofCommitFreq), 5000);
assert_eq!(owner.commit.load(AtomicOrdering::Relaxed), 1);
assert_eq!(
config.try_set(ServerConfigType::AofCommitFreq, "0"),
Err(ConfigError::CommitFreqZero)
);
assert_eq!(config.get_int(ServerConfigType::AofCommitFreq), 5000);
assert_eq!(owner.commit.load(AtomicOrdering::Relaxed), 1);
assert_eq!(
config.try_set(ServerConfigType::AofSyncMaxLagBytes, "123456"),
Ok(())
);
assert_eq!(owner.lag.load(AtomicOrdering::Relaxed), 123456);
assert_eq!(
config.try_set(ServerConfigType::ExpiredObjectCollectionFreq, "30"),
Ok(())
);
assert_eq!(owner.collect.load(AtomicOrdering::Relaxed), 1);
assert_eq!(
config.try_set(ServerConfigType::ExpiredKeyDeletionScanFreq, "15"),
Ok(())
);
assert_eq!(owner.expiry.load(AtomicOrdering::Relaxed), 1);
let bare_options = RuntimeServerOptions {
commit_frequency_ms: -1,
..RuntimeServerOptions::default()
};
let bare = RuntimeServerConfig::new(bare_options, None);
assert_eq!(bare.try_set(ServerConfigType::AofCommitFreq, "100"), Ok(()));
}
#[test]
fn commit_freq_rejected_when_started_auto_commit() {
let options = RuntimeServerOptions {
commit_frequency_ms: 0,
..RuntimeServerOptions::default()
};
let config = RuntimeServerConfig::new(options, None);
assert_eq!(
config.try_set(ServerConfigType::AofCommitFreq, "100"),
Err(ConfigError::CommitFreqAutoCommitStart)
);
}
#[test]
fn resp_format_of_runtime_slots() {
let config = RuntimeServerConfig::with_defaults();
assert_eq!(
config.resp_format(ServerConfigType::ClusterNodeTimeout),
"60"
);
assert_eq!(config.resp_format(ServerConfigType::SgGet), "yes");
assert_eq!(config.resp_format(ServerConfigType::CompactionType), "None");
assert_eq!(
config.try_set(ServerConfigType::CompactionType, "Shift"),
Ok(())
);
assert_eq!(
config.resp_format(ServerConfigType::CompactionType),
"Shift"
);
assert_eq!(
config.resp_format(ServerConfigType::AofSyncMaxLagBytes),
"-1"
);
}
#[test]
fn seconds_from_time_span_non_positive_is_zero() {
assert_eq!(RuntimeServerConfig::seconds_from_time_span(60), 60);
assert_eq!(RuntimeServerConfig::seconds_from_time_span(0), 0);
assert_eq!(RuntimeServerConfig::seconds_from_time_span(-1), 0);
}
#[test]
fn name_comparer_semantics() {
use crate::config::config_name_comparer::ConfigNameComparer;
assert!(ConfigNameComparer::equals(b"AppendOnly", b"appendonly"));
assert!(!ConfigNameComparer::equals(b"appendonly", b"appendonlyx"));
assert_eq!(ConfigNameComparer::to_upper_ascii(b'a'), b'A');
assert_eq!(ConfigNameComparer::to_upper_ascii(b'0'), b'0');
}
}