diff options
-rw-r--r-- | apscheduler/executors/base_py3.py | 2 | ||||
-rw-r--r-- | docs/versionhistory.rst | 1 | ||||
-rw-r--r-- | tests/test_executors_py35.py | 25 |
3 files changed, 27 insertions, 1 deletions
diff --git a/apscheduler/executors/base_py3.py b/apscheduler/executors/base_py3.py index 61abd84..7111d2a 100644 --- a/apscheduler/executors/base_py3.py +++ b/apscheduler/executors/base_py3.py @@ -1,5 +1,6 @@ import logging import sys +import traceback from datetime import datetime, timedelta from traceback import format_tb @@ -33,6 +34,7 @@ async def run_coroutine_job(job, jobstore_alias, run_times, logger_name): events.append(JobExecutionEvent(EVENT_JOB_ERROR, job.id, jobstore_alias, run_time, exception=exc, traceback=formatted_tb)) logger.exception('Job "%s" raised an exception', job) + traceback.clear_frames(tb) else: events.append(JobExecutionEvent(EVENT_JOB_EXECUTED, job.id, jobstore_alias, run_time, retval=retval)) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 488adba..8c5d552 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -17,6 +17,7 @@ APScheduler, see the :doc:`migration section <migration>`. * Added PySide2 support (PR by Abdulla Ibrahim) * Fixed ``BlockingScheduler`` and ``BackgroundScheduler`` shutdown hanging after the user has erroneously tried to start it twice +* Fixed memory leak when coroutine jobs raise exceptions (due to reference cycles in tracebacks) 3.6.3 diff --git a/tests/test_executors_py35.py b/tests/test_executors_py35.py index 7849eb0..ee66584 100644 --- a/tests/test_executors_py35.py +++ b/tests/test_executors_py35.py @@ -1,13 +1,18 @@ """Contains test functions using Python 3.3+ syntax.""" +import gc from asyncio import CancelledError from datetime import datetime +from unittest.mock import Mock, patch import pytest +from pytz import utc + from apscheduler.executors.asyncio import AsyncIOExecutor +from apscheduler.executors.base_py3 import run_coroutine_job from apscheduler.executors.tornado import TornadoExecutor +from apscheduler.job import Job from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.tornado import TornadoScheduler -from pytz import utc @pytest.fixture @@ -100,3 +105,21 @@ async def test_asyncio_executor_shutdown(asyncio_scheduler, asyncio_executor): asyncio_executor.shutdown() with pytest.raises(CancelledError): await futures.pop() + + +@pytest.mark.asyncio +async def test_run_job_memory_leak(): + class FooBar: + pass + + async def func(): + foo = FooBar() # noqa: F841 + raise Exception('dummy') + + fake_job = Mock(Job, func=func, args=(), kwargs={}, misfire_grace_time=1) + with patch('logging.getLogger'): + for _ in range(5): + await run_coroutine_job(fake_job, 'foo', [datetime.now(utc)], __name__) + + foos = [x for x in gc.get_objects() if type(x) is FooBar] + assert len(foos) == 0 |