Skip to content
Merged
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
33 changes: 33 additions & 0 deletions tests/test_functionality.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,39 @@ def a():
fsa = utils.find_stat_by_name(stats, 'a')
self.assertEqual(fsa.ncall, 1)

def test_clear_stats_does_not_leak_pit_name_refs(self):
# https://github.com/sumerc/yappi/issues/220
# _del_pit() must release pit->name / pit->modname. We profile a code
# object carrying runtime-built (non-interned, mortal) name strings —
# compile-interned strings are immortal on 3.12+ and hide the leak —
# and assert their refcounts do not grow across profile/clear cycles.
import types

def target():
return 42

# built at runtime: identifier-like literals would be compile-interned
code = target.__code__.replace(
co_name='leaky_name_' + '2f8a1c' * 4,
co_filename='leaky_file_' + '2f8a1c' * 4 + '.py',
)
fn = types.FunctionType(code, target.__globals__)

def cycle(n):
for _ in range(n):
yappi.start()
fn()
yappi.stop()
yappi.clear_stats()

cycle(3) # warmup any one-time caches
base_name = sys.getrefcount(code.co_name)
base_modname = sys.getrefcount(code.co_filename)
N = 10
cycle(N)
self.assertEqual(sys.getrefcount(code.co_name), base_name)
self.assertEqual(sys.getrefcount(code.co_filename), base_modname)

def test_generator(self):

def _gen(n):
Expand Down
5 changes: 4 additions & 1 deletion yappi/_yappi.c
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ _thread2ctx(PyThreadState *ts)
}

// the pit will be cleared by the relevant freelist. we do not free it here.
// we only DECREF the CodeObject or the MethodDescriptive string.
// we only DECREF the Python objects the pit owns (fn_descriptor, name, modname).
static void
_del_pit(_pit *pit)
{
Expand All @@ -522,6 +522,9 @@ _del_pit(_pit *pit)
it = next;
}
pit->children = NULL;
// name/modname can be NULL if their creation failed in _code2pit/_ccode2pit
Py_XDECREF(pit->name);
Py_XDECREF(pit->modname);
Py_DECREF(pit->fn_descriptor);
}

Expand Down
Loading