Skip to content

fix(libkernel): keep the Windows vnode watch when the watched file is deleted - #5393

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
yixx16:fix/libkernel-vnode-windows-delete
Oct 14, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
yixx16:fix/libkernel-vnode-windows-delete

Conversation

@yixx16

@yixx16 yixx16 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

What

On Windows 10 1903 and later, DeleteFileW and _wremove delete with POSIX semantics: a file with open handles is moved into \$Extend\$Deleted until its last handle closes. The EVFILT_VNODE watch thread keeps a handle on the watched file and takes GetFileInformationByHandleEx snapshots every 50 ms, so after the title deletes a watched file the next snapshot sees a new final path (\\?\C:\$Extend\$Deleted\<id>, 0 links, delete pending), replaces the parent directory watch with one on that directory, gets ERROR_ACCESS_DENIED from FindFirstChangeNotification and throws from the detached watch thread, which aborts the process. The path change was also reported as NOTE_RENAME next to NOTE_DELETE.

A snapshot with a pending delete or no links now reports NOTE_DELETE alone and drops the parent directory watch instead of replacing it: the file has no directory left to be renamed or removed from, and its own handle keeps reporting until the knote is deleted. The vnode entry in docs/dev/debt/libkernel.md records the behaviour.

Measured on Windows 11 with a probe (second handle with FILE_SHARE_DELETE, then DeleteFileW or _wremove): GetFinalPathNameByHandleW returns \\?\C:\$Extend\$Deleted\<id>, FileStandardInfo reports NumberOfLinks 0 and DeletePending 1, and FindFirstChangeNotificationW on \\?\C:\$Extend\$Deleted\ fails with error 5. The same abort appeared as an intermittent guest_kqueue failure (EVFILT_VNODE: FindFirstChangeNotification failed, error=5, exit 0xc0000409) on a Windows CI run of my fork at the end of a vnode test, where the kqueue is closed and the file removed right after, with the watch thread still in a snapshot.

Tested

guest_kqueue gains DeleteWatchedFile, registered on Linux and Windows: a watched file is unlinked while open, NOTE_DELETE is reported once without NOTE_RENAME, nothing more is reported afterwards, and the descriptor and the kqueue close normally.

Full Linux and Windows builds with every test on my fork's GitHub Actions Build workflow (1077 and 1095 tests, all passed; this branch together with three kqueue branches of mine): https://github.com/yixx16/AnyPS5/actions/runs/38445944846. The Windows abort without the fix is in the first attempt of https://github.com/yixx16/AnyPS5/actions/runs/38437850775 (job windows, guest_kqueue). No title run.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code); the author reviewed and ran everything above

… deleted

Windows 10 1903 deletes a file with open handles by moving it into
$Extend\$Deleted until the last handle closes, so the next snapshot of a
watched file that a title (or the test) deleted saw a new path, tried to
watch that directory's changes, got ERROR_ACCESS_DENIED and threw from
the watch thread, which aborts the process; the path change was also
reported as NOTE_RENAME next to NOTE_DELETE. A snapshot with a pending
delete or no links now reports NOTE_DELETE alone and drops the parent
directory watch instead of replacing it, as the file has no directory
to be renamed or removed from any more.
@likecrank-dotcom

Copy link
Copy Markdown

Independent Windows stress validation by OpenAI Codex (AI-assisted). No code changes were made to this PR.

Validated PR head 50b06110bbac6e37fad8c3dcb55fbecac1843134 on Windows 11 Pro build 26100 with WinLibs MinGW GCC 15.2.0 and CMake 4.4.4. Built guest_kqueue_tests; CTest guest_kqueue passed 20 consecutive full-suite runs (28.18 seconds total, 20/20). This includes the new DeleteWatchedFile regression. tools/check_conventions.py against base 18c438b1092b09cef54e4cfacab441c009a6b501 and git diff --check passed. This is a focused repeated test, not a full build or title run.

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#4908 docs/dev/debt/libkernel.md L97-98
#4910 docs/dev/debt/libkernel.md L97-98
#5361 docs/dev/debt/libkernel.md L97-98
#5440 core/libs/tests/GuestKqueue.cpp L402, L425
docs/dev/debt/libkernel.md L97-98
#5493 core/libs/tests/GuestKqueue.cpp L402, L425
docs/dev/debt/libkernel.md L98

@yixx16

yixx16 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

The overlap with #5361 is only the adjacent line of docs/dev/debt/libkernel.md: it edits the kqueue entry and this PR the EVFILT_VNODE entry below it. The code is unrelated, so this does not depend on #5361; whichever lands second rebases the debt line.

@simonbronk

Copy link
Copy Markdown
Contributor

Independent confirmation on Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7, full build (AI-assisted: Claude Code).

Without the fix, enabling the existing RenameAndDeleteWatchedFile test on Windows (it is under #ifndef _WIN32 today) aborts guest_kqueue deterministically with EVFILT_VNODE: FindFirstChangeNotification failed, error=5, exit 0xc0000409, the same failure as #5435.

With the same change as this PR (Deleted = delete pending or 0 links; no NOTE_RENAME and no parent rewatch when deleted), RenameAndDeleteWatchedFile passes on Windows: NOTE_RENAME after the rename, then NOTE_DELETE alone after the unlink. guest_kqueue passed 30/30 with ctest --repeat until-fail:30, and the full suite 1098/1098.

Since that test now passes on Windows, it could move out of the #ifndef _WIN32 block (only MoveDirectoryEntries needs to stay Linux-only), either here or in a follow-up after this lands.

@oneandonlydean
oneandonlydean merged commit 8a51486 into boykopovar:main Oct 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants