From 6afc0cc2ed240dbb818d6fc1e306955fa5170796 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 07:11:51 +0000 Subject: [PATCH 1/3] Initial plan From ffc1b206db400317d4ebc1e06306f4d23f265eb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 07:20:54 +0000 Subject: [PATCH 2/3] Fix Unpacker __init__ re-entry cleanup leaks --- msgpack/_unpacker.pyx | 7 +++++++ test/test_unpack.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index 40d12291..85969042 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -319,6 +319,7 @@ cdef class Unpacker: def __cinit__(self): self.buf = NULL + unpack_init(&self.ctx) def __dealloc__(self): unpack_clear(&self.ctx) @@ -338,6 +339,12 @@ cdef class Unpacker: Py_ssize_t max_ext_len=-1): cdef const char *cerr=NULL + unpack_clear(&self.ctx) + unpack_init(&self.ctx) + if self.buf != NULL: + PyMem_Free(self.buf) + self.buf = NULL + self.object_hook = object_hook self.object_pairs_hook = object_pairs_hook self.list_hook = list_hook diff --git a/test/test_unpack.py b/test/test_unpack.py index b17c3c53..705c16a6 100644 --- a/test/test_unpack.py +++ b/test/test_unpack.py @@ -1,4 +1,6 @@ +import gc import sys +import weakref from io import BytesIO from pytest import mark, raises @@ -87,3 +89,37 @@ def test_unpacker_tell_read_bytes(): assert obj == unp assert pos == unpacker.tell() assert unpacker.read_bytes(n) == raw + + +@mark.skipif( + Unpacker.__module__ == "msgpack.fallback", + reason="specific to C extension reinit leak", +) +def test_unpacker_reinit_clears_partial_state(): + refs = [] + + class Marker: + pass + + def hook(code, data): + obj = Marker() + refs.append(weakref.ref(obj)) + return obj + + unpacker = Unpacker(ext_hook=hook, strict_map_key=False) + # Keep parser state mid-map with a live key object from ext_hook. + # Encodes: [ {ExtType(1, b"a"): } ]. + unpacker.feed(b"\x91\x81\xd4\x01a") + with raises(OutOfData): + unpacker.unpack() + assert len(refs) == 1 + assert refs[0]() is not None + + unpacker.__init__() + gc.collect() + assert refs[0]() is None + with raises(OutOfData): + unpacker.unpack() + + unpacker.feed(packb({"a": 1})) + assert unpacker.unpack() == {"a": 1} From 23da5146a44848587eee11b8159b251b5ecf3951 Mon Sep 17 00:00:00 2001 From: Inada Naoki Date: Wed, 3 Jun 2026 16:35:40 +0900 Subject: [PATCH 3/3] Apply suggestion from @methane --- msgpack/_unpacker.pyx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/msgpack/_unpacker.pyx b/msgpack/_unpacker.pyx index 85969042..4bfbe064 100644 --- a/msgpack/_unpacker.pyx +++ b/msgpack/_unpacker.pyx @@ -317,10 +317,6 @@ cdef class Unpacker: cdef Py_ssize_t max_buffer_size cdef uint64_t stream_offset - def __cinit__(self): - self.buf = NULL - unpack_init(&self.ctx) - def __dealloc__(self): unpack_clear(&self.ctx) PyMem_Free(self.buf)