mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
[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:
parent
125ca02866
commit
ad97027e9b
3 changed files with 28 additions and 5 deletions
17
Lib/gzip.py
17
Lib/gzip.py
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`.
|
Loading…
Add table
Add a link
Reference in a new issue