Skip to main content

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