feat(scheduler): persist task state/progress/due_at across restart (#8)

Replace process-start next_run=0 guesses with kv task:state records
(status, due_at, stat_date, progress). Startup recovers stale running,
honors future due_at for delayed retries, and resumes the same
stat_date. Seed only enqueues evening seal; seal force keeps non-seed
ok=1 as progress (keep_scraped). Keeps !7 fail-logging and cleanup retry.
This commit is contained in:
bot-ci committed 2026-11-03 01:50:00 +08:00
1 parent 51ffa44577
commit f408da93c2
7 files changed
+1793 -902

No files matched your search

+24
View File
@@ -1983,6 +1983,22 @@ class DashboardQueries:
}
retry_seconds = 300
seed_retry_seconds = 600
# 优先 task:state.due_at(跨重启唯一时间门闩);无则回退 task:last 推导。
state_rows = conn.execute(
"SELECT k, v FROM kv WHERE k LIKE 'task:state:%'"
).fetchall()
for row in state_rows:
key = str(row["k"]).removeprefix("task:state:")
ui_name = "discover" if key == "discovery" else key
if ui_name not in next_runs:
continue
try:
state = json.loads(str(row["v"]))
due = float(state.get("due_at") or 0)
except (TypeError, ValueError, json.JSONDecodeError):
continue
if due > 0:
next_runs[ui_name] = int(due)
for name, key, at in (
("fans", "fans", cfg.schedule.snapshot_at),
("snapshot", "snapshot", cfg.schedule.snapshot_at),
@@ -1990,6 +2006,14 @@ class DashboardQueries:
("discover", "discovery", None),
("snapshot_recent", "snapshot_recent", None),
):
# 已有落盘 due_at 则不再用 last 覆盖
state_raw = self._kv(conn, f"task:state:{key}")
if state_raw:
try:
if float(json.loads(state_raw).get("due_at") or 0) > 0:
continue
except (TypeError, ValueError, json.JSONDecodeError):
pass
record = tasks.get(key)
if not (isinstance(record, dict) and record.get("finished_at")):
continue