Skip to content

fix(windows): ride out longer AV holds in atomicWriteFile's rename retry - #2041

Merged
kriszyp merged 1 commit into
mainfrom
fix/atomic-write-retry-budget
Aug 1, 2026
Merged

fix(windows): ride out longer AV holds in atomicWriteFile's rename retry#2041
kriszyp merged 1 commit into
mainfrom
fix/atomic-write-retry-budget

Conversation

@cb1kenobi

Copy link
Copy Markdown
Member

Why

The Windows Integration 6/6 job on #2036 (chore(deps): Update rocksdb-js to 2.6.1) failed twice in a row (then passed on a third run) with:

EPERM: operation not permitted, rename '...harper-config.yaml.<pid>.<tid>.<rand>.tmp' -> '...harper-config.yaml'

The failing test's duration (~949ms) matches atomicWriteFile's full retry budget (~910ms: 8 retries, 200ms cap) — the budget added in #1714 (fix(windows): widen atomicWriteFile rename-retry backoff for EPERM/EACCES) was exhausted, not skipped. Node readers can't cause this (libuv opens with full sharing flags); the holder is almost certainly Defender/AV real-time scanning, and a single scan pass can exceed a second.

Change

  • Retry budget: 8 → 12 attempts, delay cap 200ms → 500ms (worst case ~3.6s, previously ~910ms).
  • The synchronous wait is now a timeout-only Atomics.wait on a module-level SharedArrayBuffer instead of a performance.now() busy-spin — the thread yields to the OS instead of burning CPU, which is what makes the longer worst case affordable. Happy path is unchanged (no wait unless a rename actually fails).

Worst case still blocks the calling thread's event loop for ~3.6s, but only on the path that previously threw after ~0.9s of spinning; call sites are synchronous by design.

Verification

  • unitTests/config/configUtils.test.js (72 passing) — including the retry-exhaustion test updated to pin the new production budget (13 rename calls).
  • Atomics.wait timed sleep verified on the main thread on Node 24 (returns timed-out after the requested delay).
  • Cross-model review: Codex (gpt-5) — confirmed main-thread/worker legality of Atomics.wait, timeout-only safety of the never-notified buffer, and the backoff arithmetic; its one finding (unused performance import breaking lint) is fixed.

Generated by Claude (Fable 5).

🤖 Generated with Claude Code

… spinning

The ~910ms backoff budget (8 retries, 200ms cap) was exhausted twice in a row
by the same test on a Windows CI runner (harper#2036) - an AV real-time scan
can hold harper-config.yaml for over a second. Widen to 12 retries with a
500ms cap (~3.6s worst case), and replace the performance.now() busy-spin
with a timeout-only Atomics.wait so the wait is CPU-idle, which is what makes
the longer budget affordable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cb1kenobi
cb1kenobi requested review from Ethan-Arrowood and kriszyp and removed request for Ethan-Arrowood August 1, 2026 00:42

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request optimizes the synchronous retry mechanism in configUtils by replacing a CPU-spinning busy-loop with Atomics.wait on a shared buffer, yielding the thread to the OS during delays. Additionally, the retry budget has been increased from 8 to 12 attempts and the maximum delay from 200ms to 500ms to better handle transient file locks. Corresponding unit tests have been updated to reflect the new retry count. There are no review comments to address, so I have no further feedback to provide.

@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

@cb1kenobi
cb1kenobi marked this pull request as ready for review August 1, 2026 00:55
@kriszyp
kriszyp merged commit b852a72 into main Aug 1, 2026
44 checks passed
@kriszyp
kriszyp deleted the fix/atomic-write-retry-budget branch August 1, 2026 02:50
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.

2 participants