Skip to content

Fix reference leaks in Automaton pickling (leaked whole dump per pickle.dumps)#220

Open
vmax wants to merge 1 commit into
WojciechMula:masterfrom
vmax:fix-pickle-refcount-leaks
Open

Fix reference leaks in Automaton pickling (leaked whole dump per pickle.dumps)#220
vmax wants to merge 1 commit into
WojciechMula:masterfrom
vmax:fix-pickle-refcount-leaks

Conversation

@vmax

@vmax vmax commented Jul 5, 2026

Copy link
Copy Markdown

Fixes #219.

Two missing Py_DECREFs leaked approximately the full serialized size of the automaton on every successful pickle.dumps() (~1.7 MiB per call for a 3000-word automaton; see the issue for a standalone repro).

src/pickle/pickle_data.cpickle_data__add_next_buffer()
PyList_Append() takes its own reference to the chunk; the reference owned after PyBytes_FromStringAndSize() was only released on the append-failure path. Now it is also released after a successful append — the list keeps the chunk alive. This was the bulk of the leak (the chunk buffers hold the entire node dump).

src/Automaton_pickle.cautomaton___reduce__()
Py_BuildValue with the "O" format takes new references to bytes_list and values, but the success path returned without releasing the function's own references (pickle_data__cleanup() was only reachable via the error label). Now cleanup runs on the success path too. The data.values = NULL special-case for Py_None is gone: the owned Py_None reference is released by cleanup like any other value.

Verification:

  • Repro loop from the issue: RSS flat at 24 MiB after 600 dumps (previously 1020 MiB and climbing linearly).
  • Round-trip dumps/loads produces identical iter() results, including the chunked multi-buffer path (tested with a 235 MiB pickle).
  • pytest tests/: 149 passed, 8 skipped.

Two missing Py_DECREFs in the pickling path leaked roughly the full
serialized size of the automaton on every successful pickle.dumps():

1. pickle_data__add_next_buffer(): PyList_Append() takes its own
   reference to the chunk, but the reference owned after
   PyBytes_FromStringAndSize() was never released, so every chunk
   buffer (the entire node dump) stayed alive forever.

2. automaton___reduce__(): Py_BuildValue with the "O" format takes new
   references to bytes_list and values, but the function returned
   without releasing its own references; pickle_data__cleanup() was
   only ever called on the error path. This leaked the list objects
   and, combined with (1), pinned all chunk buffers.

Repro: pickling a 3000-word automaton in a loop leaked ~1.7 MiB per
iteration (1 GiB RSS after 600 dumps). With this change RSS stays flat.
Unpickling was unaffected. Existing tests pass (149 passed, 8 skipped).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pickle.dumps(Automaton) leaks the entire serialized dump on every call

1 participant