Skip to content

Commit 9dcd6f8

Browse files
Fix run_engine fixture file handler leak and add test to check for it (#1681)
* Added test to check for memory leak of run_engine * Adjusted variable names * Fixed test * Added small threshold to trigger test fail, increased number of iterations * First at attempt at fixing memory leak. Single instance of run_engine but is cleared between each use. * fixed global * Correct spelling * Remove pytest_sessionfinish * Correct _ensure_running_bluesky_event_loop to use same RunEngine * Rename test to test_run_engine_fixture_has_no_file_handler_leak * Fixed test, corrected error message to file handler
1 parent bab629c commit 9dcd6f8

File tree

4 files changed

+40
-4
lines changed

4 files changed

+40
-4
lines changed

src/dodal/testing/fixtures/run_engine.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,25 @@
1010
from bluesky.run_engine import RunEngine
1111
from bluesky.simulators import RunEngineSimulator
1212

13+
_run_engine = RunEngine()
14+
1315

1416
@pytest.fixture(scope="session", autouse=True)
1517
async def _ensure_running_bluesky_event_loop():
16-
run_engine = RunEngine()
1718
# make sure the event loop is thoroughly up and running before we try to create
1819
# any ophyd_async devices which might need it
1920
timeout = time.monotonic() + 1
20-
while not run_engine.loop.is_running():
21+
while not _run_engine.loop.is_running():
2122
await asyncio.sleep(0)
2223
if time.monotonic() > timeout:
2324
raise TimeoutError("This really shouldn't happen but just in case...")
2425

2526

2627
@pytest.fixture()
27-
async def run_engine():
28-
yield RunEngine()
28+
def run_engine():
29+
global _run_engine
30+
_run_engine.reset()
31+
return _run_engine
2932

3033

3134
@pytest.fixture

tests/testing/__init__.py

Whitespace-only changes.

tests/testing/fixtures/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import os
2+
3+
import pytest
4+
from bluesky import RunEngine
5+
6+
_baseline_n_open_files = None
7+
threshold = 5
8+
9+
10+
@pytest.mark.parametrize("iteration", range(11))
11+
def test_run_engine_fixture_has_no_file_handler_leak(
12+
run_engine: RunEngine, iteration: int
13+
) -> None:
14+
global _baseline_n_open_files
15+
pid = os.getpid()
16+
n_open_files = len(os.listdir(f"/proc/{pid}/fd"))
17+
if iteration == 0:
18+
_baseline_n_open_files = n_open_files
19+
else:
20+
if _baseline_n_open_files is None:
21+
raise Exception(
22+
"Unable to determine number of file handlers as it is None."
23+
)
24+
try:
25+
delta = n_open_files - _baseline_n_open_files
26+
# Allow a small threshold from other processes e.g from pytest itself.
27+
assert delta < threshold
28+
except AssertionError as exc:
29+
raise AssertionError(
30+
"Detected file handle leak, the number of open files has increased from "
31+
f"{_baseline_n_open_files} to {n_open_files} when calling the "
32+
"run_engine fixture",
33+
) from exc

0 commit comments

Comments
 (0)