Implement Phase A: Stop Dying resilience fixes
Implements all 6 critical resilience fixes from phase-a-stop-dying.md: - Issue 2.9: Migrate std::sync::RwLock → parking_lot::RwLock (7 files) Prevents lock poisoning cascade on writer panic - Issue 2.2: Add install_panic_hook() to log panics via tracing Ensures panics are captured in logs/journald before process death - Issue 3.7: Add ExecStopPost to systemd service Cleans up stale FUSE mounts on service stop - Issue 2.7: Add check_stale_mount() detection on startup Auto-cleans leftover mounts from previous crashes - Issue 2.10: Integrate sd_notify for systemd lifecycle Sends READY=1 after mount, STOPPING on shutdown - Issue 2.1: Add signal handling with spawn_mount Catches SIGTERM/SIGINT for clean shutdown instead of instant death All 7 Phase A tests pass: - test_poisoned_tree_lock_returns_eio_not_panic - test_parking_lot_rwlock_survives_panic - test_panic_hook_logs_to_tracing - test_systemd_service_has_execstoppost - test_stale_mount_check_function_exists - test_sd_notify_ready_sent - test_sigterm_triggers_shutdown
This commit is contained in:
@@ -6,10 +6,11 @@ use fuser::{
|
||||
use musicfs_cache::{VirtualNode, VirtualTree, ROOT_INODE};
|
||||
use musicfs_cas::FileReader;
|
||||
use musicfs_core::Result;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::runtime::Handle;
|
||||
use tracing::{debug, info, instrument, trace, warn};
|
||||
@@ -65,15 +66,15 @@ impl MusicFs {
|
||||
}
|
||||
|
||||
fn get_or_create_query_inode(&self, query: &str) -> u64 {
|
||||
let query_inodes = self.query_inodes.read().unwrap();
|
||||
let query_inodes = self.query_inodes.read();
|
||||
if let Some(&inode) = query_inodes.get(query) {
|
||||
return inode;
|
||||
}
|
||||
drop(query_inodes);
|
||||
|
||||
let mut query_inodes = self.query_inodes.write().unwrap();
|
||||
let mut inode_queries = self.inode_queries.write().unwrap();
|
||||
let mut next_inode = self.next_query_inode.write().unwrap();
|
||||
let mut query_inodes = self.query_inodes.write();
|
||||
let mut inode_queries = self.inode_queries.write();
|
||||
let mut next_inode = self.next_query_inode.write();
|
||||
|
||||
if let Some(&inode) = query_inodes.get(query) {
|
||||
return inode;
|
||||
@@ -87,7 +88,7 @@ impl MusicFs {
|
||||
}
|
||||
|
||||
fn get_query_for_inode(&self, inode: u64) -> Option<String> {
|
||||
self.inode_queries.read().unwrap().get(&inode).cloned()
|
||||
self.inode_queries.read().get(&inode).cloned()
|
||||
}
|
||||
|
||||
pub fn mount(self, mountpoint: &Path) -> Result<()> {
|
||||
@@ -105,6 +106,22 @@ impl MusicFs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn spawn_mount(self, mountpoint: &Path) -> Result<fuser::BackgroundSession> {
|
||||
info!("Mounting MusicFS at {:?}", mountpoint);
|
||||
|
||||
let options = vec![
|
||||
fuser::MountOption::RO,
|
||||
fuser::MountOption::FSName("musicfs".to_string()),
|
||||
fuser::MountOption::AutoUnmount,
|
||||
fuser::MountOption::AllowOther,
|
||||
];
|
||||
|
||||
let session =
|
||||
fuser::spawn_mount2(self, mountpoint, &options).map_err(musicfs_core::Error::Io)?;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
fn node_to_attr(&self, node: &VirtualNode) -> FileAttr {
|
||||
match node {
|
||||
VirtualNode::Directory(dir) => FileAttr {
|
||||
@@ -189,7 +206,7 @@ impl Filesystem for MusicFs {
|
||||
}
|
||||
}
|
||||
|
||||
let tree = self.tree.read().unwrap();
|
||||
let tree = self.tree.read();
|
||||
|
||||
if let Some(inode) = tree.lookup(parent, name) {
|
||||
trace!(parent, name = %name_str, ino = inode, "file found in tree");
|
||||
@@ -230,7 +247,7 @@ impl Filesystem for MusicFs {
|
||||
}
|
||||
}
|
||||
|
||||
let tree = self.tree.read().unwrap();
|
||||
let tree = self.tree.read();
|
||||
|
||||
if let Some(node) = tree.get(ino) {
|
||||
trace!(ino, "inode found in tree");
|
||||
@@ -267,7 +284,7 @@ impl Filesystem for MusicFs {
|
||||
}
|
||||
}
|
||||
|
||||
let tree = self.tree.read().unwrap();
|
||||
let tree = self.tree.read();
|
||||
|
||||
if let Some(children) = tree.readdir(ino) {
|
||||
trace!(ino, offset, children_count = children.len(), "directory found");
|
||||
@@ -324,7 +341,7 @@ impl Filesystem for MusicFs {
|
||||
return;
|
||||
}
|
||||
|
||||
let tree = self.tree.read().unwrap();
|
||||
let tree = self.tree.read();
|
||||
|
||||
if tree.get(ino).is_some() {
|
||||
trace!(ino, "inode found");
|
||||
@@ -348,7 +365,7 @@ impl Filesystem for MusicFs {
|
||||
reply: ReplyData,
|
||||
) {
|
||||
let file_id = {
|
||||
let tree = self.tree.read().unwrap();
|
||||
let tree = self.tree.read();
|
||||
if let Some(VirtualNode::File(file)) = tree.get(ino) {
|
||||
trace!(ino, "file found in tree");
|
||||
file.file_id
|
||||
@@ -564,7 +581,7 @@ mod tests {
|
||||
|
||||
let _fs = MusicFs::new(tree.clone(), handle);
|
||||
|
||||
let tree_read = tree.read().unwrap();
|
||||
let tree_read = tree.read();
|
||||
assert!(tree_read.get(ROOT_INODE).is_some());
|
||||
assert!(tree_read.get_by_path(&VirtualPath::new("/Artist")).is_some());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user