Bug report
Bug description:
Bug report
Bug description
In Lib/tarfile.py, the four compressed-archive open classmethods all share
the same structure: open an underlying compressed file object, then wrap it
with cls.taropen(...). If taropen fails, they catch the exception, close
the just-opened fileobj, and re-raise.
Three of them — gzopen , bz2open , xzopen — use a bare except: for the final cleanup branch. zstopen
uses except Exception: (line 2099) instead:
# Lib/tarfile.py — gzopen / bz2open / xzopen (consistent across all three)
except:
fileobj.close()
raise
# Lib/tarfile.py — zstopen (the outlier)
except Exception:
fileobj.close()
raise
A bare except: is equivalent to except BaseException: and catches
KeyboardInterrupt, SystemExit, and asyncio.CancelledError (which is a
BaseException subclass since 3.8). except Exception: does not. As a
result, if any of those exceptions is raised during the cls.taropen(...)
call inside zstopen — e.g. the user hits Ctrl+C, the surrounding async task
is cancelled, or the process is shutting down — fileobj.close() is never
called and the file descriptor held by ZstdFile is leaked.
The same scenario is handled correctly by gzopen/bz2open/xzopen.
This is the classic "catch-and-cleanup-and-rethrow" pattern: the bare
except: is intentional here, because the only purpose of the handler is to
release the resource before letting the exception propagate. The exception
is not swallowed.
Reproducer
import unittest.mock, tarfile
fileobj = unittest.mock.Mock()
with unittest.mock.patch("compression.zstd.ZstdFile", return_value=fileobj), \
unittest.mock.patch.object(tarfile.TarFile, "taropen",
side_effect=KeyboardInterrupt):
try:
tarfile.TarFile.zstopen("foo.tar.zst")
except KeyboardInterrupt:
pass
print("close called:", fileobj.close.called)
Actual output on current main:
Expected (matching gzopen/bz2open/xzopen behavior):
Replacing "compression.zstd.ZstdFile" with "gzip.GzipFile" (and
zstopen with gzopen) prints True, confirming the inconsistency.
Suggested fix
In Lib/tarfile.py:2099, change except Exception: to bare except: (or
the equivalent except BaseException:), matching the existing convention in
gzopen/bz2open/xzopen:
- except Exception:
+ except:
fileobj.close()
raise
A parameterized regression test across all four compressed-open methods
should be added to Lib/test/test_tarfile.py so that any future compressed
format added to tarfile is held to the same cleanup contract.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Windows
Linked PRs
Bug report
Bug description:
Bug report
Bug description
In
Lib/tarfile.py, the four compressed-archive open classmethods all sharethe same structure: open an underlying compressed file object, then wrap it
with
cls.taropen(...). Iftaropenfails, they catch the exception, closethe just-opened
fileobj, and re-raise.Three of them —
gzopen,bz2open,xzopen— use a bareexcept:for the final cleanup branch.zstopenuses
except Exception:(line 2099) instead:A bare
except:is equivalent toexcept BaseException:and catchesKeyboardInterrupt,SystemExit, andasyncio.CancelledError(which is aBaseExceptionsubclass since 3.8).except Exception:does not. As aresult, if any of those exceptions is raised during the
cls.taropen(...)call inside
zstopen— e.g. the user hits Ctrl+C, the surrounding async taskis cancelled, or the process is shutting down —
fileobj.close()is nevercalled and the file descriptor held by
ZstdFileis leaked.The same scenario is handled correctly by
gzopen/bz2open/xzopen.This is the classic "catch-and-cleanup-and-rethrow" pattern: the bare
except:is intentional here, because the only purpose of the handler is torelease the resource before letting the exception propagate. The exception
is not swallowed.
Reproducer
Actual output on current
main:Expected (matching gzopen/bz2open/xzopen behavior):
Replacing
"compression.zstd.ZstdFile"with"gzip.GzipFile"(andzstopenwithgzopen) printsTrue, confirming the inconsistency.Suggested fix
In
Lib/tarfile.py:2099, changeexcept Exception:to bareexcept:(orthe equivalent
except BaseException:), matching the existing convention ingzopen/bz2open/xzopen:A parameterized regression test across all four compressed-open methods
should be added to
Lib/test/test_tarfile.pyso that any future compressedformat added to
tarfileis held to the same cleanup contract.CPython versions tested on:
CPython main branch
Operating systems tested on:
Windows
Linked PRs
tarfile.TarFile.zstopenonBaseException#150080