Description of the bug
Description
The extractBLOCKS code path leaks a Python object for every text, image, and vector block on every call. Since extractBLOCKS (a.k.a. page.get_text("blocks")) is commonly called once per page in bulk-processing loops, the leak accumulates quickly across large/multi-page documents.
Location
src/extra.i, in the recursive helper _as_blocks (around line 3501).
Root cause
The block tuple is populated like this:
PyObject *litem = PyTuple_New(7);
...
PyTuple_SET_ITEM(litem, 4, Py_BuildValue("O", text)); // <-- leak
...
text is created as a new reference, via either:
JM_EscapeStrFromBuffer(res) (text blocks), or
PyUnicode_FromFormat(...) (image / vector blocks).
The "O" format code in Py_BuildValue increments the refcount of text and returns a separate new object. That separate object is the one stolen into the tuple by PyTuple_SET_ITEM. The original text reference is then discarded on the next iteration:
…without ever being decref'd. Net effect: text is leaked once per block.
The other six PyTuple_SET_ITEM calls in the same block are correct — Py_BuildValue("f"/"i", ...) creates a fresh object the tuple legitimately owns.
Fix
Hand the original text reference to the tuple instead of creating an extra one. Since PyTuple_SET_ITEM steals a reference, set it directly:
PyTuple_SET_ITEM(litem, 4, text);
(Equivalently, Py_BuildValue("N", text), where "N" steals rather than increments.)
Environment
- PyMuPDF:
<fill in version, e.g. output of fitz.__doc__>
- OS / Python:
<fill in>
How to reproduce the bug
Reproduction
import pymupdf, os, psutil
doc = pymupdf.open("some_multipage.pdf")
p = psutil.Process(os.getpid())
for i in range(200):
for page in doc:
page.get_text("blocks")
rss_mb = p.memory_info().rss / (1024 * 1024) # Convert to MiB
print(f"Iteration {i + 1}: {rss_mb:.2f} MiB") # grows steadily
PyMuPDF version
1.27.2.2
Operating system
Linux
Python version
3.12
Description of the bug
Description
The
extractBLOCKScode path leaks a Python object for every text, image, and vector block on every call. SinceextractBLOCKS(a.k.a.page.get_text("blocks")) is commonly called once per page in bulk-processing loops, the leak accumulates quickly across large/multi-page documents.Location
src/extra.i, in the recursive helper_as_blocks(around line 3501).Root cause
The block tuple is populated like this:
textis created as a new reference, via either:JM_EscapeStrFromBuffer(res)(text blocks), orPyUnicode_FromFormat(...)(image / vector blocks).The
"O"format code inPy_BuildValueincrements the refcount oftextand returns a separate new object. That separate object is the one stolen into the tuple byPyTuple_SET_ITEM. The originaltextreference is then discarded on the next iteration:…without ever being decref'd. Net effect:
textis leaked once per block.The other six
PyTuple_SET_ITEMcalls in the same block are correct —Py_BuildValue("f"/"i", ...)creates a fresh object the tuple legitimately owns.Fix
Hand the original
textreference to the tuple instead of creating an extra one. SincePyTuple_SET_ITEMsteals a reference, set it directly:(Equivalently,
Py_BuildValue("N", text), where"N"steals rather than increments.)Environment
<fill in version, e.g. output of fitz.__doc__><fill in>How to reproduce the bug
Reproduction
PyMuPDF version
1.27.2.2
Operating system
Linux
Python version
3.12