rustio_admin/background.rs
1//! Background tasks. A tiny runner that spawns one tokio task per
2//! recurring job. Kept simple on purpose — we don't ship a cron DSL,
3//! just "run this every N seconds".
4
5use std::time::Duration;
6
7use tokio::time::{interval, MissedTickBehavior};
8
9use crate::auth::purge_expired_sessions;
10use crate::auth::recovery::purge_expired_reset_tokens;
11use crate::orm::Db;
12
13/// Spin up the standard housekeeping tasks. Call this once during
14/// app startup. Returns immediately — the tasks run forever.
15pub fn spawn_housekeeping(db: Db) {
16 spawn_session_sweeper(db.clone());
17}
18
19/// Periodic maintenance loop — every 10 minutes, sweep:
20///
21/// 1. Expired sessions (`auth::purge_expired_sessions`) — removes
22/// rows from `rustio_sessions` where `expires_at < NOW()`.
23/// 2. Reset-token rows past their forensic-retention window
24/// (`auth::recovery::purge_expired_reset_tokens` — R1 commit
25/// #12) — removes rows from `rustio_password_reset_tokens`
26/// where `expires_at < NOW() - INTERVAL '7 days'`.
27///
28/// **Failure isolation:** the two sweeps run sequentially within
29/// each tick but each handles its own `Result` — a failure in one
30/// does NOT prevent the other from running, and neither prevents
31/// the next tick. The next tick happens unconditionally on the
32/// timer.
33///
34/// The 10-minute interval is kept from R0; reset-token sweeping
35/// piggy-backs on the existing tick rather than spawning a second
36/// timer loop.
37///
38/// (The function name is preserved for backwards compatibility —
39/// downstream projects already call `spawn_session_sweeper`
40/// directly. The expanded scope is documented above.)
41pub fn spawn_session_sweeper(db: Db) {
42 log::info!("background housekeeping sweeper spawned (10 min interval)");
43 tokio::spawn(async move {
44 let mut tick = interval(Duration::from_secs(600));
45 tick.set_missed_tick_behavior(MissedTickBehavior::Delay);
46 // Skip the first tick (it fires immediately) — no need to
47 // sweep the second the server boots.
48 tick.tick().await;
49 loop {
50 tick.tick().await;
51
52 // Session sweep — independent of the recovery-token
53 // sweep below. A failure here does NOT prevent the
54 // recovery sweep from running on this same tick.
55 match purge_expired_sessions(&db).await {
56 Ok(0) => {}
57 Ok(n) => log::info!("swept {n} expired sessions"),
58 Err(e) => log::warn!("session sweep failed: {e}"),
59 }
60
61 // Recovery-token sweep — independent of the session
62 // sweep above. A failure here does NOT prevent the
63 // session sweep from running, AND does not prevent
64 // the next tick. The subsystem-tagged target lets
65 // operators filter by stream.
66 match purge_expired_reset_tokens(&db).await {
67 Ok(0) => {}
68 Ok(n) => log::info!(
69 target: "rustio_admin::recovery_sweeper",
70 "swept {n} expired reset tokens"
71 ),
72 Err(e) => log::warn!(
73 target: "rustio_admin::recovery_sweeper",
74 "recovery-token sweep failed: {e}"
75 ),
76 }
77 }
78 });
79}