fix(scheduler): daily slot off-plan + cross-midnight resume (#8)
- Cross-midnight: unfinished yesterday seals ASAP with yesterday's stat_date; force seal ignores today's snapshot_at gate when stat_date < today. - Off-plan already_completed: compare completion started_at (not finished_at) to today's planned slot; earlier → still run at slot for all daily tasks. - Cold start: bootstrap task:state from task:last / seed markers. - ensure_pending: do not glue new due onto a different day's unfinished state. - run_if_needed(force): keep_scraped True for seal, False for CLI --force. - Scheduler due_reader: pick up disk due_at each loop (seed→evening seal).
This commit is contained in:
1 parent
dcf37c4a1d
commit
f6bef59e07
7 files changed
+630
-24
No files matched your search
@@ -59,6 +59,7 @@ class Scheduler:
|
||||
clock=time.time,
|
||||
sleeper=time.sleep,
|
||||
logger=None,
|
||||
due_reader: Callable[[str], float | None] | None = None,
|
||||
) -> None:
|
||||
if not tasks:
|
||||
raise ValueError("至少需要一个调度任务")
|
||||
@@ -69,6 +70,8 @@ class Scheduler:
|
||||
self._clock = clock
|
||||
self._sleeper = sleeper
|
||||
self.logger = logger
|
||||
# 每轮从落盘 due_at 刷新 next_run(seed 挂当晚 seal 等同进程可见)
|
||||
self._due_reader = due_reader
|
||||
|
||||
def _log(self, level: str, event: str, **fields) -> None:
|
||||
if self.logger is not None:
|
||||
@@ -78,6 +81,10 @@ class Scheduler:
|
||||
current = self._clock() if now is None else now
|
||||
results: list[TaskResult] = []
|
||||
for task in self.tasks:
|
||||
if self._due_reader is not None:
|
||||
disk_due = self._due_reader(task.name)
|
||||
if disk_due is not None:
|
||||
task.next_run = float(disk_due)
|
||||
if task.next_run > current:
|
||||
continue
|
||||
started = current
|
||||
|
||||
Reference in new issue
Block a user