diff --git a/src/daemon.rs b/src/daemon.rs index bf3c663..4de73a4 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -15,6 +15,7 @@ use crate::{ }; const SOCKET_POLL_INTERVAL: Duration = Duration::from_millis(250); +const CONNECTION_MESSAGE_TIMEOUT: Duration = Duration::from_secs(8); /// Run the daemon, optionally limiting synchronization to one folder. /// @@ -44,6 +45,8 @@ pub async fn serve(folder_path: Option<&Path>) -> anyhow::Result<()> { } => result?, accepted = listener.accept() => { let (mut stream, _) = accepted?; + // Commands share the daemon's single SQLite connection and sync state. + // Keep their ordering deterministic until folder-level operation locks exist. handle_connection(&service, &sync_runner, &mut stream).await; } } @@ -57,7 +60,7 @@ async fn handle_connection( sync_runner: &SyncRunner, stream: &mut UnixStream, ) { - let response = match ipc::read_message::(stream).await { + let response = match read_request(stream).await { Ok(request) if request.protocol_version == PROTOCOL_VERSION => { handle_command(service, sync_runner, request.command).await } @@ -67,11 +70,26 @@ async fn handle_connection( )), Err(error) => Response::Error(error.to_string()), }; - if let Err(error) = ipc::write_message(stream, &response).await { + if let Err(error) = write_response(stream, &response).await { tracing::debug!(%error, "Could not send Appa daemon response"); } } +async fn read_request(stream: &mut UnixStream) -> anyhow::Result { + tokio::time::timeout(CONNECTION_MESSAGE_TIMEOUT, ipc::read_message(stream)) + .await + .map_err(|_| anyhow::anyhow!("timed out waiting for an Appa daemon request"))? +} + +async fn write_response(stream: &mut UnixStream, response: &Response) -> anyhow::Result<()> { + tokio::time::timeout( + CONNECTION_MESSAGE_TIMEOUT, + ipc::write_message(stream, response), + ) + .await + .map_err(|_| anyhow::anyhow!("timed out sending an Appa daemon response"))? +} + async fn handle_command( service: &AppaService, sync_runner: &SyncRunner,