-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: don't pass process stack via context (#4699)
This PR fixes a memory leak: when running `CalcJob`s over an SSH connection, the first CalcJob that was run remained in memory indefinitely. `plumpy` uses the `contextvars` module to provide a reference to the `current_process` anywhere in a task launched by a process. When using any of `asyncio`'s `call_soon`, `call_later` or `call_at` methods, each individual function execution gets their own copy of this context. This means that as long as a handle to these scheduled executions remains in memory, the copy of the `'process stack'` context var (and thus the process itself) remain in memory, In this particular case, a handle to such a task (`do_open` a `transport`) remained in memory and caused the whole process to remain in memory as well via the 'process stack' context variable. This is fixed by explicitly passing an empty context to the execution of `do_open` (which anyhow does not need access to the `current_process`). An explicit test is added to make sure that no references to processes are leaked after running process via the interpreter as well as in the daemon tests. This PR adds the empty context in two other invocations of `call_later`, but there are more places in the code where these methods are used. As such it is a bit of a workaround. Eventually, this problem should likely be addressed by converting any functions that use `call_soon`, `call_later` or `call_at` and all their parents in the call stack to coroutines. Co-authored-by: Chris Sewell <[email protected]>
- Loading branch information
1 parent
e7223ae
commit b07841a
Showing
10 changed files
with
154 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# -*- coding: utf-8 -*- | ||
########################################################################### | ||
# Copyright (c), The AiiDA team. All rights reserved. # | ||
# This file is part of the AiiDA code. # | ||
# # | ||
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core # | ||
# For further information on the license, see the LICENSE.txt file # | ||
# For further information please visit http://www.aiida.net # | ||
########################################################################### | ||
"""Utilities for testing memory leakage.""" | ||
from tests.utils import processes as test_processes # pylint: disable=no-name-in-module,import-error | ||
from tests.utils.memory import get_instances # pylint: disable=no-name-in-module,import-error | ||
from aiida.engine import processes, run | ||
from aiida.plugins import CalculationFactory | ||
from aiida import orm | ||
|
||
ArithmeticAddCalculation = CalculationFactory('arithmetic.add') | ||
|
||
|
||
def test_leak_run_process(): | ||
"""Test whether running a dummy process leaks memory.""" | ||
inputs = {'a': orm.Int(2), 'b': orm.Str('test')} | ||
run(test_processes.DummyProcess, **inputs) | ||
|
||
# check that no reference to the process is left in memory | ||
# some delay is necessary in order to allow for all callbacks to finish | ||
process_instances = get_instances(processes.Process, delay=0.2) | ||
assert not process_instances, f'Memory leak: process instances remain in memory: {process_instances}' | ||
|
||
|
||
def test_leak_local_calcjob(aiida_local_code_factory): | ||
"""Test whether running a local CalcJob leaks memory.""" | ||
inputs = {'x': orm.Int(1), 'y': orm.Int(2), 'code': aiida_local_code_factory('arithmetic.add', '/usr/bin/diff')} | ||
run(ArithmeticAddCalculation, **inputs) | ||
|
||
# check that no reference to the process is left in memory | ||
# some delay is necessary in order to allow for all callbacks to finish | ||
process_instances = get_instances(processes.Process, delay=0.2) | ||
assert not process_instances, f'Memory leak: process instances remain in memory: {process_instances}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# -*- coding: utf-8 -*- | ||
########################################################################### | ||
# Copyright (c), The AiiDA team. All rights reserved. # | ||
# This file is part of the AiiDA code. # | ||
# # | ||
# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core # | ||
# For further information on the license, see the LICENSE.txt file # | ||
# For further information please visit http://www.aiida.net # | ||
########################################################################### | ||
"""Utilities for testing memory leakage.""" | ||
import asyncio | ||
from pympler import muppy | ||
|
||
|
||
def get_instances(classes, delay=0.0): | ||
"""Return all instances of provided classes that are in memory. | ||
Useful for investigating memory leaks. | ||
:param classes: A class or tuple of classes to check (passed to `isinstance`). | ||
:param delay: How long to sleep (seconds) before collecting the memory dump. | ||
This is a convenience function for tests involving Processes. For example, :py:func:`~aiida.engine.run` returns | ||
before all futures are resolved/cleaned up. Dumping memory too early would catch those and the references they | ||
carry, although they may not actually be leaking memory. | ||
""" | ||
if delay > 0: | ||
loop = asyncio.get_event_loop() | ||
loop.run_until_complete(asyncio.sleep(delay)) | ||
|
||
all_objects = muppy.get_objects() # this also calls gc.collect() | ||
return [o for o in all_objects if hasattr(o, '__class__') and isinstance(o, classes)] |