use std::{
os::unix::net::UnixStream,
path::Path,
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
time::{Duration, Instant},
};
use anyhow::{Context, Result};
use objects::{error::HeddleError, sync::LockExt};
use repo::daemon::{
EndpointState, IdleDecision, MOUNT_PROTOCOL_VERSION, MountClientAuth, MountDaemonRequest,
UnixDaemonHandler, bind_unix_socket, handle_authenticated_unix_connection,
mount_daemon_endpoint_path, mount_daemon_socket_path, mount_idle_policy, persist_endpoint,
remove_endpoint, run_unix_server_loop,
};
use tracing::info;
use super::{dispatch::dispatch, registry::MountRegistry};
pub fn run_mount_daemon(repo_root: &Path) -> Result<()> {
let endpoint_path = mount_daemon_endpoint_path(repo_root);
if let Some(parent) = endpoint_path.parent() {
std::fs::create_dir_all(parent)?;
}
let socket_path = mount_daemon_socket_path(repo_root);
let listener = bind_unix_socket(&socket_path).context("bind mount daemon socket")?;
persist_endpoint(
&endpoint_path,
&EndpointState {
version: MOUNT_PROTOCOL_VERSION,
host: "unix".to_string(),
port: 0,
pid: Some(std::process::id()),
socket_path: Some(socket_path.clone()),
},
)
.context("persist daemon endpoint")?;
info!(
socket = %socket_path.display(),
pid = std::process::id(),
"heddle daemon serving"
);
let registry = Arc::new(Mutex::new(MountRegistry::new(repo_root.to_path_buf())));
let started = Instant::now();
let shutdown_requested = Arc::new(AtomicBool::new(false));
let mut handler = MountDaemonHandler {
registry: Arc::clone(®istry),
started,
shutdown_requested: Arc::clone(&shutdown_requested),
};
let result = run_unix_server_loop(&listener, &mut handler);
{
let mut guard = registry.lock_or_poisoned();
guard.shutdown_all();
}
remove_endpoint(&endpoint_path);
let _ = std::fs::remove_file(&socket_path);
info!("heddle daemon exiting");
result.map_err(Into::into)
}
struct MountDaemonHandler {
registry: Arc<Mutex<MountRegistry>>,
started: Instant,
shutdown_requested: Arc<AtomicBool>,
}
impl UnixDaemonHandler for MountDaemonHandler {
fn handle(&mut self, stream: UnixStream) -> Result<(), HeddleError> {
let registry = Arc::clone(&self.registry);
let started = self.started;
let shutdown_requested = Arc::clone(&self.shutdown_requested);
handle_authenticated_unix_connection(stream, move |request: MountDaemonRequest| {
dispatch(
®istry,
started,
&shutdown_requested,
MountClientAuth::SameUid,
request,
)
})
}
fn on_tick(&mut self, idle_for: Duration) -> IdleDecision {
let shutdown = self.shutdown_requested.load(Ordering::Acquire);
let live_count = self.registry.lock_or_poisoned().len();
mount_idle_policy(shutdown, live_count, idle_for)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use repo::daemon::HELPER_IDLE_TIMEOUT_SECS;
use tempfile::TempDir;
use super::*;
#[test]
fn idle_exit_blocked_while_mount_is_live() {
let tmp = TempDir::new().unwrap();
let registry = Arc::new(Mutex::new(MountRegistry::new(tmp.path().to_path_buf())));
registry
.lock()
.unwrap()
.__test_inject_phantom_mount("phantom", tmp.path().to_path_buf());
let mut handler = MountDaemonHandler {
registry: Arc::clone(®istry),
started: Instant::now(),
shutdown_requested: Arc::new(AtomicBool::new(false)),
};
let decision = handler.on_tick(Duration::from_secs(HELPER_IDLE_TIMEOUT_SECS * 10));
assert_eq!(decision, IdleDecision::Continue);
}
#[test]
fn idle_exit_when_registry_empty() {
let tmp = TempDir::new().unwrap();
let registry = Arc::new(Mutex::new(MountRegistry::new(tmp.path().to_path_buf())));
let mut handler = MountDaemonHandler {
registry: Arc::clone(®istry),
started: Instant::now(),
shutdown_requested: Arc::new(AtomicBool::new(false)),
};
let decision = handler.on_tick(Duration::from_secs(HELPER_IDLE_TIMEOUT_SECS + 1));
assert_eq!(decision, IdleDecision::Exit);
}
#[test]
fn shutdown_request_short_circuits_idle_check() {
let tmp = TempDir::new().unwrap();
let registry = Arc::new(Mutex::new(MountRegistry::new(tmp.path().to_path_buf())));
registry
.lock()
.unwrap()
.__test_inject_phantom_mount("phantom", tmp.path().to_path_buf());
let shutdown = Arc::new(AtomicBool::new(true));
let mut handler = MountDaemonHandler {
registry: Arc::clone(®istry),
started: Instant::now(),
shutdown_requested: Arc::clone(&shutdown),
};
let decision = handler.on_tick(Duration::from_millis(0));
assert_eq!(decision, IdleDecision::Exit);
}
}