summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Grönholm <alex.gronholm@nextday.fi>2017-12-09 18:58:38 +0200
committerAlex Grönholm <alex.gronholm@nextday.fi>2017-12-09 19:02:58 +0200
commit0a85a0a1375271929d6f708ae74fab59a4b5c04a (patch)
treebe3673f6c51935f5d8c1ed628662f4e14109f406
parentad9109042b727fe1366b7f1b10382766a9a3469f (diff)
downloadapscheduler-0a85a0a1375271929d6f708ae74fab59a4b5c04a.tar.gz
Fixed memory leak when scheduled jobs raise exceptions
Fixes #235.
-rw-r--r--apscheduler/executors/base.py10
-rw-r--r--docs/versionhistory.rst3
-rw-r--r--tests/test_executors.py21
3 files changed, 33 insertions, 1 deletions
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 <migration>`.
* 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