Skip to content

Commit e7c0d74

Browse files
authored
fix: wrapping context __exit__ leak (#13626)
We fix a leak in the universal wrapping context caused to bound __exit__ method leaking in CPython 3.9 and 3.10. This was due to a reference to the bound method __exit__ of the universal wrapping context not getting popped from the stack before every return statement. Every call to a wrapped function would then create one such bounded method object that would not get GC'd. No other versions of CPython are affected because the bytecode and/or approach are different. ## Checklist - [ ] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent c02a068 commit e7c0d74

File tree

3 files changed

+30
-0
lines changed

3 files changed

+30
-0
lines changed

ddtrace/internal/wrapping/context.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,14 @@
230230

231231
CONTEXT_RETURN.parse(
232232
r"""
233+
pop_block
233234
load_const {context}
234235
load_method $__return__
235236
rot_three
236237
rot_three
237238
call_method 1
239+
rot_two
240+
pop_top
238241
"""
239242
)
240243

@@ -258,11 +261,14 @@
258261

259262
CONTEXT_RETURN.parse(
260263
r"""
264+
pop_block
261265
load_const {context}
262266
load_method $__return__
263267
rot_three
264268
rot_three
265269
call_method 1
270+
rot_two
271+
pop_top
266272
"""
267273
)
268274

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
code origin: fixed a potential memory leak when collecting entry span
5+
location information.

tests/internal/test_wrapping.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from contextlib import asynccontextmanager
3+
import gc
34
import inspect
45
import sys
56
from types import CoroutineType
@@ -805,3 +806,21 @@ async def fibonacci(n):
805806
await asyncio.gather(*[fibonacci(n) for n in range(1, N)])
806807

807808
assert set(values) == {(n, n) for n in range(0, N)}
809+
810+
811+
def test_wrapping_context_method_leaks():
812+
def foo():
813+
return 42
814+
815+
wc = DummyWrappingContext(foo)
816+
wc.wrap()
817+
818+
method_count = len([_ for _ in gc.get_objects() if type(_).__name__ == "method"])
819+
820+
for _ in range(10000):
821+
foo()
822+
823+
gc.collect()
824+
825+
new_method_count = len([_ for _ in gc.get_objects() if type(_).__name__ == "method"])
826+
assert new_method_count <= method_count + 1

0 commit comments

Comments
 (0)