Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES/11235.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Included object creation tracebacks in unclosed resource warnings when loop debug mode is enabled.
-- by :user:`nightcityblade`.
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ Moss Collum
Mun Gwan-gyeong
Navid Sheikhol
Nicolas Braem
Nightcity Blade
Nikolay Kim
Nikolay Novik
Nikolay Tiunov
Expand Down
5 changes: 4 additions & 1 deletion aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
_auth_header_from_netrc,
frozen_dataclass_decorator,
get_env_proxy_for_url,
get_unclosed_warning_message,
netrc_from_env,
sentinel,
strip_auth_from_url,
Expand Down Expand Up @@ -431,7 +432,9 @@ def __init_subclass__(cls: type["ClientSession"]) -> None:
def __del__(self, _warnings: Any = warnings) -> None:
if not self.closed:
_warnings.warn(
f"Unclosed client session {self!r}",
get_unclosed_warning_message(
f"Unclosed client session {self!r}", self._source_traceback
),
ResourceWarning,
source=self,
)
Expand Down
7 changes: 6 additions & 1 deletion aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
TimerNoop,
encode_basic_auth,
frozen_dataclass_decorator,
get_unclosed_warning_message,
is_expected_content_type,
parse_mimetype,
reify,
Expand Down Expand Up @@ -390,7 +391,11 @@ def __del__(self, _warnings: Any = warnings) -> None:

if self._loop.get_debug():
_warnings.warn(
f"Unclosed response {self!r}", ResourceWarning, source=self
get_unclosed_warning_message(
f"Unclosed response {self!r}", self._source_traceback
),
ResourceWarning,
source=self,
)
context = {"client_response": self, "message": "Unclosed response"}
if self._source_traceback:
Expand Down
15 changes: 13 additions & 2 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from .helpers import (
_SENTINEL,
ceil_timeout,
get_unclosed_warning_message,
is_ip_address,
sentinel,
set_exception,
Expand Down Expand Up @@ -130,7 +131,11 @@ def __repr__(self) -> str:
def __del__(self, _warnings: Any = warnings) -> None:
if self._protocol is not None:
_warnings.warn(
f"Unclosed connection {self!r}", ResourceWarning, source=self
get_unclosed_warning_message(
f"Unclosed connection {self!r}", self._source_traceback
),
ResourceWarning,
source=self,
)
if self._loop.is_closed():
return
Expand Down Expand Up @@ -333,7 +338,13 @@ def __del__(self, _warnings: Any = warnings) -> None:

self._close_immediately()

_warnings.warn(f"Unclosed connector {self!r}", ResourceWarning, source=self)
_warnings.warn(
get_unclosed_warning_message(
f"Unclosed connector {self!r}", self._source_traceback
),
ResourceWarning,
source=self,
)
context = {
"connector": self,
"connections": conns,
Expand Down
9 changes: 9 additions & 0 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import re
import sys
import time
import traceback
import warnings
import weakref
from collections.abc import Callable, Iterable, Iterator, Mapping
Expand Down Expand Up @@ -119,6 +120,14 @@
)


def get_unclosed_warning_message(
message: str, source_traceback: traceback.StackSummary | None
) -> str:
if source_traceback is None:
return message
return f"{message}\nThe object was created at (most recent call last):\n{''.join(traceback.format_list(source_traceback)).rstrip()}"


CHAR = {chr(i) for i in range(0, 128)}
CTL = {chr(i) for i in range(0, 32)} | {
chr(127),
Expand Down
4 changes: 3 additions & 1 deletion tests/test_client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,10 +534,12 @@ async def test_del_debug(connector: BaseConnector) -> None:
logs = []
loop.set_exception_handler(lambda loop, ctx: logs.append(ctx))

with pytest.warns(ResourceWarning):
with pytest.warns(ResourceWarning) as cm:
del session
gc.collect()

warning_message = str(cm[0].message)
assert "The object was created at" in warning_message
assert len(logs) == 1
expected = {
"client_session": mock.ANY,
Expand Down
4 changes: 3 additions & 1 deletion tests/test_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,12 @@ async def test_connection_del_loop_debug() -> None:
exc_handler = mock.Mock()
loop.set_exception_handler(exc_handler)

with pytest.warns(ResourceWarning):
with pytest.warns(ResourceWarning) as cm:
del conn
gc.collect()

warning_message = str(cm[0].message)
assert "The object was created at" in warning_message
msg = {
"message": mock.ANY,
"client_connection": mock.ANY,
Expand Down
Loading