[3.13] gh-129726: Break gzip.GzipFile reference loop (GH-130055) (#130669)

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 7f39137662)

Co-authored-by: Cody Maloney <cmaloney@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2025-02-28 09:32:44 +01:00 committed by GitHub
parent 125ca02866
commit ad97027e9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 28 additions and 5 deletions

View file

@ -5,11 +5,15 @@ but random access is not allowed."""
# 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"]
@ -125,10 +129,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

View file

@ -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
@ -848,6 +850,17 @@ class TestGzip(BaseTest):
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

View file

@ -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`.