Skip to content

Commit da68262

Browse files
committed
gh-145846: Fix memory leak in _lsprof clearEntries() context chain
clearEntries() only freed the top currentProfilerContext but did not walk the previous linked list. When clear() is called during active profiling with nested calls, all contexts except the top one were leaked. Fix by iterating the entire linked list, matching the existing freelistProfilerContext cleanup pattern.
1 parent d19de37 commit da68262

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

Lib/test/test_profiling/test_tracing_profiler.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,28 @@ def gen():
142142

143143
self.assertTrue(any("throw" in func[2] for func in pr.stats.keys())),
144144

145+
def test_clear_with_nested_calls(self):
146+
# Calling clear() during nested profiled calls should not leak
147+
# ProfilerContexts. clearEntries() must walk the entire linked list,
148+
# not just free the top context.
149+
import _lsprof
150+
151+
def level3(profiler):
152+
profiler.clear()
153+
154+
def level2(profiler):
155+
level3(profiler)
156+
157+
def level1(profiler):
158+
level2(profiler)
159+
160+
p = _lsprof.Profiler()
161+
p.enable()
162+
for _ in range(100):
163+
level1(p)
164+
p.disable()
165+
p.clear()
166+
145167
def test_bad_descriptor(self):
146168
# gh-132250
147169
# cProfile should not crash when the profiler callback fails to locate
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix memory leak in :mod:`_lsprof` when ``clear()`` is called during active
2+
profiling with nested calls. ``clearEntries()`` now walks the entire
3+
``currentProfilerContext`` linked list instead of only freeing the top context.

Modules/_lsprof.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,9 +292,10 @@ static void clearEntries(ProfilerObject *pObj)
292292
RotatingTree_Enum(pObj->profilerEntries, freeEntry, NULL);
293293
pObj->profilerEntries = EMPTY_ROTATING_TREE;
294294
/* release the memory hold by the ProfilerContexts */
295-
if (pObj->currentProfilerContext) {
296-
PyMem_Free(pObj->currentProfilerContext);
297-
pObj->currentProfilerContext = NULL;
295+
while (pObj->currentProfilerContext) {
296+
ProfilerContext *c = pObj->currentProfilerContext;
297+
pObj->currentProfilerContext = c->previous;
298+
PyMem_Free(c);
298299
}
299300
while (pObj->freelistProfilerContext) {
300301
ProfilerContext *c = pObj->freelistProfilerContext;

0 commit comments

Comments
 (0)