mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
fix(update): run periodic update check in daemon so hints actually fire (#76)
* fix(update): run periodic update check in daemon so hints actually fire The CLI spawned a detached thread to refresh ~/.bsk/update-check.json, but the process exits as soon as the command finishes, killing the thread before the fetch could complete — so the cache was never written and the new-version hint never showed. Move the periodic check into the daemon: a tokio task ticks every 30 minutes (down from 24h), refreshes the cache when missing/stale, and logs failures at warn/info into the daemon log. The CLI path is now read-only: it prints the hint only from a fresh cache and never spawns threads or touches the network. * fix(update): decouple daemon refresh window so checks truly run every 30min The refresh decision reused the 30min tick interval as its freshness window, and with is_fresh()'s inclusive bound a cache written just after tick N still counted as fresh at tick N+1 — so in steady state the daemon only refetched every other tick (~60min). Judge staleness against DAEMON_REFRESH_WINDOW (25min = 5/6 of the tick) instead: every 30min tick now finds the cache stale and really refreshes, while a daemon restarted with a younger-than-25min cache still skips its first tick. CLI hint freshness semantics (UPDATE_CHECK_INTERVAL = 30min) are unchanged.
This commit is contained in:
@@ -220,6 +220,7 @@ pub fn run_foreground(cfg: DaemonConfig) -> Result<()> {
|
||||
let state = Arc::new(DaemonState::new(cfg.clone()));
|
||||
let session_idle_task = spawn_session_idle_reaper(Arc::clone(&state));
|
||||
let browser_liveness_task = spawn_browser_liveness_reaper(Arc::clone(&state));
|
||||
let update_check_task = spawn_update_check_task();
|
||||
let ws_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), cfg.ws_port);
|
||||
let ws_handle = ws::WsServer::new(Arc::clone(&state))
|
||||
.bind(ws_addr)
|
||||
@@ -383,6 +384,8 @@ pub fn run_foreground(cfg: DaemonConfig) -> Result<()> {
|
||||
let _ = session_idle_task.await;
|
||||
browser_liveness_task.abort();
|
||||
let _ = browser_liveness_task.await;
|
||||
update_check_task.abort();
|
||||
let _ = update_check_task.await;
|
||||
ws_handle.shutdown.notify_waiters();
|
||||
let _ = ws_handle.task.await;
|
||||
|
||||
@@ -491,6 +494,68 @@ pub(crate) fn spawn_session_idle_reaper(state: Arc<DaemonState>) -> tokio::task:
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn the periodic update check owned by the production daemon.
|
||||
///
|
||||
/// The daemon is the only writer of `~/.bsk/update-check.json`; CLI
|
||||
/// commands only read it to print the "new version available" hint
|
||||
/// (see [`crate::cli::update::print_update_hint_from_cache`]). The first
|
||||
/// tick of `tokio::time::interval` is immediate, so the check runs right
|
||||
/// after startup and then every
|
||||
/// [`crate::cli::update::UPDATE_CHECK_INTERVAL`]. Each tick re-reads the
|
||||
/// cache and skips the network fetch while it is still fresh within
|
||||
/// [`crate::cli::update::DAEMON_REFRESH_WINDOW`] (25min — shorter than
|
||||
/// the 30min tick, so steady state really refreshes on every tick
|
||||
/// instead of every other one). The task
|
||||
/// loops forever; shutdown aborts it like the other background tasks, so
|
||||
/// it never delays daemon exit (an in-flight fetch is bounded by the
|
||||
/// update client's own timeout and detached on abort).
|
||||
pub(crate) fn spawn_update_check_task() -> tokio::task::JoinHandle<()> {
|
||||
use crate::cli::update;
|
||||
|
||||
tokio::spawn(async move {
|
||||
let cache_path = match paths::update_check_path() {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
warn!(error = %err, "periodic update check disabled: cannot resolve cache path");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut ticker = tokio::time::interval(update::UPDATE_CHECK_INTERVAL);
|
||||
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
|
||||
let needs_refresh = match update::read_update_cache(&cache_path) {
|
||||
Ok(cache) => update::cache_needs_refresh(
|
||||
cache.as_ref(),
|
||||
update::now_epoch_secs(),
|
||||
update::DAEMON_REFRESH_WINDOW,
|
||||
),
|
||||
Err(err) => {
|
||||
warn!(error = %err, "update cache unreadable; will refresh it");
|
||||
true
|
||||
}
|
||||
};
|
||||
if !needs_refresh {
|
||||
debug!("update cache still fresh; skipping update check");
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = {
|
||||
let cache_path = cache_path.clone();
|
||||
tokio::task::spawn_blocking(move || update::refresh_update_cache(&cache_path)).await
|
||||
};
|
||||
match result {
|
||||
Ok(Ok(Some(hint))) => info!(%hint, "periodic update check found a new version"),
|
||||
Ok(Ok(None)) => info!("periodic update check refreshed cache (already up to date)"),
|
||||
Ok(Err(err)) => warn!(error = %err, "periodic update check failed"),
|
||||
Err(err) => warn!(error = %err, "periodic update check task panicked"),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn record_activity(activity: &Arc<Mutex<Instant>>) {
|
||||
if let Ok(mut a) = activity.lock() {
|
||||
*a = Instant::now();
|
||||
|
||||
Reference in New Issue
Block a user