CFFI backend: crash when ZstdDecompressor instances share a ZstdCompressionDict
维护者通常 10 天内回复
评估
调研方向
Start with ZstdCompressionDict._ddict in zstandard/backend_cffi.py and _ensure_dctx() in the same file, then compare how the C backend retains its DDict in c-ext/compressiondict.c. Run the reproducer with PYTHON_ZSTANDARD_IMPORT_POLICY=cffi and check for a regression test covering multiple decompressors sharing one dictionary. Done means repeated and concurrent use no longer crashes or raises a dictionary mismatch; the reporter says they plan to open a PR.
由索引模型根据 Issue 内容生成。
描述
With the CFFI backend, using one ZstdCompressionDict with more than one ZstdDecompressor can crash the interpreter, or fail with ZstdError: zstd decompress error: Dictionary mismatch. The C backend isn't affected.
This reproducer doesn't need threads:
import zstandard as zstd
samples = [b"sample %d " % i + b"data " * (i % 50) for i in range(1000)]
dict_data = zstd.train_dictionary(8192, samples)
data = b"".join(samples)
frame = zstd.ZstdCompressor(dict_data=dict_data).compress(data)
reader = zstd.ZstdDecompressor(dict_data=dict_data).stream_reader(frame)
zstd.ZstdDecompressor(dict_data=dict_data) # another decompressor, same dictionary
assert reader.read() == data
With PYTHON_ZSTANDARD_IMPORT_POLICY=cffi, every run either segfaulted or raised ZstdError: zstd decompress error: Dictionary mismatch. I ran it with:
- zstandard 0.25.0 (PyPI wheel) on CPython 3.14.8;
- main (a2bb3c0) on CPython 3.15.0 and 3.15.0t.
With PYTHON_ZSTANDARD_IMPORT_POLICY=cext, it passes on all three. I also ran a stress test where 8 threads each create their own decompressors for one shared dictionary. With the CFFI backend it segfaults within a few hundred operations, both with and without the GIL.
Cause. ZstdCompressionDict._ddict in zstandard/backend_cffi.py (lines 2748–2766) is meant to create the DDict once and cache it in self.__dict__["_ddict"]. But property is a data descriptor, so it takes precedence over the instance __dict__ and the cached value is never read.
- Every access creates a new DDict and stores it. That drops the previous DDict, and its
ffi.gcdestructor frees it. ZstdDecompressor._ensure_dctx()runs when a decompressor is created and at the start of every operation. It passesself._dict_data._ddicttoZSTD_DCtx_refDDict()(line 4473), which only keeps a pointer.- So creating or using a second decompressor with the same dictionary frees the DDict that the first decompressor's context still points to.
The C backend creates the DDict once in ensure_ddict() (c-ext/compressiondict.c:148) and keeps it as long as the dictionary. The CFFI code has been like this since bef32a9 (2018), whose commit message says a DDict "can be used by multiple decompressors".
I have a fix that creates the DDict once, under a lock so that concurrent first uses don't create two, plus a regression test. I'll open a PR.
Prepared with the help of Claude Code.
- 主要语言
- C
- 星标
- 642
- 派生
- 118
- 平均合并
- 9 天 3 小时
- 30 天内合并 PR
- 6
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
indygreg/python-zstandard 的其他 Issue
-
`multi_decompress_to_buffer([])` terminates the process with SIGFPE可能已有人在做 @mikamikasuki 于 17 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
indygreg/python-zstandard#335 ·
维护者通常 10 天内回复
-
Silent data-correctness bug: `readinto()` / `readinto1()` on `stream_reader` return `tell() == 0` after successful reads可能已有人在做 @CAOShurong 于 10 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 76/100
indygreg/python-zstandard#295 ·
维护者通常 10 天内回复
-
Python 3.15 wheels可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 55/100
indygreg/python-zstandard#354 · 1 个 reaction ·
维护者通常 10 天内回复
-
`estimated_compression_context_size()` raises SIGFPE with `enable_ldm=True可能已有人在做 @yousefissa 于 4 天前认领。 未关闭
难度 3/5 1-2 天 新手友好度 64/100
indygreg/python-zstandard#345 ·
维护者通常 10 天内回复
-
难度 2/5 1-3 小时 新手友好度 55/100
indygreg/python-zstandard#334 ·
维护者通常 10 天内回复
查看 indygreg/python-zstandard 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
micropython/micropython#19786 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 84/100
vercel-labs/zerolang#442 ·
-
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
triage
难度 2/5 1-3 小时 新手友好度 67/100
维护者通常 1 天内回复
-
mewencode: base256[] and base128[] are indexed out of bounds by a non-ASCII octet可能已有人在做 @kazu-yamamoto 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
kazu-yamamoto/Mew#276 ·
维护者通常 1 天内回复