From 8d257adf3d2dbe1b67e559fc0abe06307f665077 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Tue, 23 Jun 2026 10:52:54 +0200 Subject: [PATCH] fix: scheduler next_run accounts for elapsed time since last cycle On restart the scheduler was counting interval_hours from now, ignoring when the last cycle actually ran. It now reads last_run_at (in-memory or DB) and deducts elapsed time so a restart doesn't silently push the next fire by a full interval. Co-Authored-By: Claude Sonnet 4.6 --- backend/services/auto_cycle.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/backend/services/auto_cycle.py b/backend/services/auto_cycle.py index b071fd2..ae8beea 100644 --- a/backend/services/auto_cycle.py +++ b/backend/services/auto_cycle.py @@ -1870,11 +1870,30 @@ def _scheduler_loop(stop_event: threading.Event): _current_status["interval_hours"] = interval_hours logger.info(f"[Scheduler] Weekend — prochain cycle à {next_slot.strftime('%H:%M')} UTC ({wait_secs/3600:.1f}h)") else: - wait_secs = interval_hours * 3600 - next_run = now_utc + timedelta(hours=interval_hours) + # Account for time already elapsed since last cycle so a restart + # doesn't reset the countdown — fire as soon as the interval is due. + last_run_iso = _current_status.get("last_run_at") + if not last_run_iso: + # Also check DB for the most recent completed run + try: + from services.database import get_cycle_runs + recent = get_cycle_runs(limit=1) + if recent: + last_run_iso = recent[0].get("created_at") or recent[0].get("run_id", "")[:19].replace("T", " ") + except Exception: + pass + elapsed_secs = 0.0 + if last_run_iso: + try: + last_dt = datetime.fromisoformat(last_run_iso.replace(" ", "T").rstrip("Z")) + elapsed_secs = (now_utc - last_dt).total_seconds() + except Exception: + pass + wait_secs = max(60, interval_hours * 3600 - elapsed_secs) + next_run = now_utc + timedelta(seconds=wait_secs) _current_status["next_run_at"] = next_run.isoformat() _current_status["interval_hours"] = interval_hours - logger.info(f"[Scheduler] Prochain cycle dans {interval_hours}h ({next_run.strftime('%H:%M')} UTC)") + logger.info(f"[Scheduler] Prochain cycle dans {wait_secs/3600:.1f}h ({next_run.strftime('%H:%M')} UTC) — elapsed since last: {elapsed_secs/3600:.1f}h") stop_event.wait(timeout=wait_secs)