fix: reject pickling writable ZipStore - #4168
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4168 +/- ##
==========================================
+ Coverage 93.99% 94.23% +0.23%
==========================================
Files 91 92 +1
Lines 12795 12882 +87
==========================================
+ Hits 12027 12139 +112
+ Misses 768 743 -25
🚀 New features to boost your workflow:
|
…le-guard # Conflicts: # src/zarr/storage/_zip.py
…o codex/zipstore-pickle-guard
|
I'm not quite sure I understand what gets pickled here. What if the file changes between when it is pickled and unpickled? |
|
Thanks, that is the important distinction. The ZIP data itself is not pickled. Therefore, if the archive changes between pickling and unpickling, the restored read-only store sees the archive as it exists at unpickle time. If the path was removed or no longer contains a valid ZIP, opening or reading will fail. This is path-reference semantics, not snapshot isolation. I verified that replacing a valid archive between those operations makes the restored store read the replacement contents. That behavior is non-destructive for I can add an explicit documentation sentence or regression test for the current-file semantics if you think it should be part of the contract. |
|
Documentation and a regression test would be appreciated. |
|
Added in
Local verification:
The updated PR CI is now running. |
|
Hi @mkitti, following up after adding the requested documentation and regression test in |
|
I've asked @d-v-b to conduct final review. |
There was a problem hiding this comment.
🟡 Changes recommended
The critical append-mode, read-only regression case is not tested.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Prevents writable ZipStore instances from being pickled, avoiding ZIP corruption when reopened across processes.
Changes:
- Rejects pickling in archive-writing modes.
- Preserves reader pickling and fixes restored-store testing.
- Adds tests, documentation, and release notes.
File summaries
| File | Review |
|---|---|
tests/test_store/test_zip.py |
Missing coverage for mode="a", read_only=True; add this regression case. |
src/zarr/testing/store.py |
Correctly tests the restored store instance. |
src/zarr/storage/_zip.py |
Enforces safe pickling modes. |
docs/user-guide/performance.md |
Documents pickling constraints. |
changes/4168.bugfix.md |
Records the bug fix. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| store.close() | ||
| assert store.path is not None | ||
| with zipfile.ZipFile(store.path, mode="r") as archive: | ||
| assert archive.read("sentinel") == data.to_bytes() |
There was a problem hiding this comment.
Addressed in 83181c1f. The new regression test opens an existing archive with mode="a", read_only=True, asserts both pieces of state, and verifies that pickle.dumps raises. It therefore fails if the guard regresses to checking read_only instead of _zmode. Verification: 84 passed, 18 skipped for tests/test_store/test_zip.py; Ruff check, Ruff format check, and git diff --check pass.
Documentation build overview
25 files changed ·
|
Documentation build overview
10 files changed ·
|
Summary
Pickling a
ZipStoredoesn't serialize the archive; it serializes the parameters to reopen it. So pickling is safe exactly when reopening is safe.For readers (
mode="r"), it is: many independent processes can open the same path and just read. Round-trips fine.For writers, it isn't. A ZIP has one central directory written at close. Fan out several unpickled writers to one path and each rewrites that directory on close; last close wins, the rest is corruption. Since cross-process fan-out is why you pickle at all, the writer pickle path leads straight into the one thing ZIP can't survive. Rejecting at
__getstate__fails fast instead.Closes #3516.
For reviewers
Keying on
_zmode != "r"(notread_only) is right: an append-mode store can beread_only=Trueyet still rewrite its directory on reopen. Only"r"reopens harmlessly.Author attestation
TODO
docs/user-guide/*.mdchanges/