From b1f5636ccaf6a2fe86b208d7bd6b43024a1546b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 19 Jan 2025 13:28:31 +0200 Subject: [PATCH] Fixed shutdown() not raising the correct exception for some schedulers Fixes #1019. --- docs/versionhistory.rst | 5 +++++ src/apscheduler/schedulers/asyncio.py | 12 ++++++++++-- src/apscheduler/schedulers/tornado.py | 12 +++++++++++- src/apscheduler/schedulers/twisted.py | 9 ++++++++- tests/test_schedulers.py | 4 ++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 3fea1d86..5877bfab 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -4,6 +4,11 @@ Version history To find out how to migrate your application from a previous version of APScheduler, see the :doc:`migration section `. +**UNRELEASED** + +- Fixed ``scheduler.shutdown()`` not raising ``SchedulerNotRunning`` (or raising the + wrong exception) for asynchronous schedulers when the scheduler is in fact not running + **3.11.0** - Dropped support for Python 3.6 and 3.7 diff --git a/src/apscheduler/schedulers/asyncio.py b/src/apscheduler/schedulers/asyncio.py index ff31adbb..7fb3fa60 100644 --- a/src/apscheduler/schedulers/asyncio.py +++ b/src/apscheduler/schedulers/asyncio.py @@ -1,6 +1,7 @@ import asyncio from functools import partial, wraps +from apscheduler.schedulers import SchedulerNotRunningError from apscheduler.schedulers.base import BaseScheduler from apscheduler.util import maybe_ref @@ -31,15 +32,22 @@ class AsyncIOScheduler(BaseScheduler): _timeout = None def start(self, paused=False): - if not self._eventloop: + if not self._eventloop or self._eventloop.is_closed(): self._eventloop = asyncio.get_running_loop() super().start(paused) @run_in_event_loop - def shutdown(self, wait=True): + def _shutdown(self, wait=True): super().shutdown(wait) self._stop_timer() + self._eventloop = None + + def shutdown(self, wait=True): + if not self.running: + raise SchedulerNotRunningError + + self._shutdown(wait) def _configure(self, config): self._eventloop = maybe_ref(config.pop("event_loop", None)) diff --git a/src/apscheduler/schedulers/tornado.py b/src/apscheduler/schedulers/tornado.py index 96741b79..a25764d2 100644 --- a/src/apscheduler/schedulers/tornado.py +++ b/src/apscheduler/schedulers/tornado.py @@ -1,6 +1,7 @@ from datetime import timedelta from functools import wraps +from apscheduler.schedulers import SchedulerNotRunningError from apscheduler.schedulers.base import BaseScheduler from apscheduler.util import maybe_ref @@ -13,6 +14,9 @@ def run_in_ioloop(func): @wraps(func) def wrapper(self, *args, **kwargs): + if self._ioloop is None: + raise SchedulerNotRunningError + self._ioloop.add_callback(func, self, *args, **kwargs) return wrapper @@ -33,10 +37,16 @@ class TornadoScheduler(BaseScheduler): _timeout = None @run_in_ioloop - def shutdown(self, wait=True): + def _shutdown(self, wait=True): super().shutdown(wait) self._stop_timer() + def shutdown(self, wait=True): + if not self.running: + raise SchedulerNotRunningError + + self._shutdown(wait) + def _configure(self, config): self._ioloop = maybe_ref(config.pop("io_loop", None)) or IOLoop.current() super()._configure(config) diff --git a/src/apscheduler/schedulers/twisted.py b/src/apscheduler/schedulers/twisted.py index 7a3622c1..8a918314 100644 --- a/src/apscheduler/schedulers/twisted.py +++ b/src/apscheduler/schedulers/twisted.py @@ -1,5 +1,6 @@ from functools import wraps +from apscheduler.schedulers import SchedulerNotRunningError from apscheduler.schedulers.base import BaseScheduler from apscheduler.util import maybe_ref @@ -36,10 +37,16 @@ def _configure(self, config): super()._configure(config) @run_in_reactor - def shutdown(self, wait=True): + def _shutdown(self, wait=True): super().shutdown(wait) self._stop_timer() + def shutdown(self, wait=True): + if not self.running: + raise SchedulerNotRunningError + + self._shutdown(wait) + def _start_timer(self, wait_seconds): self._stop_timer() if wait_seconds is not None: diff --git a/tests/test_schedulers.py b/tests/test_schedulers.py index 8319fe8b..b781461e 100644 --- a/tests/test_schedulers.py +++ b/tests/test_schedulers.py @@ -1154,6 +1154,10 @@ def eventqueue(self, scheduler): def wait_event(self, queue): return queue.get(True, 1) + def test_immediate_shutdown(self, scheduler): + with pytest.raises(SchedulerNotRunningError): + scheduler.shutdown() + def test_add_pending_job(self, scheduler, freeze_time, eventqueue, start_scheduler): """Tests that pending jobs are added (and if due, executed) when the scheduler starts.""" freeze_time.set_increment(timedelta(seconds=0.2))