Skip to content

[3.15] gh-160310: Fix quadratic one-shot decompress() with many members (GH-160311) - #161037

Merged
gpshead merged 1 commit into
python:3.15from
gpshead:backport-e80962a-3.15
Oct 31, 2026
Merged

gpshead merged 1 commit into
python:3.15from
gpshead:backport-e80962a-3.15

Conversation

@gpshead

@gpshead gpshead commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

gzip.decompress(), bz2.decompress(), lzma.decompress() and
compression.zstd.decompress() handed each new decompressor all of the
remaining input and copied the rest back through unused_data, so input
made of many concatenated members took quadratic time. Each member is
now fed in chunks that start at 1 KiB and double, and the loop steps
back over unused_data by offset. The first chunk is the whole input,
so single-member data still takes one decompressor call.

bz2, lzma and zstd share the loop through a private helper in
compression._common._streams; gzip keeps its own because it parses the
member header, trailer and padding itself.

ANT-2026-9R8SO5S2

(cherry picked from commit e80962a)

Manual backport notes (3.15): the cherry-pick conflicted in Lib/bz2.py and Lib/compression/zstd/init.py, only on surrounding context that exists on main but not on 3.15: the module-level getattr at the end of bz2.py and the extra names main imports from _zstd. Each conflict was resolved by keeping 3.15's own code and adding only what the original commit adds or changes. The rest applied cleanly, and test_gzip, test_bz2, test_lzma, test_zstd, test_tarfile, test_shutil, test_zipfile and test_zipimport pass.

…ny members (pythonGH-160311)

gzip.decompress(), bz2.decompress(), lzma.decompress() and
compression.zstd.decompress() handed each new decompressor all of the
remaining input and copied the rest back through unused_data, so input
made of many concatenated members took quadratic time.  Each member is
now fed in chunks that start at 1 KiB and double, and the loop steps
back over unused_data by offset.  The first chunk is the whole input,
so single-member data still takes one decompressor call.

bz2, lzma and zstd share the loop through a private helper in
compression._common._streams; gzip keeps its own because it parses the
member header, trailer and padding itself.

ANT-2026-9R8SO5S2

(cherry picked from commit e80962a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant