Skip to content

run_redline leaks a NamedTemporaryFile object, causing a ResourceWarning on Python 3.13+ #30

Description

@mauritium

Summary

BaseEngine.run_redline creates the output scratch file with

target_path = tempfile.NamedTemporaryFile(delete=False).name

This constructs a NamedTemporaryFile, takes its .name, and immediately drops the only reference to the file object. The object is never closed, so its finalizer runs while close_called is still false and CPython reports a ResourceWarning: Implicitly cleaning up <_TemporaryFileWrapper ...>.

One warning is emitted per call to run_redline. The file descriptor stays open until the garbage collector reaches the object.

_write_to_temp_file in the same class does this correctly — it keeps the object, writes, closes it, and then returns .name — so the fix is to make the two consistent.

Environment

  • python-redlines 0.3.0 (DocxodusEngine; XmlPowerToolsEngine shares the same BaseEngine.run_redline)
  • Python 3.14.2, macOS
  • Also present in the source on Python 3.12, but silent there: tempfile._TemporaryFileCloser.__del__ only began emitting this warning in a later CPython release. On 3.14 the message comes from tempfile.py:484.

Location

python_redlines/engines.py, line 153, in BaseEngine.run_redline:

        temp_files = []
        try:

            target_path = tempfile.NamedTemporaryFile(delete=False).name    # <-- here
            original_path = self._write_to_temp_file(original) if isinstance(original, bytes) else original
            modified_path = self._write_to_temp_file(modified) if isinstance(modified, bytes) else modified

For contrast, line 184 in the same class handles it properly:

    def _write_to_temp_file(self, data):
        temp_file = tempfile.NamedTemporaryFile(delete=False)
        temp_file.write(data)
        temp_file.close()
        return temp_file.name

Reproduction

import gc
import warnings

from python_redlines import DocxodusEngine

warnings.simplefilter("error", ResourceWarning)

engine = DocxodusEngine()
engine.run_redline("Author", original_bytes, modified_bytes)

gc.collect()   # ResourceWarning: Implicitly cleaning up <_TemporaryFileWrapper ...>

The leak happens before the subprocess is launched, so it occurs even when the comparison itself fails.

Why this is worth fixing rather than filtering

Under -W error or pytest's filterwarnings = ["error"], the warning becomes an exception. Because it is raised inside __del__, it surfaces as an unraisable exception, and pytest attributes it to whichever test happened to be running when the collector ran rather than the one that called run_redline. In our suite it produced five consecutive failures in unrelated tests, none of which touched the library, with a traceback that pointed only at tempfile. It took a while to trace back here.

The open descriptor also accumulates for as long as collection is deferred, which matters for a process comparing many documents in a loop.

Suggested fix

mkstemp is the right tool when only a path is wanted, since it returns a descriptor the caller is expected to close:

        fd, target_path = tempfile.mkstemp()
        os.close(fd)

os is already imported in the module, and run_redline already appends target_path to temp_files for its finally cleanup, so nothing else changes.

Alternatively, keep NamedTemporaryFile and close it explicitly, matching _write_to_temp_file:

        target_file = tempfile.NamedTemporaryFile(delete=False)
        target_file.close()
        target_path = target_file.name

Both silence the warning and close the descriptor at the point of creation. I've verified the mkstemp form leaves no warning under warnings.simplefilter("error", ResourceWarning) and that the path still exists for the subprocess to write to.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions