fix(storage): S-06 — container advisory lock prevents parallel mount and concurrent writes
This commit is contained in:
@@ -170,6 +170,7 @@ fn test_carrier_protection_in_storage_and_sync() {
|
||||
checksum: false,
|
||||
exclude_patterns: Vec::new(),
|
||||
quiet: true,
|
||||
force: false,
|
||||
};
|
||||
let pull_res = sanctum::sync::run_sync(
|
||||
&db,
|
||||
|
||||
@@ -786,3 +786,120 @@ fn test_s05_zero_byte_file_truncate_removes_all_chunks() {
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_s06_advisory_lock_blocks_sync_and_force_overrides() {
|
||||
let temp_root =
|
||||
std::env::temp_dir().join(format!("sanctum_s06_test_{}", rand::random::<u64>()));
|
||||
let container_path = temp_root.join("test.sanctum");
|
||||
let source_dir = temp_root.join("source");
|
||||
|
||||
fs::create_dir_all(&source_dir).unwrap();
|
||||
fs::write(source_dir.join("test_lock.txt"), b"Locked content").unwrap();
|
||||
|
||||
let (db, dek) = create_test_container(&container_path);
|
||||
|
||||
// 1. Initial: Kein Lock aktiv
|
||||
assert!(db.check_advisory_lock().unwrap().is_none());
|
||||
|
||||
// 2. Lock setzen (simuliert laufenden Mount)
|
||||
db.acquire_advisory_lock(false)
|
||||
.expect("Acquire advisory lock");
|
||||
let active_lock = db.check_advisory_lock().unwrap();
|
||||
assert!(active_lock.is_some());
|
||||
let (pid, _host, _time) = active_lock.unwrap();
|
||||
assert_eq!(pid, std::process::id());
|
||||
|
||||
let mut opts = SyncOptions::default();
|
||||
opts.direction = SyncDirection::Push;
|
||||
opts.quiet = true;
|
||||
opts.force = false;
|
||||
|
||||
// 3. Sync ohne --force muss mit Lock-Fehler abbrechen
|
||||
let sync_res = run_sync(
|
||||
&db,
|
||||
0,
|
||||
&dek,
|
||||
FORMAT_VERSION,
|
||||
source_dir.to_str().unwrap(),
|
||||
"/",
|
||||
&opts,
|
||||
);
|
||||
assert!(
|
||||
sync_res.is_err(),
|
||||
"Sync ohne --force muss bei aktivem Advisory Lock abbrechen"
|
||||
);
|
||||
let err_msg = sync_res.unwrap_err().to_string();
|
||||
assert!(
|
||||
err_msg.contains("Container ist gesperrt"),
|
||||
"Fehlermeldung muss Lock-Hinweis enthalten: {}",
|
||||
err_msg
|
||||
);
|
||||
|
||||
// 4. Sync mit --force muss trotz Lock erfolgreich durchlaufen
|
||||
opts.force = true;
|
||||
let sync_force_res = run_sync(
|
||||
&db,
|
||||
0,
|
||||
&dek,
|
||||
FORMAT_VERSION,
|
||||
source_dir.to_str().unwrap(),
|
||||
"/",
|
||||
&opts,
|
||||
);
|
||||
assert!(
|
||||
sync_force_res.is_ok(),
|
||||
"Sync mit --force muss trotz aktivem Advisory Lock gelingen"
|
||||
);
|
||||
|
||||
// 5. Lock freigeben
|
||||
db.release_advisory_lock().expect("Release advisory lock");
|
||||
assert!(db.check_advisory_lock().unwrap().is_none());
|
||||
|
||||
// 6. Sync ohne --force gelingt jetzt wieder
|
||||
opts.force = false;
|
||||
let sync_unlocked = run_sync(
|
||||
&db,
|
||||
0,
|
||||
&dek,
|
||||
FORMAT_VERSION,
|
||||
source_dir.to_str().unwrap(),
|
||||
"/",
|
||||
&opts,
|
||||
);
|
||||
assert!(sync_unlocked.is_ok());
|
||||
|
||||
// 7. Stale Lock Test: Lock mit nicht mehr existierender PID wird automatisch bereinigt
|
||||
{
|
||||
let conn = rusqlite::Connection::open(&container_path).unwrap();
|
||||
let current_host = std::env::var("COMPUTERNAME")
|
||||
.or_else(|_| std::env::var("HOSTNAME"))
|
||||
.unwrap_or_else(|_| "localhost".to_string());
|
||||
conn.execute(
|
||||
"UPDATE meta SET lock_pid = 99999999, lock_host = ?1, lock_time = 1000 WHERE slot_id = 0",
|
||||
rusqlite::params![current_host],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// check_advisory_lock erkennt den toten Prozess und räumt den Lock auf
|
||||
assert!(
|
||||
db.check_advisory_lock().unwrap().is_none(),
|
||||
"Stale Lock eines toten Prozesses muss automatisch als None bewertet werden"
|
||||
);
|
||||
let sync_stale = run_sync(
|
||||
&db,
|
||||
0,
|
||||
&dek,
|
||||
FORMAT_VERSION,
|
||||
source_dir.to_str().unwrap(),
|
||||
"/",
|
||||
&opts,
|
||||
);
|
||||
assert!(
|
||||
sync_stale.is_ok(),
|
||||
"Sync muss bei verwaistem Lock eines toten Prozesses ohne --force gelingen"
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(&temp_root);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user