Skip to main content

brows3r_lib/locks/
lifecycle.rs

1//! Background lifecycle tasks for the lock registry.
2//!
3//! - `start_heartbeat_loop`: spawns a tokio task that scans for stale locks
4//!   every `interval` and emits `lock:released { reason: ttl }` for each.
5//!
6//! # OCP contract
7//!
8//! The heartbeat loop is generic over `EventEmitter`, so any emitter (real
9//! Tauri `AppHandle` or `MockChannel`) can be substituted without changing
10//! this module.
11
12use std::{sync::Arc, time::Duration};
13
14use crate::events::EventEmitter;
15
16use super::{emit_released, LockRegistry, LockRegistryHandle, ReleaseReason};
17
18// ---------------------------------------------------------------------------
19// start_heartbeat_loop
20// ---------------------------------------------------------------------------
21
22/// Spawn a tokio task that periodically scans for stale locks and releases them.
23///
24/// The task runs every `interval` until the process exits (there is no
25/// cancellation token in v1; the task exits when the runtime shuts down).
26///
27/// For each stale lock `release_stale` is called and a `lock:released`
28/// event with `reason: Ttl` is emitted via `channel`.
29pub fn start_heartbeat_loop<E>(registry: Arc<LockRegistry>, interval: Duration, channel: Arc<E>)
30where
31    E: EventEmitter + Send + Sync + 'static,
32{
33    // Use Tauri's async runtime so this works whether or not the caller is
34    // already inside a Tokio reactor (Tauri's setup callback is sync and
35    // outside any reactor, but it owns its own runtime).
36    tauri::async_runtime::spawn(async move {
37        let mut ticker = tokio::time::interval(interval);
38        loop {
39            ticker.tick().await;
40            let now = current_unix_secs();
41            let stale = registry.release_stale(now);
42            for lock in &stale {
43                // Best-effort: ignore emit errors in the background task.
44                let _ = emit_released(channel.as_ref(), lock, ReleaseReason::Ttl);
45            }
46        }
47    });
48}
49
50/// Convenience wrapper that accepts a `LockRegistryHandle` instead of a bare
51/// `Arc<LockRegistry>`.
52pub fn start_heartbeat_loop_handle<E>(
53    handle: &LockRegistryHandle,
54    interval: Duration,
55    channel: Arc<E>,
56) where
57    E: EventEmitter + Send + Sync + 'static,
58{
59    start_heartbeat_loop(handle.0.clone(), interval, channel);
60}
61
62// ---------------------------------------------------------------------------
63// current_unix_secs
64// ---------------------------------------------------------------------------
65
66/// Return current Unix timestamp in seconds.
67///
68/// Separated into a function so tests can substitute a mock clock by calling
69/// registry methods directly with a controlled `now` value.
70pub fn current_unix_secs() -> i64 {
71    std::time::SystemTime::now()
72        .duration_since(std::time::UNIX_EPOCH)
73        .unwrap_or_default()
74        .as_secs() as i64
75}