From e0ba56808967f6bac652fa8e5ed13ac32874f2e4 Mon Sep 17 00:00:00 2001 From: weitongtong Date: Sat, 11 Apr 2026 14:34:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(cron):=20=E4=BF=AE=E5=A4=8D=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E9=97=B4=E9=9A=94=E4=BB=BB=E5=8A=A1=E5=9B=A0=20store?= =?UTF-8?q?=20=E5=B9=B6=E5=8F=91=E6=9B=BF=E6=8D=A2=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E9=87=8D=E5=A4=8D=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _on_timer 中 await _execute_job 让出控制权期间,前端轮询触发的 list_jobs 调用 _load_store 从磁盘重新加载覆盖 self._store, 已执行任务的状态被旧值回退,导致再次触发。 引入 _timer_active 标志位,在任务执行期间阻止并发 _load_store 替换 store。同时修复 store 为空时未重新 arm timer 的问题。 Made-with: Cursor --- nanobot/cron/service.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/nanobot/cron/service.py b/nanobot/cron/service.py index 267613012..165ce54d7 100644 --- a/nanobot/cron/service.py +++ b/nanobot/cron/service.py @@ -80,6 +80,7 @@ class CronService: self._store: CronStore | None = None self._timer_task: asyncio.Task | None = None self._running = False + self._timer_active = False self.max_sleep_ms = max_sleep_ms def _load_jobs(self) -> tuple[list[CronJob], int]: @@ -171,7 +172,11 @@ class CronService: def _load_store(self) -> CronStore: """Load jobs from disk. Reloads automatically if file was modified externally. - Reload every time because it needs to merge operations on the jobs object from other instances. + - During _on_timer execution, return the existing store to prevent concurrent + _load_store calls (e.g. from list_jobs polling) from replacing it mid-execution. """ + if self._timer_active and self._store: + return self._store jobs, version = self._load_jobs() self._store = CronStore(version=version, jobs=jobs) self._merge_action() @@ -290,18 +295,23 @@ class CronService: """Handle timer tick - run due jobs.""" self._load_store() if not self._store: + self._arm_timer() return - now = _now_ms() - due_jobs = [ - j for j in self._store.jobs - if j.enabled and j.state.next_run_at_ms and now >= j.state.next_run_at_ms - ] + self._timer_active = True + try: + now = _now_ms() + due_jobs = [ + j for j in self._store.jobs + if j.enabled and j.state.next_run_at_ms and now >= j.state.next_run_at_ms + ] - for job in due_jobs: - await self._execute_job(job) + for job in due_jobs: + await self._execute_job(job) - self._save_store() + self._save_store() + finally: + self._timer_active = False self._arm_timer() async def _execute_job(self, job: CronJob) -> None: