From 0a85a0a1375271929d6f708ae74fab59a4b5c04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 9 Dec 2017 18:58:38 +0200 Subject: Fixed memory leak when scheduled jobs raise exceptions Fixes #235. --- apscheduler/executors/base.py | 10 ++++++++++ docs/versionhistory.rst | 3 +++ tests/test_executors.py | 21 ++++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apscheduler/executors/base.py b/apscheduler/executors/base.py index a42768e..6ed1ec3 100644 --- a/apscheduler/executors/base.py +++ b/apscheduler/executors/base.py @@ -126,6 +126,16 @@ def run_job(job, jobstore_alias, run_times, logger_name): except BaseException: exc, tb = sys.exc_info()[1:] formatted_tb = ''.join(format_tb(tb)) + + # This is to prevent cyclic references that would lead to memory leaks + if six.PY2: + sys.exc_clear() + del tb + else: + import traceback + traceback.clear_frames(tb) + del tb + 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) diff --git a/docs/versionhistory.rst b/docs/versionhistory.rst index 66c09d3..8f54329 100644 --- a/docs/versionhistory.rst +++ b/docs/versionhistory.rst @@ -9,6 +9,9 @@ APScheduler, see the :doc:`migration section `. * Added the ``engine_options`` option to ``SQLAlchemyJobStore`` +* Fixed memory leak due to a cyclic reference when jobs raise exceptions + (thanks to gilbsgilbs for help on solving this) + 3.4.0 ----- diff --git a/tests/test_executors.py b/tests/test_executors.py index 2e90c17..d4aa221 100644 --- a/tests/test_executors.py +++ b/tests/test_executors.py @@ -1,12 +1,15 @@ from datetime import datetime from threading import Event from types import TracebackType +import gc import time import pytest +from pytz import UTC from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_MISSED, EVENT_JOB_EXECUTED -from apscheduler.executors.base import MaxInstancesReachedError +from apscheduler.executors.base import MaxInstancesReachedError, run_job +from apscheduler.job import Job from apscheduler.schedulers.base import BaseScheduler try: @@ -124,3 +127,19 @@ def test_run_job_error(monkeypatch, executor): assert str(exc_traceback[0]) == "dummy" if exc_traceback[1] is not None: assert isinstance(exc_traceback[1], TracebackType) + + +def test_run_job_memory_leak(): + class FooBar(object): + pass + + def func(): + foo = FooBar() # noqa: F841 + raise Exception('dummy') + + fake_job = Mock(Job, func=func, args=(), kwargs={}, misfire_grace_time=1) + for _ in range(5): + run_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 -- cgit v1.2.1