aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client/daemon.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/turtle/src/command/client/daemon.rs')
-rw-r--r--crates/turtle/src/command/client/daemon.rs183
1 files changed, 17 insertions, 166 deletions
diff --git a/crates/turtle/src/command/client/daemon.rs b/crates/turtle/src/command/client/daemon.rs
index 4960dacd..30b478ec 100644
--- a/crates/turtle/src/command/client/daemon.rs
+++ b/crates/turtle/src/command/client/daemon.rs
@@ -146,21 +146,6 @@ fn is_legacy_daemon_error(err: &eyre::Report) -> bool {
matches!(classify_error(err), DaemonClientErrorKind::Unimplemented)
}
-fn should_retry_after_error(err: &eyre::Report) -> bool {
- matches!(
- classify_error(err),
- DaemonClientErrorKind::Connect
- | DaemonClientErrorKind::Unavailable
- | DaemonClientErrorKind::Unimplemented
- )
-}
-
-fn daemon_startup_lock_path(pidfile_path: &Path) -> PathBuf {
- let mut os = pidfile_path.as_os_str().to_os_string();
- os.push(".startup.lock");
- PathBuf::from(os)
-}
-
fn open_lock_file(path: &Path) -> Result<File> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
@@ -317,78 +302,6 @@ async fn wait_until_ready(settings: &Settings, timeout: Duration) -> Result<Hist
}
}
-fn ensure_autostart_supported(settings: &Settings) -> Result<()> {
- #[cfg(unix)]
- if settings.daemon.systemd_socket {
- bail!(
- "daemon autostart is incompatible with `daemon.systemd_socket = true`; use systemd to manage the daemon"
- );
- }
-
- Ok(())
-}
-
-/// Ensure the daemon is running, starting it if necessary.
-///
-/// If the daemon is already running and up-to-date, this is a no-op.
-/// If it is not running or needs a restart, this will spawn a new daemon
-/// process and wait for it to become ready.
-///
-/// Returns an error if the daemon could not be started.
-pub(crate) async fn ensure_daemon_running(settings: &Settings) -> Result<()> {
- ensure_autostart_supported(settings)?;
-
- let timeout = startup_timeout(settings);
- let pidfile_path = PathBuf::from(&settings.daemon.pidfile_path);
- let startup_lock_path = daemon_startup_lock_path(&pidfile_path);
- let startup_lock = wait_for_lock(&startup_lock_path, timeout).await?;
-
- match probe(settings).await {
- Probe::Ready(_) => {
- drop(startup_lock);
- return Ok(());
- }
- Probe::NeedsRestart(_) => {
- request_shutdown(settings).await;
- }
- Probe::Unreachable(err) => {
- if is_legacy_daemon_error(&err) {
- return Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE));
- }
- }
- }
-
- // This prevents rapid-fire hook invocations from racing daemon restart.
- wait_for_pidfile_available(&pidfile_path, timeout).await?;
-
- #[cfg(unix)]
- remove_stale_socket_if_present(settings)?;
-
- spawn_daemon_process()?;
- drop(wait_until_ready(settings, timeout).await?);
-
- drop(startup_lock);
- Ok(())
-}
-
-async fn restart_daemon(settings: &Settings) -> Result<HistoryClient> {
- ensure_daemon_running(settings).await?;
- connect_client(settings).await
-}
-
-fn ensure_reply_compatible(settings: &Settings, version: &str, protocol: u32) -> Result<()> {
- if daemon_matches_expected(version, protocol) {
- return Ok(());
- }
-
- let message = daemon_mismatch_message(version, protocol);
- if settings.daemon.autostart {
- bail!("{message}");
- }
-
- bail!("{message}. Enable `daemon.autostart = true` or restart the daemon manually");
-}
-
pub(crate) async fn start_history(settings: &Settings, history: History) -> Result<String> {
match async {
connect_client(settings)
@@ -403,24 +316,13 @@ pub(crate) async fn start_history(settings: &Settings, history: History) -> Resu
return Ok(resp.id);
}
- if !settings.daemon.autostart {
- return Err(eyre!(
- "{}. Enable `daemon.autostart = true` or restart the daemon manually",
- daemon_mismatch_message(&resp.version, resp.protocol)
- ));
- }
+ Err(eyre!(
+ "{}. Restart the daemon manually",
+ daemon_mismatch_message(&resp.version, resp.protocol)
+ ))
}
- Err(err) if !settings.daemon.autostart => return Err(err),
- Err(err) if !should_retry_after_error(&err) => return Err(err),
- Err(_) => {}
+ Err(err) => Err(err),
}
-
- let resp = restart_daemon(settings)
- .await?
- .start_history(history)
- .await?;
- ensure_reply_compatible(settings, &resp.version, resp.protocol)?;
- Ok(resp.id)
}
pub(crate) async fn end_history(
@@ -442,36 +344,16 @@ pub(crate) async fn end_history(
return Ok(());
}
- if !settings.daemon.autostart {
- return Err(eyre!(
- "{}. Enable `daemon.autostart = true` or restart the daemon manually",
- daemon_mismatch_message(&resp.version, resp.protocol)
- ));
- }
-
- // End succeeded on the running daemon, so avoid replaying it.
- // We only restart to make subsequent hook calls target the expected version.
- drop(restart_daemon(settings).await);
- return Ok(());
+ Err(eyre!(
+ "{}. Restart the daemon manually",
+ daemon_mismatch_message(&resp.version, resp.protocol)
+ ))
}
- Err(err) if !settings.daemon.autostart => return Err(err),
- Err(err) if !should_retry_after_error(&err) => return Err(err),
- Err(_) => {}
+ Err(err) => Err(err),
}
-
- let resp = restart_daemon(settings)
- .await?
- .end_history(id, duration, exit)
- .await?;
- ensure_reply_compatible(settings, &resp.version, resp.protocol)?;
- Ok(())
}
-/// Emit a daemon event, auto-starting the daemon if it is not running.
-///
-/// If the daemon is not reachable and `daemon.autostart` is enabled, this
-/// will start the daemon and retry the event. If the daemon cannot be
-/// started or the retry fails, a warning is printed to stderr.
+/// Emit a daemon event.
pub(crate) async fn emit_event(settings: &Settings, event: DaemonEvent) {
// Try to connect and send
match ControlClient::from_settings(settings).await {
@@ -479,48 +361,24 @@ pub(crate) async fn emit_event(settings: &Settings, event: DaemonEvent) {
if let Err(e) = client.send_event(event).await {
tracing::debug!(?e, "failed to send event to daemon");
}
- return;
- }
- Err(e) if !settings.daemon.autostart || !should_retry_after_error(&e) => {
- tracing::debug!(?e, "daemon not available, skipping event emission");
- return;
- }
- Err(_) => {}
- }
-
- // Auto-start the daemon and retry
- if let Err(e) = ensure_daemon_running(settings).await {
- eprintln!("Could not start daemon: {e}");
- return;
- }
-
- match ControlClient::from_settings(settings).await {
- Ok(mut client) => {
- if let Err(e) = client.send_event(event).await {
- eprintln!("Daemon started but failed to send event: {e}");
- }
}
Err(e) => {
- eprintln!("Daemon started but failed to connect: {e}");
+ tracing::debug!(?e, "daemon not available, skipping event emission");
}
}
}
pub(crate) async fn tail_client(settings: &Settings) -> Result<HistoryClient> {
match probe(settings).await {
- Probe::Ready(client) => return Ok(client),
- Probe::NeedsRestart(reason) if !settings.daemon.autostart => {
- bail!("{reason}. Enable `daemon.autostart = true` or restart the daemon manually");
+ Probe::Ready(client) => Ok(client),
+ Probe::NeedsRestart(reason) => {
+ bail!("{reason}. Restart the daemon manually");
}
Probe::Unreachable(err) if is_legacy_daemon_error(&err) => {
- return Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE));
+ Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE))
}
- Probe::Unreachable(err) if !settings.daemon.autostart => return Err(err),
- Probe::Unreachable(err) if !should_retry_after_error(&err) => return Err(err),
- Probe::NeedsRestart(_) | Probe::Unreachable(_) => {}
+ Probe::Unreachable(err) => Err(err),
}
-
- restart_daemon(settings).await
}
async fn status_cmd(settings: &Settings) -> Result<()> {
@@ -720,11 +578,4 @@ mod tests {
let msg = daemon_mismatch_message(DAEMON_VERSION, 999);
assert!(msg.contains("protocol mismatch"), "got: {msg}");
}
-
- #[test]
- fn test_startup_lock_path() {
- let pidfile = Path::new("/tmp/atuin-daemon.pid");
- let lock = daemon_startup_lock_path(pidfile);
- assert_eq!(lock, PathBuf::from("/tmp/atuin-daemon.pid.startup.lock"));
- }
}