aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-daemon/src/components/sync.rs
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-03-11 23:33:30 -0700
committerGitHub <noreply@github.com>2026-03-11 23:33:30 -0700
commitbde6814411696f23e09f9d4a67fdc1e1fef55263 (patch)
treef1f99f27f9467792a969a7622f5c3feebfeb0b97 /crates/atuin-daemon/src/components/sync.rs
parentfix: ctrl-c not exiting ai (#3256) (diff)
downloadatuin-bde6814411696f23e09f9d4a67fdc1e1fef55263.zip
feat: Initialize Atuin AI by default with `atuin init` (#3255)
* Run Atuin AI's `init` during main `init` for bash, zsh, and fish * Note that logging into Hub will enable sync * Add instructions for users with existing sync accounts * Ensure daemon respects `auto_sync` setting * Update docs on disabling Atuin AI
Diffstat (limited to 'crates/atuin-daemon/src/components/sync.rs')
-rw-r--r--crates/atuin-daemon/src/components/sync.rs61
1 files changed, 50 insertions, 11 deletions
diff --git a/crates/atuin-daemon/src/components/sync.rs b/crates/atuin-daemon/src/components/sync.rs
index 6217706a..314b375e 100644
--- a/crates/atuin-daemon/src/components/sync.rs
+++ b/crates/atuin-daemon/src/components/sync.rs
@@ -2,6 +2,8 @@
//!
//! Handles periodic synchronization with the Atuin cloud server.
+use std::time::Duration;
+
use eyre::Result;
use rand::Rng;
use tokio::sync::mpsc;
@@ -23,6 +25,16 @@ enum SyncCommand {
Stop,
}
+/// Sync state - tracks whether we're in normal operation or retrying after failure.
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum SyncState {
+ /// Normal operation. Periodic syncs only run if auto_sync is enabled.
+ Idle,
+ /// Retrying after a sync failure. Retries continue regardless of auto_sync
+ /// until the sync succeeds.
+ Retrying,
+}
+
/// Sync component - handles periodic cloud synchronization.
///
/// This component:
@@ -123,29 +135,43 @@ async fn sync_loop(handle: DaemonHandle, mut cmd_rx: mpsc::Receiver<SyncCommand>
// we may end up running a lot of syncs in a hot loop.
ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
+ let mut sync_state = SyncState::Idle;
+
loop {
tokio::select! {
_ = ticker.tick() => {
- do_sync_tick(
+ let settings = handle.settings().await;
+
+ // Skip periodic ticks if auto_sync is disabled AND we're not retrying
+ // a previous failure. Retries must continue regardless of auto_sync.
+ if !settings.auto_sync && sync_state == SyncState::Idle {
+ tracing::debug!("auto_sync disabled, skipping periodic sync tick");
+ continue;
+ }
+
+ sync_state = do_sync_tick(
&handle,
&history_store,
&alias_store,
&var_store,
&mut ticker,
max_interval,
+ &settings,
).await;
}
cmd = cmd_rx.recv() => {
match cmd {
Some(SyncCommand::ForceSync) => {
tracing::info!("executing force sync");
- do_sync_tick(
+ let settings = handle.settings().await;
+ sync_state = do_sync_tick(
&handle,
&history_store,
&alias_store,
&var_store,
&mut ticker,
max_interval,
+ &settings,
).await;
}
Some(SyncCommand::Stop) | None => {
@@ -159,6 +185,8 @@ async fn sync_loop(handle: DaemonHandle, mut cmd_rx: mpsc::Receiver<SyncCommand>
}
/// Execute a single sync tick.
+///
+/// Returns the new sync state: `Idle` on success, `Retrying` on failure.
async fn do_sync_tick(
handle: &DaemonHandle,
history_store: &HistoryStore,
@@ -166,10 +194,8 @@ async fn do_sync_tick(
var_store: &VarStore,
ticker: &mut time::Interval,
max_interval: f64,
-) {
- // Clone settings since we need them across await points
- let settings = handle.settings().await.clone();
-
+ settings: &Settings,
+) -> SyncState {
tracing::info!("sync tick");
// Check if logged in
@@ -177,17 +203,17 @@ async fn do_sync_tick(
Ok(v) => v,
Err(e) => {
tracing::warn!("failed to check login status, skipping sync tick: {e}");
- return;
+ return SyncState::Idle;
}
};
if !logged_in {
tracing::debug!("not logged in, skipping sync tick");
- return;
+ return SyncState::Idle;
}
// Perform the sync
- let res = sync::sync(&settings, handle.store()).await;
+ let res = sync::sync(settings, handle.store()).await;
match res {
Err(e) => {
@@ -206,10 +232,16 @@ async fn do_sync_tick(
new_interval = max_interval;
}
- *ticker = time::interval(time::Duration::from_secs(new_interval as u64));
+ *ticker = time::interval_at(
+ tokio::time::Instant::now() + Duration::from_secs(new_interval as u64),
+ time::Duration::from_secs(new_interval as u64),
+ );
ticker.reset_after(time::Duration::from_secs(new_interval as u64));
+ ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
tracing::error!("backing off, next sync tick in {new_interval}");
+
+ SyncState::Retrying
}
Ok((uploaded_count, downloaded_records)) => {
tracing::info!(
@@ -245,13 +277,20 @@ async fn do_sync_tick(
// Reset backoff on success
if ticker.period().as_secs() != settings.daemon.sync_frequency {
- *ticker = time::interval(time::Duration::from_secs(settings.daemon.sync_frequency));
+ *ticker = time::interval_at(
+ tokio::time::Instant::now()
+ + Duration::from_secs(settings.daemon.sync_frequency),
+ time::Duration::from_secs(settings.daemon.sync_frequency),
+ );
+ ticker.set_missed_tick_behavior(MissedTickBehavior::Skip);
}
// Store sync time
if let Err(e) = Settings::save_sync_time().await {
tracing::error!("failed to save sync time: {e}");
}
+
+ SyncState::Idle
}
}
}