Skip to content

Commit 2d67d94

Browse files
committed
pythongh-129726: Break gzip.GzipFile reference loop
A reference loop was resulting in the `fileobj` held by the `GzipFile` being closed before the `GzipFile`. The issue started with pythongh-89550 in 3.12, but was hidden in most cases until 3.13 when pythongh-62948 made it more visible.
1 parent 1b27f36 commit 2d67d94

File tree

3 files changed

+19
-1
lines changed

3 files changed

+19
-1
lines changed

Lib/gzip.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import builtins
1111
import io
1212
import _compression
13+
import weakref
1314

1415
__all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"]
1516

@@ -226,7 +227,8 @@ def __init__(self, filename=None, mode=None,
226227
0)
227228
self._write_mtime = mtime
228229
self._buffer_size = _WRITE_BUFFER_SIZE
229-
self._buffer = io.BufferedWriter(_WriteBufferStream(self),
230+
write_wrap = _WriteBufferStream(weakref.proxy(self))
231+
self._buffer = io.BufferedWriter(write_wrap,
230232
buffer_size=self._buffer_size)
231233
else:
232234
raise ValueError("Invalid mode: {!r}".format(mode))

Lib/test/test_gzip.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
import array
55
import functools
6+
import gc
67
import io
78
import os
89
import struct
910
import sys
1011
import unittest
1112
from subprocess import PIPE, Popen
13+
from test.support import catch_unraisable_exception
1214
from test.support import import_helper
1315
from test.support import os_helper
1416
from test.support import _4G, bigmemtest, requires_subprocess
@@ -859,6 +861,17 @@ def test_write_seek_write(self):
859861
self.assertEqual(gzip.decompress(data), message * 2)
860862

861863

864+
def test_refloop_unraisable(self):
865+
# Ensure a GzipFile referring to a temporary fileobj deletes cleanly.
866+
# Previously an unraisable exception would occur on close because the
867+
# fileobj would be closed before the GzipFile as the result of a
868+
# reference loop. See issue gh-129726
869+
with catch_unraisable_exception() as cm:
870+
gzip.GzipFile(fileobj=io.BytesIO(), mode="w")
871+
gc.collect()
872+
self.assertIsNone(cm.unraisable)
873+
874+
862875
class TestOpen(BaseTest):
863876
def test_binary_modes(self):
864877
uncompressed = data1 * 50
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`gzip.GzipFile` raising an unraisable exception during garbage
2+
collection when referring to a temporary object by breaking the reference
3+
loop with :mod:`weakref`.

0 commit comments

Comments
 (0)