Skip to content

refactor(store): polish the LocalStore rename retry from #4358 - #4359

Merged
d-v-b merged 1 commit into
zarr-developers:mainfrom
d-v-b:polish/local-store-retry
Sep 15, 2026
Merged

d-v-b merged 1 commit into
zarr-developers:mainfrom
d-v-b:polish/local-store-retry

Conversation

@d-v-b

@d-v-b d-v-b commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

AI-written polish on top of #4358. Fixes the changelog entry, removes some double backticks, removes a 0.0 delay, adds test improvements.

🤖 AI text below 🤖

Follow-up cleanup to #4358 (thanks @Tomatokeftes). No behaviour change on any platform; the retry set, attempt count, and delay schedule are identical.

What changed

_move_with_retry: dropped the leading 0.0 sentinel in _RETRY_DELAYS, the bare last_error: OSError annotation, and the post-loop raise last_error. The final attempt now runs after the loop and propagates its own traceback, which also removes a possibly-unbound path if the delay tuple were ever emptied.

Comment on ERROR_ACCESS_DENIED: the original said code 5 means "could not be superseded right now, as opposed to a permission problem that will not clear". MoveFileEx also returns 5 when the destination is an existing directory, a read-only file, or ACL-denied, so those cases go through the retry too and surface the same PermissionError after the bounded delay. The comment now says so. The measurement narrative moved out of the source; it lives in the #4358 description.

Changelog: the fragment said the retry "deliberately does not cover the exclusive path", but the code routes that path through the retry and only excludes FileExistsError (183). Reworded to match. Also renamed to 4358.bugfix.md per changes/README.md, and #3522 is now described as mitigated rather than fixed, since a second process holding the destination open past the retry budget still fails.

Tests: time.sleep is recorded via a sleeps fixture (the test_latency.py idiom) instead of sleeping for real, which cuts ~1.2 s per run and lets the tests assert the actual delay schedule. The closure test double is now a small callable class, removing four type: ignore[attr-defined]. Added test_atomic_write_onto_directory as a regression guard for the directory-destination case, checking both the error and that no .partial file is left behind.

Checks

  • pytest tests/test_store/test_local.py: 109 passed
  • ruff, mypy, codespell, numpydoc-validation, towncrier: pass

TODO

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/user-guide/*.md — n/a
  • Changes documented as a new file in changes/ (existing fragment edited)
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

🤖 Generated with Claude Code

…ers#4358

Follow-up cleanup to the retry landed in zarr-developers#4358, no behaviour change:

- drop the leading 0.0 sentinel, the bare `last_error` annotation, and
  the possibly-unbound re-raise from `_move_with_retry`; the final
  attempt now runs after the loop and propagates its own traceback
- correct the comment on ERROR_ACCESS_DENIED: Windows also reports it
  for destinations that will never clear (directory, read-only, ACL),
  so those surface the same error after the bounded delay
- align the changelog with the code: the exclusive path is routed
  through the retry, only FileExistsError is excluded; rename the
  fragment to the PR number and describe zarr-developers#3522 as mitigated, since a
  second process holding the destination open past the budget still
  fails
- tests record `time.sleep` instead of sleeping (~1.2 s per run before,
  now instant) and assert the actual delay schedule; the closure test
  double becomes a small callable class, removing four type-ignores
- add a regression test for `_atomic_write` onto an existing directory

Assisted-by: ClaudeCode:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.34%. Comparing base (6947dd8) to head (b1ddc75).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4359      +/-   ##
==========================================
- Coverage   94.34%   94.34%   -0.01%     
==========================================
  Files          92       92              
  Lines       12950    12948       -2     
==========================================
- Hits        12218    12216       -2     
  Misses        732      732              
Files with missing lines Coverage Δ
src/zarr/storage/_local.py 97.67% <100.00%> (-0.03%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@d-v-b
d-v-b marked this pull request as ready for review September 15, 2026 17:43
@d-v-b
d-v-b merged commit 4c61dcf into zarr-developers:main Sep 15, 2026
39 checks passed
d-v-b added a commit to d-v-b/zarr-python that referenced this pull request Sep 16, 2026
Resolves the import conflict in tests/test_store/test_local.py: main's
LocalStore rename-retry tests (zarr-developers#4358, zarr-developers#4359) import `time`, this branch's
event-loop detector imports `dataclass`/`field` and typing names. Both are
kept.

The retry helpers main added (`_move_with_retry`, `_atomic_write`) are only
reached from `_put`, which this branch already runs under `asyncio.to_thread`,
so their `time.sleep` blocks a worker thread rather than the event loop and the
detector stays quiet. Main's new tests are module-level, so the class-scoped
autouse detector does not apply to them.

Assisted-by: ClaudeCode:claude-opus-5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant