https://github.com/python/cpython/commit/500ea3b0ee04ee3589597dcbe6a2b9fb8251fc65 commit: 500ea3b0ee04ee3589597dcbe6a2b9fb8251fc65 branch: 3.12 author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com> committer: vstinner <vstin...@python.org> date: 2025-02-28T08:28:14Z summary:
[3.12] gh-129726: Break `gzip.GzipFile` reference loop (GH-130055) (#130670) gh-129726: Break `gzip.GzipFile` reference loop (GH-130055) A reference loop was resulting in the `fileobj` held by the `GzipFile` being closed before the `GzipFile`. The issue started with gh-89550 in 3.12, but was hidden in most cases until 3.13 when gh-62948 made it more visible. (cherry picked from commit 7f39137662f637518a74228286e7ec675fa4e27d) Co-authored-by: Cody Maloney <cmalo...@users.noreply.github.com> files: A Misc/NEWS.d/next/Library/2025-02-12-12-38-24.gh-issue-129726.jB0sxu.rst M Lib/gzip.py M Lib/test/test_gzip.py diff --git a/Lib/gzip.py b/Lib/gzip.py index 177f9080dc5af8..01a791e5824a0b 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -5,11 +5,15 @@ # based on Andrew Kuchling's minigzip.py distributed with the zlib module -import struct, sys, time, os -import zlib +import _compression import builtins import io -import _compression +import os +import struct +import sys +import time +import weakref +import zlib __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] @@ -124,10 +128,13 @@ class BadGzipFile(OSError): class _WriteBufferStream(io.RawIOBase): """Minimal object to pass WriteBuffer flushes into GzipFile""" def __init__(self, gzip_file): - self.gzip_file = gzip_file + self.gzip_file = weakref.ref(gzip_file) def write(self, data): - return self.gzip_file._write_raw(data) + gzip_file = self.gzip_file() + if gzip_file is None: + raise RuntimeError("lost gzip_file") + return gzip_file._write_raw(data) def seekable(self): return False diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py index d220c7d06e50c9..e0dc07cdadd035 100644 --- a/Lib/test/test_gzip.py +++ b/Lib/test/test_gzip.py @@ -3,12 +3,14 @@ import array import functools +import gc import io import os import struct import sys import unittest from subprocess import PIPE, Popen +from test.support import catch_unraisable_exception from test.support import import_helper from test.support import os_helper from test.support import _4G, bigmemtest, requires_subprocess @@ -836,6 +838,17 @@ def test_write_seek_write(self): self.assertEqual(gzip.decompress(data), message * 2) + def test_refloop_unraisable(self): + # Ensure a GzipFile referring to a temporary fileobj deletes cleanly. + # Previously an unraisable exception would occur on close because the + # fileobj would be closed before the GzipFile as the result of a + # reference loop. See issue gh-129726 + with catch_unraisable_exception() as cm: + gzip.GzipFile(fileobj=io.BytesIO(), mode="w") + gc.collect() + self.assertIsNone(cm.unraisable) + + class TestOpen(BaseTest): def test_binary_modes(self): uncompressed = data1 * 50 diff --git a/Misc/NEWS.d/next/Library/2025-02-12-12-38-24.gh-issue-129726.jB0sxu.rst b/Misc/NEWS.d/next/Library/2025-02-12-12-38-24.gh-issue-129726.jB0sxu.rst new file mode 100644 index 00000000000000..31032b59b5ea36 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-12-12-38-24.gh-issue-129726.jB0sxu.rst @@ -0,0 +1,3 @@ +Fix :class:`gzip.GzipFile` raising an unraisable exception during garbage +collection when referring to a temporary object by breaking the reference +loop with :mod:`weakref`. _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com