Skip to content

Commit 696ae52

Browse files
[PR #11054/e2eb1959 backport][3.12] Fix CookieJar memory leak in filter_cookies() (#11068)
Co-authored-by: J. Nick Koston <[email protected]> Fixes #11052 memory leak issue
1 parent 872cab6 commit 696ae52

File tree

4 files changed

+59
-0
lines changed

4 files changed

+59
-0
lines changed

CHANGES/11052.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed memory leak in :py:meth:`~aiohttp.CookieJar.filter_cookies` that caused unbounded memory growth
2+
when making requests to different URL paths -- by :user:`bdraco` and :user:`Cycloctane`.

CHANGES/11054.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
11052.bugfix.rst

aiohttp/cookiejar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ def filter_cookies(self, request_url: URL = URL()) -> "BaseCookie[str]":
353353
path_len = len(request_url.path)
354354
# Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4
355355
for p in pairs:
356+
if p not in self._cookies:
357+
continue
356358
for name, cookie in self._cookies[p].items():
357359
domain = cookie["domain"]
358360

tests/test_cookiejar.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,3 +1127,57 @@ async def test_treat_as_secure_origin() -> None:
11271127
assert len(jar) == 1
11281128
filtered_cookies = jar.filter_cookies(request_url=endpoint)
11291129
assert len(filtered_cookies) == 1
1130+
1131+
1132+
async def test_filter_cookies_does_not_leak_memory() -> None:
1133+
"""Test that filter_cookies doesn't create empty cookie entries.
1134+
1135+
Regression test for https://github.com/aio-libs/aiohttp/issues/11052
1136+
"""
1137+
jar = CookieJar()
1138+
1139+
# Set a cookie with Path=/
1140+
jar.update_cookies({"test_cookie": "value; Path=/"}, URL("http://example.com/"))
1141+
1142+
# Check initial state
1143+
assert len(jar) == 1
1144+
initial_storage_size = len(jar._cookies)
1145+
initial_morsel_cache_size = len(jar._morsel_cache)
1146+
1147+
# Make multiple requests with different paths
1148+
paths = [
1149+
"/",
1150+
"/api",
1151+
"/api/v1",
1152+
"/api/v1/users",
1153+
"/api/v1/users/123",
1154+
"/static/css/style.css",
1155+
"/images/logo.png",
1156+
]
1157+
1158+
for path in paths:
1159+
url = URL(f"http://example.com{path}")
1160+
filtered = jar.filter_cookies(url)
1161+
# Should still get the cookie
1162+
assert len(filtered) == 1
1163+
assert "test_cookie" in filtered
1164+
1165+
# Storage size should not grow significantly
1166+
# Only the shared cookie entry ('', '') may be added
1167+
final_storage_size = len(jar._cookies)
1168+
assert final_storage_size <= initial_storage_size + 1
1169+
1170+
# Verify _morsel_cache doesn't leak either
1171+
# It should only have entries for domains/paths where cookies exist
1172+
final_morsel_cache_size = len(jar._morsel_cache)
1173+
assert final_morsel_cache_size <= initial_morsel_cache_size + 1
1174+
1175+
# Verify no empty entries were created for domain-path combinations
1176+
for key, cookies in jar._cookies.items():
1177+
if key != ("", ""): # Skip the shared cookie entry
1178+
assert len(cookies) > 0, f"Empty cookie entry found for {key}"
1179+
1180+
# Verify _morsel_cache entries correspond to actual cookies
1181+
for key, morsels in jar._morsel_cache.items():
1182+
assert key in jar._cookies, f"Orphaned morsel cache entry for {key}"
1183+
assert len(morsels) > 0, f"Empty morsel cache entry found for {key}"

0 commit comments

Comments
 (0)