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

Reply via email to