Skip to content

Commit 90133b7

Browse files
authored
Another try at tracking down ResourceWarning with tracemalloc. (#1353)
1 parent 72d7ca8 commit 90133b7

File tree

4 files changed

+47
-17
lines changed

4 files changed

+47
-17
lines changed

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ filterwarnings= [
173173
# Ignore jupyter_client warnings
174174
"module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning",
175175

176+
# we do not want to raise on resources warnings, or we will not have a chance to
177+
# collect the messages and print the location of the leak
178+
"always::ResourceWarning",
179+
176180
# ignore unclosed sqlite in traits
177181
"ignore:unclosed database in <sqlite3.Connection:ResourceWarning",
178182

tests/conftest.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import logging
33
import os
4+
import warnings
45
from math import inf
56
from typing import Any, Callable, no_type_check
67
from unittest.mock import MagicMock
@@ -22,6 +23,11 @@
2223
# Windows
2324
resource = None # type:ignore
2425

26+
try:
27+
import tracemalloc
28+
except ModuleNotFoundError:
29+
tracemalloc = None
30+
2531

2632
@pytest.fixture()
2733
def anyio_backend():
@@ -207,3 +213,38 @@ async def ipkernel(anyio_backend):
207213
yield kernel
208214
kernel.destroy()
209215
ZMQInteractiveShell.clear_instance()
216+
217+
218+
@pytest.fixture()
219+
def tracemalloc_resource_warning(recwarn, N=10):
220+
"""fixture to enable tracemalloc for a single test, and report the
221+
location of the leaked resource
222+
223+
We cannot only enable tracemalloc, as otherwise it is stopped just after the
224+
test, the frame cache is cleared by tracemalloc.stop() and thus the warning
225+
printing code get None when doing
226+
`tracemalloc.get_object_traceback(r.source)`.
227+
228+
So we need to both filter the warnings to enable ResourceWarning, and loop
229+
through it print the stack before we stop tracemalloc and continue.
230+
231+
"""
232+
if tracemalloc is None:
233+
yield
234+
return
235+
236+
tracemalloc.start(N)
237+
with warnings.catch_warnings():
238+
warnings.simplefilter("always", category=ResourceWarning)
239+
yield None
240+
try:
241+
for r in recwarn:
242+
if r.category is ResourceWarning and r.source is not None:
243+
tb = tracemalloc.get_object_traceback(r.source)
244+
if tb:
245+
info = f"Leaking resource:{r}\n |" + "\n |".join(tb.format())
246+
# technically an Error and not a failure as we fail in the fixture
247+
# and not the test
248+
pytest.fail(info)
249+
finally:
250+
tracemalloc.stop()

tests/test_connect.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,6 @@
1919

2020
from .utils import TemporaryWorkingDirectory
2121

22-
23-
@pytest.fixture(scope="module", autouse=True)
24-
def _enable_tracemalloc():
25-
try:
26-
import tracemalloc
27-
except ModuleNotFoundError:
28-
# pypy
29-
tracemalloc = None
30-
if tracemalloc is not None:
31-
tracemalloc.start()
32-
yield
33-
if tracemalloc is not None:
34-
tracemalloc.stop()
35-
36-
3722
sample_info: dict = {
3823
"ip": "1.2.3.4",
3924
"transport": "ipc",
@@ -133,7 +118,7 @@ def test_port_bind_failure_recovery(request):
133118
app.init_sockets()
134119

135120

136-
def test_port_bind_failure_gives_up_retries(request):
121+
def test_port_bind_failure_gives_up_retries(request, tracemalloc_resource_warning):
137122
cfg = Config()
138123
with TemporaryWorkingDirectory() as d:
139124
cfg.ProfileDir.location = d

tests/test_ipkernel_direct.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def test_direct_execute_request_aborting(ipkernel):
5151
assert reply["content"]["status"] == "aborted"
5252

5353

54-
async def test_complete_request(ipkernel):
54+
async def test_complete_request(ipkernel, tracemalloc_resource_warning):
5555
reply = await ipkernel.test_shell_message("complete_request", dict(code="hello", cursor_pos=0))
5656
assert reply["header"]["msg_type"] == "complete_reply"
5757
ipkernel.use_experimental_completions = False

0 commit comments

Comments
 (0)