Skip to content
4 changes: 4 additions & 0 deletions src/conf/kiwi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ memory 10M
# Log directory for Kiwi logs
# log-dir /data/kiwi_rs/logs

# Directory where RocksDB data files are stored.
# When db-instance-num > 1, each instance creates a subdirectory (e.g., ./db/0, ./db/1).
# db-dir ./db

# Enable Redis compatibility mode
# redis-compatible-mode yes

Expand Down
13 changes: 13 additions & 0 deletions src/conf/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub struct Config {
pub binding: String,
pub timeout: u32,
pub log_dir: String,
pub db_dir: String,
pub redis_compatible_mode: bool,
pub db_instance_num: usize,
pub db_path: String,
Expand All @@ -77,6 +78,7 @@ impl Default for Config {
timeout: 50,
memory: 1024 * 1024 * 1024, // 1GB
log_dir: "/data/kiwi_rs/logs".to_string(),
db_dir: "./db".to_string(),
redis_compatible_mode: false,

rocksdb_max_subcompactions: 0,
Expand Down Expand Up @@ -142,6 +144,17 @@ impl Config {
"log-dir" => {
config.log_dir = value;
}
"db-dir" => {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(Error::InvalidConfig {
source: serde_ini::de::Error::Custom(
"db-dir must not be empty".to_string(),
),
});
}
config.db_dir = trimmed.to_string();
}
"redis-compatible-mode" => {
config.redis_compatible_mode =
parse_bool_from_string(&value).map_err(|e| Error::InvalidConfig {
Expand Down
63 changes: 63 additions & 0 deletions src/conf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod tests {
use validator::Validate;

use super::*;
use crate::config::Config;

#[test]
fn test_config_parsing() {
Expand Down Expand Up @@ -61,6 +62,7 @@ mod tests {

assert_eq!(50, config.timeout);
assert_eq!("/data/kiwi_rs/logs", config.log_dir);
assert_eq!("./db", config.db_dir);
assert!(!config.redis_compatible_mode);
assert_eq!(3, config.db_instance_num);

Expand All @@ -70,4 +72,65 @@ mod tests {
config.validate().err()
);
}

#[test]
fn test_validate_port_range() {
let mut invalid_config = Config {
binding: "127.0.0.1".to_string(),
port: 999,
timeout: 100,
redis_compatible_mode: false,
log_dir: "".to_string(),
db_dir: "./db".to_string(),
memory: 1024,
rocksdb_max_subcompactions: 0,
rocksdb_max_background_jobs: 4,
rocksdb_max_write_buffer_number: 2,
rocksdb_min_write_buffer_number_to_merge: 2,
rocksdb_write_buffer_size: 64 << 20,
rocksdb_level0_file_num_compaction_trigger: 4,
rocksdb_num_levels: 7,
rocksdb_enable_pipelined_write: false,
rocksdb_level0_slowdown_writes_trigger: 20,
rocksdb_level0_stop_writes_trigger: 36,
rocksdb_ttl_second: 30 * 24 * 60 * 60,
rocksdb_periodic_second: 30 * 24 * 60 * 60,
rocksdb_level_compaction_dynamic_level_bytes: true,
rocksdb_max_open_files: 10000,
rocksdb_target_file_size_base: 64 << 20,
db_instance_num: 3,
small_compaction_threshold: 5000,
small_compaction_duration_threshold: 10000,
db_path: "./db".to_string(),
raft: None,
};
assert!(invalid_config.validate().is_err());

invalid_config.port = 8080;
assert!(invalid_config.validate().is_ok());
}

#[test]
fn test_db_dir_default() {
let config = Config::default();
assert_eq!("./db", config.db_dir);
}

#[test]
fn test_db_dir_from_config_file() {
use std::io::Write;

let filename = format!("kiwi_test_db_dir_{}.conf", std::process::id());
let tmp = std::env::temp_dir().join(filename);
let config_path = tmp.to_str().unwrap();
let mut f = std::fs::File::create(config_path).unwrap();
writeln!(f, "port 7379").unwrap();
writeln!(f, "db-dir /data/kiwi/db").unwrap();
drop(f);

let config = Config::load(config_path).unwrap();
assert_eq!("/data/kiwi/db", config.db_dir);

let _ = std::fs::remove_file(config_path);
}
}
11 changes: 8 additions & 3 deletions src/net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ impl ServerFactory {
}
},
#[cfg(unix)]
"unix" => Some(Box::new(unix::UnixServer::new(addr))),
"unix" => unix::UnixServer::new(addr, None)
.ok()
.map(|s| Box::new(s) as Box<dyn ServerTrait>),
#[cfg(not(unix))]
Comment on lines 68 to 72
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The create_server_with_mode path uses the dual-runtime NetworkServer for TCP (storage is initialized separately in initialize_storage_server). The Unix fallback here is a legacy path — I've left it as None (uses default ./db) since the dual-runtime architecture doesn't use this code path. Added a comment to document this.

"unix" => None,
_ => None,
Expand All @@ -77,13 +79,16 @@ impl ServerFactory {
pub fn create_legacy_server(
protocol: &str,
addr: Option<String>,
db_dir: Option<&str>,
) -> Option<Box<dyn ServerTrait>> {
match protocol.to_lowercase().as_str() {
"tcp" => TcpServer::new(addr)
"tcp" => TcpServer::new(addr, db_dir)
.ok()
.map(|s| Box::new(s) as Box<dyn ServerTrait>),
#[cfg(unix)]
"unix" => Some(Box::new(unix::UnixServer::new(addr))),
"unix" => unix::UnixServer::new(addr, db_dir)
.ok()
.map(|s| Box::new(s) as Box<dyn ServerTrait>),
#[cfg(not(unix))]
"unix" => None,
_ => None,
Expand Down
5 changes: 2 additions & 3 deletions src/net/src/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,9 @@ pub struct TcpServer {
}

impl TcpServer {
pub fn new(addr: Option<String>) -> Result<Self, Box<dyn Error>> {
// TODO: Get storage options from config
pub fn new(addr: Option<String>, db_dir: Option<&str>) -> Result<Self, Box<dyn Error>> {
let storage_options = Arc::new(StorageOptions::default());
let db_path = PathBuf::from("./db");
let db_path = PathBuf::from(db_dir.unwrap_or("./db"));
let mut storage = Storage::new(1, 0);
let executor = Arc::new(CmdExecutorBuilder::new().build());

Expand Down
10 changes: 5 additions & 5 deletions src/net/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ pub struct UnixServer {
}

impl UnixServer {
pub fn new(path: Option<String>) -> Self {
pub fn new(path: Option<String>, db_dir: Option<&str>) -> Result<Self, Box<dyn Error>> {
let path = path.unwrap_or_else(|| "/tmp/kiwidb.sock".to_string());
let storage_options = Arc::new(StorageOptions::default());
let db_path = PathBuf::from("./db");
let db_path = PathBuf::from(db_dir.unwrap_or("./db"));
let mut storage = Storage::new(1, 0);
storage
.open(storage_options, db_path)
.expect("failed to open storage");
.map_err(|e| Box::new(e) as Box<dyn Error>)?;
let executor = Arc::new(CmdExecutorBuilder::new().build());
Comment on lines 38 to 44
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the same change — new() returns Result and all callers in ServerFactory use .ok().


Self {
Ok(Self {
path,
storage: Arc::new(storage),
cmd_table: Arc::new(create_command_table()),
executor,
}
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ async fn initialize_storage(config: &Config) -> Result<Arc<Storage>, DualRuntime
info!("Initializing storage...");

let storage_options = Arc::new(StorageOptions::default());
let db_path = PathBuf::from(&config.db_path);
let db_path = PathBuf::from(&config.db_dir);

let mut storage = Storage::new(1, 0);

Expand Down
Loading