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:
@@ -22,6 +22,7 @@ rmp-serde.workspace = true
|
||||
hex.workspace = true
|
||||
dirs.workspace = true
|
||||
thiserror.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
||||
@@ -2,8 +2,9 @@ use crate::{CasStore, ChunkManifest, ChunkRef};
|
||||
use musicfs_core::{Event, EventBus, FileId, FileMeta, OriginId};
|
||||
use musicfs_origins::Origin;
|
||||
use musicfs_sync::CdcChunker;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, info};
|
||||
|
||||
pub struct ContentFetcher {
|
||||
@@ -37,15 +38,15 @@ impl ContentFetcher {
|
||||
|
||||
pub fn register_origin(&self, origin: Arc<dyn Origin>) {
|
||||
let id = origin.id().clone();
|
||||
self.origins.write().unwrap().insert(id, origin);
|
||||
self.origins.write().insert(id, origin);
|
||||
}
|
||||
|
||||
pub fn register_file(&self, meta: FileMeta) {
|
||||
self.file_meta.write().unwrap().insert(meta.id, meta);
|
||||
self.file_meta.write().insert(meta.id, meta);
|
||||
}
|
||||
|
||||
pub fn register_files(&self, files: impl IntoIterator<Item = FileMeta>) {
|
||||
let mut map = self.file_meta.write().unwrap();
|
||||
let mut map = self.file_meta.write();
|
||||
for meta in files {
|
||||
map.insert(meta.id, meta);
|
||||
}
|
||||
@@ -53,7 +54,7 @@ impl ContentFetcher {
|
||||
|
||||
pub async fn fetch_file(&self, file_id: FileId) -> Result<ChunkManifest, FetchError> {
|
||||
let meta = {
|
||||
let files = self.file_meta.read().unwrap();
|
||||
let files = self.file_meta.read();
|
||||
files
|
||||
.get(&file_id)
|
||||
.cloned()
|
||||
@@ -61,7 +62,7 @@ impl ContentFetcher {
|
||||
};
|
||||
|
||||
let origin = {
|
||||
let origins = self.origins.read().unwrap();
|
||||
let origins = self.origins.read();
|
||||
origins
|
||||
.get(&meta.real_path.origin_id)
|
||||
.cloned()
|
||||
@@ -123,7 +124,7 @@ impl ContentFetcher {
|
||||
}
|
||||
|
||||
pub fn get_file_meta(&self, file_id: FileId) -> Option<FileMeta> {
|
||||
self.file_meta.read().unwrap().get(&file_id).cloned()
|
||||
self.file_meta.read().get(&file_id).cloned()
|
||||
}
|
||||
|
||||
pub fn emit_access_event(&self, meta: &FileMeta, offset: u64, size: u32) {
|
||||
|
||||
@@ -3,9 +3,10 @@ use crate::fetcher::{ContentFetcher, FetchError};
|
||||
use crate::store::CasStore;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use musicfs_core::FileId;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -60,13 +61,13 @@ impl FileReader {
|
||||
}
|
||||
|
||||
pub fn register_manifest(&self, manifest: ChunkManifest) {
|
||||
let mut manifests = self.manifests.write().unwrap();
|
||||
let mut manifests = self.manifests.write();
|
||||
manifests.insert(manifest.file_id, manifest);
|
||||
}
|
||||
|
||||
async fn get_or_fetch_manifest(&self, file_id: FileId) -> Result<ChunkManifest, ReaderError> {
|
||||
{
|
||||
let manifests = self.manifests.read().unwrap();
|
||||
let manifests = self.manifests.read();
|
||||
if let Some(m) = manifests.get(&file_id) {
|
||||
trace!(file_id = ?file_id, "manifest cache hit");
|
||||
return Ok(m.clone());
|
||||
@@ -81,7 +82,6 @@ impl FileReader {
|
||||
let manifest = fetcher.ensure_cached(file_id).await?;
|
||||
self.manifests
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(file_id, manifest.clone());
|
||||
Ok(manifest)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user