fix(utils): retry transient atomic replace permission errors - #36921
fix(utils): retry transient atomic replace permission errors#36921zapabob wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real Windows atomic-write failure mode. Current main still performs a single os.replace at utils.py:115, while its handler only recognizes EXDEV/EBUSY at utils.py:116-135, so PermissionError remains unhandled.
Problems
- The proposed loop retries every platform at
utils.py:84-91, despite the Windows-specific rationale. That delays permanent POSIX permission failures. - Current main added the
EXDEV/EBUSYcopy fallback inutils.py:114-135viabf8effad0(#43852). This branch is currently unmergeable, so salvage needs to integrate the retry without bypassing that fallback. - The new test at
tests/hermes_cli/test_atomic_json_write.py:168-188covers only success after transient failures; it does not cover retry exhaustion or POSIX non-retry behavior. The duplicate discussion on #45022 identified the same coverage gap.
Suggested changes
- Scope bounded
PermissionErrorretries to Windows within the current fallback structure. - Add Windows-success, Windows-exhaustion, and POSIX-immediate-propagation tests with sleep mocked out.
Automated hermes-sweeper review.
| target_str = str(target) | ||
| real_path = os.path.realpath(target_str) if os.path.islink(target_str) else target_str | ||
| os.replace(str(tmp_path), real_path) | ||
| for attempt in range(_ATOMIC_REPLACE_MAX_ATTEMPTS): |
There was a problem hiding this comment.
The PR rationale is Windows mandatory locking, but this loop retries every POSIX PermissionError too. Please scope the retry to the Windows path so a permanent POSIX permission denial still propagates immediately.
| raise PermissionError("temporarily locked") | ||
| real_replace(src, dst) | ||
|
|
||
| with patch.object(utils.os, "replace", side_effect=flaky_replace): |
There was a problem hiding this comment.
This only proves eventual success. When the implementation is scoped to Windows, explicitly simulate that branch here and add a separate persistent-failure assertion so the bounded retry still surfaces a real denial.
|
Superseded by #84852. Your bounded-retry instinct was right and is preserved in #84852 — a retry that wins keeps the write fully atomic, which no pure-fallback approach achieves. Credited as a co-author. Two extensions: the retry is keyed on the specific winerror codes ({5, 32, 33}) rather than |
Summary
PermissionErrorfailures aroundos.replaceinatomic_replaceWhy
On Windows, antivirus/indexers or another rapid writer can briefly hold the target path while
atomic_json_writeis replacing it. That can make otherwise-safe concurrent writes fail withPermissionErroreven though retrying moments later succeeds.Validation
python -m pytest --timeout-method=thread tests/hermes_cli/test_atomic_json_write.py -qpython -m ruff check utils.py tests/hermes_cli/test_atomic_json_write.py