diff options
Diffstat (limited to 'src/tasks/mod.rs')
| -rw-r--r-- | src/tasks/mod.rs | 41 |
1 files changed, 28 insertions, 13 deletions
diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index f3f8811..1519c12 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
| 2 | 2 | ||
| 3 | use chrono::{Duration, Timelike, Utc}; | 3 | use chrono::{DateTime, Duration, Timelike, Utc}; |
| 4 | use cron::Schedule; | 4 | use cron::Schedule; |
| 5 | use poise::serenity_prelude as serenity; | 5 | use poise::serenity_prelude as serenity; |
| 6 | use std::str::FromStr; | 6 | use std::str::FromStr; |
| @@ -33,9 +33,6 @@ fn parse_cron(raw: &str) -> Result<Schedule, cron::error::Error> { | |||
| 33 | Schedule::from_str(&normalized) | 33 | Schedule::from_str(&normalized) |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | /// Check window: look for cron fires in the last WINDOW_SECONDS. | ||
| 37 | /// 61s gives 1s overlap with 60s tick to avoid missing exact-boundary fires. | ||
| 38 | const WINDOW_SECONDS: i64 = 61; | ||
| 39 | /// Tick interval for the scheduler loop. | 36 | /// Tick interval for the scheduler loop. |
| 40 | const TICK_SECONDS: u64 = 60; | 37 | const TICK_SECONDS: u64 = 60; |
| 41 | 38 | ||
| @@ -49,7 +46,6 @@ pub async fn check_and_execute_jobs(ctx: &CronContext) { | |||
| 49 | }; | 46 | }; |
| 50 | 47 | ||
| 51 | let now = Utc::now(); | 48 | let now = Utc::now(); |
| 52 | let window_start = now - Duration::seconds(WINDOW_SECONDS); | ||
| 53 | 49 | ||
| 54 | for job in jobs { | 50 | for job in jobs { |
| 55 | let schedule = match parse_cron(&job.cron_expression) { | 51 | let schedule = match parse_cron(&job.cron_expression) { |
| @@ -60,26 +56,45 @@ pub async fn check_and_execute_jobs(ctx: &CronContext) { | |||
| 60 | } | 56 | } |
| 61 | }; | 57 | }; |
| 62 | 58 | ||
| 63 | // Check if any fire time fell within the last WINDOW_SECONDS. | 59 | // Find the next scheduled fire strictly after last_run. |
| 64 | let mut has_fired = false; | 60 | // - If last_run is None (freshly created or never run), start from the |
| 65 | for next_fire in schedule.after(&window_start) { | 61 | // last fire at/after the previous tick boundary so we still catch the |
| 62 | // fire scheduled for "now" on the first scan without doubling up. | ||
| 63 | // - If last_fire <= now, the job is due; we record last_run = last_fire | ||
| 64 | // (not `now`) so a long execution delay can't skip a legitimately | ||
| 65 | // pending later fire. | ||
| 66 | let reference: DateTime<Utc> = job.last_run.unwrap_or_else(|| { | ||
| 67 | // Step back a tick so the first scan after startup catches the fire | ||
| 68 | // scheduled for the minute we wake up on. | ||
| 69 | now - chrono::Duration::seconds(TICK_SECONDS as i64) | ||
| 70 | }); | ||
| 71 | |||
| 72 | let mut due_fire: Option<DateTime<Utc>> = None; | ||
| 73 | for next_fire in schedule.after(&reference) { | ||
| 66 | if next_fire > now { | 74 | if next_fire > now { |
| 67 | break; | 75 | break; |
| 68 | } | 76 | } |
| 69 | has_fired = true; | 77 | due_fire = Some(next_fire); |
| 70 | break; // one fire per window is enough | 78 | break; |
| 71 | } | 79 | } |
| 72 | 80 | ||
| 73 | if !has_fired { | 81 | let Some(fire_time) = due_fire else { |
| 74 | continue; | 82 | continue; |
| 75 | } | 83 | }; |
| 76 | 84 | ||
| 77 | info!("Executing cron job {}", job.id); | 85 | info!("Executing cron job {} (fire {})", job.id, fire_time); |
| 78 | match job.r#type.as_str() { | 86 | match job.r#type.as_str() { |
| 79 | "audio" => execute_audio_job(ctx, &job).await, | 87 | "audio" => execute_audio_job(ctx, &job).await, |
| 80 | "wiadomość" => execute_message_job(ctx, &job).await, | 88 | "wiadomość" => execute_message_job(ctx, &job).await, |
| 81 | _ => {} | 89 | _ => {} |
| 82 | } | 90 | } |
| 91 | |||
| 92 | // Record last_run so we never re-fire the same schedule instant on the | ||
| 93 | // next tick. Kept outside the match so a failed/empty send still marks | ||
| 94 | // the fire as consumed and doesn't retry on every tick. | ||
| 95 | if let Err(e) = database::update_cron_last_run(&ctx.pool, job.id, fire_time).await { | ||
| 96 | error!("Failed to update last_run for cron job {}: {e}", job.id); | ||
| 97 | } | ||
| 83 | } | 98 | } |
| 84 | } | 99 | } |
| 85 | 100 | ||
