Skip to content

Commit

Permalink
Fixed shutdown() not raising the correct exception for some schedulers
Browse files Browse the repository at this point in the history
Fixes #1019.
  • Loading branch information
agronholm committed Jan 19, 2025
1 parent 6c72a51 commit b1f5636
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <migration>`.

**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
Expand Down
12 changes: 10 additions & 2 deletions src/apscheduler/schedulers/asyncio.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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))
Expand Down
12 changes: 11 additions & 1 deletion src/apscheduler/schedulers/tornado.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion src/apscheduler/schedulers/twisted.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions tests/test_schedulers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit b1f5636

Please sign in to comment.