Skip to content

Watcher: list a folder without blocking its removal (restores failing since #100) - #134

Open
sivadaboi wants to merge 1 commit into
dev-publicfrom
fix/watcher-dir-delete
Open

sivadaboi wants to merge 1 commit into
dev-publicfrom
fix/watcher-dir-delete

Conversation

@sivadaboi

@sivadaboi sivadaboi commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

A regression from #100, on Windows: restoring a snapshot of a save that has subfolders fails most of the time, and a game deleting one of its own save folders can be refused in the same way.

Seen as:

  • TestAdversarial_AwkwardFilenamesSurviveASnapshotRoundTrip and TestAdversarial_RepeatedRollbackIsIdempotent fail 7 runs in 10 on dev-public, with restore snapshot …: clear target dir: unlinkat …\saves\b: The process cannot access the file because it is being used by another process.
  • Bisecting dev-public's merges with those two tests: 0 failures up to e2e: branchHolds reads a branch's newest snapshot, as it says #98, failures from watcher: a subfolder missed when it was created is watched on the next change in its parent #100 on. The restore is safe either way: it puts the save back and says so.
  • By hand, with a scratch daemon built from dev-public: restoring Terraria (saves in Players\) failed 2 times in 5. A script standing in for the game, deleting Players\Backups while OpenSave watched, was refused ("Device or resource busy").

Why. Since #100, a Write on a watched folder makes the watcher list that folder (checkFolders), to find subfolders whose Create was missed. On Windows, os.ReadDir opens the folder without FILE_SHARE_DELETE, and while it is open the folder can't be removed. Every file a restore deletes is a Write on its folder, so the watcher was listing the very folders the restore was about to remove. A game emptying a folder of its own is the same race.

The change. fsx.ReadDirShared lists a folder through a handle opened with FILE_SHARE_DELETE, falling back to os.ReadDir if that open fails, as fsx.OpenShared does for files. checkFolders uses it. Nothing else changes: on other systems an open folder never blocked removing it, and there it is os.ReadDir.

Measured before writing it: a folder held by os.Open can't be removed (the error above), while one held by the shared handle can, and the listing is the same.

Tests:

Checks:

cc @ChakraFusion, since it's #100's check. The folder check itself is untouched; only how it reads the folder changes.

@vercel

vercel Bot commented Oct 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
open-save Ready Ready Preview Oct 27, 2026 9:50pm UTC

This branch was successfully deployed

1 active deployment
Preview — 74ec18d2 Deployed Oct 27, 2026 by vercel[bot]
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