diff --git a/Cargo.lock b/Cargo.lock index 7d8670a..4f31cd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2305,6 +2305,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -5989,6 +5999,7 @@ dependencies = [ "crc32fast", "crossterm", "eyre", + "fs2", "futures", "indicatif", "jsonrpsee", diff --git a/node/Cargo.toml b/node/Cargo.toml index 794b9d1..88b309c 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -45,6 +45,7 @@ ratatui = "0.29" crossterm = "0.28" rand = "0.8" lru = "0.12" +fs2 = "0.4" # ============================================ # RUST-MAGIC-LINTER - Maximum Preset diff --git a/node/src/storage/sharded/mod.rs b/node/src/storage/sharded/mod.rs index 9aaa186..5f4db0a 100644 --- a/node/src/storage/sharded/mod.rs +++ b/node/src/storage/sharded/mod.rs @@ -432,6 +432,10 @@ impl SegmentWriter { pub struct Storage { data_dir: PathBuf, peer_cache_dir: PathBuf, + /// Exclusive lock on `{data_dir}/.lock` — prevents a second process from + /// opening the same data directory. Held for the lifetime of this struct; + /// released automatically on drop. + _lock_file: fs::File, meta: Mutex, shards: Mutex>>>, peer_cache: Mutex>, @@ -484,6 +488,13 @@ impl Storage { tracing::info!(data_dir = %config.data_dir.display(), "storage open: starting"); fs::create_dir_all(&config.data_dir).wrap_err("failed to create data dir")?; + + let lock_path = config.data_dir.join(".lock"); + let lock_file = + fs::File::create(&lock_path).wrap_err("failed to create lock file")?; + fs2::FileExt::try_lock_exclusive(&lock_file) + .wrap_err("data directory is already in use by another shinode process")?; + let peer_cache_dir = config .peer_cache_dir .clone() @@ -708,6 +719,7 @@ impl Storage { Ok(Self { data_dir: config.data_dir.clone(), peer_cache_dir, + _lock_file: lock_file, meta: Mutex::new(meta), shards: Mutex::new(shards), peer_cache: Mutex::new(peer_cache), @@ -724,6 +736,14 @@ impl Storage { return Ok(RepairReport { shards: vec![] }); } + // Acquire exclusive lock to prevent concurrent repair / sync + let lock_path = config.data_dir.join(".lock"); + let lock_file = + fs::File::create(&lock_path).wrap_err("failed to create lock file")?; + fs2::FileExt::try_lock_exclusive(&lock_file) + .wrap_err("data directory is already in use by another shinode process")?; + let _lock_file = lock_file; // hold until end of function + let shards_root = shards_dir(&config.data_dir); if !shards_root.exists() { return Ok(RepairReport { shards: vec![] });