diff --git a/msgspec/_core.c b/msgspec/_core.c index 0f287814..20f32516 100644 --- a/msgspec/_core.c +++ b/msgspec/_core.c @@ -1513,7 +1513,9 @@ Raw_copy(Raw *self, PyObject *unused) } PyObject *buf = PyBytes_FromStringAndSize(self->buf, self->len); if (buf == NULL) return NULL; - return Raw_New(buf); + PyObject *out = Raw_New(buf); + Py_DECREF(buf); + return out; } static PyMethodDef Raw_methods[] = { diff --git a/tests/test_raw.py b/tests/test_raw.py index f37f0e43..5aa86f91 100644 --- a/tests/test_raw.py +++ b/tests/test_raw.py @@ -1,5 +1,7 @@ import operator +import subprocess import sys +import textwrap import weakref import pytest @@ -69,6 +71,29 @@ def test_raw_copy(): assert ref() is None +def test_raw_copy_doesnt_leak(): + """See https://github.com/jcrist/msgspec/pull/709""" + script = textwrap.dedent( + """ + import msgspec + import tracemalloc + + tracemalloc.start() + + raw = msgspec.Raw(bytearray(1000)) + for _ in range(10000): + raw.copy() + + _, peak = tracemalloc.get_traced_memory() + print(peak) + """ + ) + + output = subprocess.check_output([sys.executable, "-c", script]) + peak = int(output.decode().strip()) + assert peak < 10_000 # should really be ~2000 + + def test_raw_pickle_bytes(): orig_buffer = b"test" r = msgspec.Raw(orig_buffer)