Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions astrbot/core/cron/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,28 @@ def __init__(self, db: BaseDatabase) -> None:
self._basic_handlers: dict[str, Callable[..., Any]] = {}
self._lock = asyncio.Lock()
self._started = False
# The scheduler may start early via _schedule_job; track DB sync separately.
self._db_synced = False

async def start(self, ctx: "Context") -> None:
self.ctx: Context = ctx # star context
async with self._lock:
if self._started:
if self._db_synced:
return
self.scheduler.start()
self._started = True
if not self._started:
self.scheduler.start()
self._started = True
await self.sync_from_db()
self._db_synced = True

async def shutdown(self) -> None:
async with self._lock:
if not self._started:
return
self.scheduler.shutdown(wait=False)
await asyncio.sleep(0)
self._started = False
self._db_synced = False

async def sync_from_db(self) -> None:
jobs = await self.db.list_cron_jobs()
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_cron_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_init(self, mock_db):
assert manager.db == mock_db
assert manager._basic_handlers == {}
assert manager._started is False
assert manager._db_synced is False


class TestCronJobManagerStart:
Expand All @@ -95,6 +96,52 @@ async def test_start_idempotent(self, cron_manager, mock_db, mock_context):
# Should only sync once
assert mock_db.list_cron_jobs.call_count == 1

@pytest.mark.asyncio
async def test_start_resyncs_after_shutdown(
self, cron_manager, mock_db, mock_context
):
"""Test that restarting the manager resyncs the database."""
mock_db.list_cron_jobs.return_value = []

await cron_manager.start(mock_context)
await cron_manager.shutdown()

assert cron_manager._started is False
assert cron_manager._db_synced is False

await cron_manager.start(mock_context)

assert mock_db.list_cron_jobs.call_count == 2
assert cron_manager._started is True
assert cron_manager._db_synced is True

await cron_manager.shutdown()

@pytest.mark.asyncio
async def test_start_syncs_after_scheduler_started_early(
self, cron_manager, mock_db, mock_context, sample_cron_job
):
"""Test that early scheduler startup does not skip database sync."""
mock_db.create_cron_job.return_value = sample_cron_job
mock_db.list_cron_jobs.return_value = [sample_cron_job]

await cron_manager.add_basic_job(
name="Early Job",
cron_expression="0 9 * * *",
handler=MagicMock(),
enabled=True,
persistent=False,
)

await cron_manager.start(mock_context)

assert cron_manager._started is True
assert cron_manager._db_synced is True
assert cron_manager.scheduler.get_job(sample_cron_job.job_id) is not None
assert mock_db.list_cron_jobs.call_count == 1

await cron_manager.shutdown()


class TestCronJobManagerShutdown:
"""Tests for CronJobManager.shutdown method."""
Expand Down