mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-24 11:55:52 +00:00
Merge PR #3038: fix(cron): guard _load_store against reentrant reload during job execution
fix(cron): guard _load_store against reentrant reload during job execution
This commit is contained in:
commit
1263869c0a
@ -479,3 +479,49 @@ def test_update_job_sentinel_channel_and_to(tmp_path) -> None:
|
|||||||
assert isinstance(result, CronJob)
|
assert isinstance(result, CronJob)
|
||||||
assert result.payload.channel is None
|
assert result.payload.channel is None
|
||||||
assert result.payload.to is None
|
assert result.payload.to is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_jobs_during_on_job_does_not_cause_stale_reload(tmp_path) -> None:
|
||||||
|
"""Regression: if the bot calls list_jobs (which reloads from disk) during
|
||||||
|
on_job execution, the in-memory next_run_at_ms update must not be lost.
|
||||||
|
Previously this caused an infinite re-trigger loop."""
|
||||||
|
store_path = tmp_path / "cron" / "jobs.json"
|
||||||
|
execution_count = 0
|
||||||
|
|
||||||
|
async def on_job_that_lists(job):
|
||||||
|
nonlocal execution_count
|
||||||
|
execution_count += 1
|
||||||
|
# Simulate the bot calling cron(action=list) mid-execution
|
||||||
|
service.list_jobs()
|
||||||
|
|
||||||
|
service = CronService(store_path, on_job=on_job_that_lists, max_sleep_ms=100)
|
||||||
|
await service.start()
|
||||||
|
|
||||||
|
# Add two jobs scheduled in the past so they're immediately due
|
||||||
|
now_ms = int(time.time() * 1000)
|
||||||
|
for name in ("job-a", "job-b"):
|
||||||
|
service.add_job(
|
||||||
|
name=name,
|
||||||
|
schedule=CronSchedule(kind="every", every_ms=3_600_000),
|
||||||
|
message="test",
|
||||||
|
)
|
||||||
|
# Force next_run to the past so _on_timer picks them up
|
||||||
|
for job in service._store.jobs:
|
||||||
|
job.state.next_run_at_ms = now_ms - 1000
|
||||||
|
service._save_store()
|
||||||
|
service._arm_timer()
|
||||||
|
|
||||||
|
# Let the timer fire once
|
||||||
|
await asyncio.sleep(0.3)
|
||||||
|
service.stop()
|
||||||
|
|
||||||
|
# Each job should have run exactly once, not looped
|
||||||
|
assert execution_count == 2
|
||||||
|
|
||||||
|
# Verify next_run_at_ms was persisted correctly (in the future)
|
||||||
|
raw = json.loads(store_path.read_text())
|
||||||
|
for j in raw["jobs"]:
|
||||||
|
next_run = j["state"]["nextRunAtMs"]
|
||||||
|
assert next_run is not None
|
||||||
|
assert next_run > now_ms, f"Job '{j['name']}' next_run should be in the future"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user