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}