Skip to content

fix: make atomic state writes work on Windows - #88

Merged
nianzhibai merged 2 commits into
nianzhibai:mainfrom
thazjswe42700:fix/windows-atomic-state-writes
Aug 14, 2026
Merged

fix: make atomic state writes work on Windows#88
nianzhibai merged 2 commits into
nianzhibai:mainfrom
thazjswe42700:fix/windows-atomic-state-writes

Conversation

@thazjswe42700

Copy link
Copy Markdown
Contributor

Problem

On Windows the server aborts during startup:

configure backup transfer service: backup transfer: write server identity:
sync C:\...\backend\data\backups\.peer-transfer: Access is denied.

writeJSONAtomic in internal/backuptransfer/store.go — and its twin in
internal/backup/manager.go — fsyncs the parent directory after os.Rename.
Windows rejects FlushFileBuffers on a directory handle opened read-only via
os.Open and returns ERROR_ACCESS_DENIED. That error is propagated as fatal,
even though the rename has already committed: identity.json is intact on disk
when the process dies.

A second, latent problem surfaces once the first is fixed. readJSONFile
rejects state files whose mode has any of 0o077 set. On Windows Chmod(0600)
only toggles the read-only attribute and Lstat reports 0666 for any writable
file, so the guard rejects the very file the process just wrote itself. First
start hits bug 1 (file absent, write path); every restart afterwards hits bug 2
(file present, read path). Both have to go for the server to run.

This reproduces in the test suite, not just at runtime. On main
(8cda4a5), on Windows:

--- FAIL: TestBeginImportRecreatesExpiredStagingAfterTokenWasRefreshed
    receiver_test.go:67: backup transfer: write server identity:
    sync C:\...\001\peer-transfer: Access is denied.
FAIL	github.com/video-site/backend/internal/backuptransfer

Change

  • The directory fsync after rename is now best-effort in both packages.
  • The Unix permission guard is skipped on Windows, where access is governed by
    the directory ACL instead.

internal/config/config.go already does exactly this and documents why; this
brings the other two atomic writers in line with that convention. The comment
wording is taken from there.

Tradeoff worth flagging

Making the directory fsync best-effort also silences genuine fsync failures on
Linux, so this is not a Windows-only behaviour change. The rationale is the one
already recorded in internal/config: the rename has committed by that point,
so returning an error tells the caller the write failed when it did not. Calling
it out explicitly rather than burying it.

Verification

  • go build ./... and go vet ./internal/backup/... ./internal/backuptransfer/... clean
  • go test ./internal/backuptransfer/... ./internal/config/... pass on Windows
    (they fail on main, as shown above)
  • Server starts and writes .peer-transfer/identity.json successfully
  • Not verified: internal/backup tests could not be executed on the machine
    used here — the test binary is blocked by a Windows Application Control policy
    (go test -c compiles fine; running it is denied regardless of path). The edit
    in that package is byte-identical to the backuptransfer one, whose tests
    pass. Worth confirming on CI or Linux.

🤖 Generated with Claude Code

thazjswe42700 and others added 2 commits August 14, 2026 21:48
The directory fsync issued after rename in backup and backuptransfer is
now best-effort. Windows rejects FlushFileBuffers on a read-only directory
handle with ERROR_ACCESS_DENIED, so the server aborted at startup with
"write server identity: ... Access is denied" even though the rename had
already committed and the file on disk was intact. This mirrors the
convention already documented in internal/config.

Also skip the Unix permission guard on backup transfer state files when
running on Windows: Chmod only toggles the read-only attribute there and
Lstat always reports 0666 for a writable file, so the guard rejected every
state file the process had just written itself. Access is governed by the
directory ACL on that platform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nianzhibai
nianzhibai merged commit e1fa25c into nianzhibai:main Aug 14, 2026
1 check passed
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