Skip to content

feat(libkernel): support kevent read and write events on regular files - #7088

Open
inmert wants to merge 1 commit into
boykopovar:mainfrom
inmert:feat/kevent-regular-file
Open

inmert wants to merge 1 commit into
boykopovar:mainfrom
inmert:feat/kevent-regular-file

Conversation

@inmert

@inmert inmert commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

kevent threw for EVFILT_READ and EVFILT_WRITE on a regular file:

kevent: EVFILT_READ on a descriptor that is neither a socket nor the read end of a pipe not implemented

Both now follow FreeBSD 11's vfs_kqfilter, filt_vfsread and filt_vfswrite, from the same commit the kqueue debt entry already links. A read event reports the file size minus the descriptor's offset (negative past the end) and fires when that is not zero, or always with NOTE_FILE_POLL. NOTE_LOWAT and data are ignored, and adding the event again replaces its fflags, as kqueue_register does for a filter without f_touch. A write event always fires with data 0. Size and offset come from sceKernelFstat and sceKernelLseek, so files served from app0.zar work as well.

A waiting kevent is woken by the host watch EVFILT_VNODE already uses, so a write from another descriptor or process wakes it. Files from app0.zar never change and get no watch.

GuestSockets::WaitAny also needed a change. It passed every interest that is not a socket to ppoll, and the host reports a regular file (and the null device behind an archived file) ready at once, so a kevent waiting on a file at its end spun on the CPU. It now passes only pipes to the host, and on Windows the 1 ms pipe slicing is only turned on when a pipe is actually in the set.

EV_CLEAR on a regular file still throws, as it does on sockets and pipes, and so do directories (their va_size is not modelled) and the other descriptor types. The debt entry also notes the one difference I know of: the event is re-evaluated at every scan, so after reading to the end a seek back is reported straight away, where FreeBSD waits for the next write, truncation or attribute change.

Overlap: #7075 changes the same line of WaitAny. I used its interest.write ? WriteMask : ReadMask there, so whichever lands second only rebases that line. #7049 changes the lines next to mine in ModifyKnote; the two don't interact. #7092 (NOTE_LOWAT on sockets) conflicts in ModifyKnote and in the kqueue debt sentence (git merge-tree of the two heads). They don't clash in behaviour: its DescriptorLowWater only applies to sockets, and a regular file ignores the mark as filt_vfsread does, so the resolution is to keep its DescriptorLowWater call in place of my !file guard, and to keep both edits to the debt sentence. #6343 (descriptor 0) touches the same branch of AddDescriptorKnote; with this PR a stdin redirected from a regular file takes the new path, which is what FreeBSD does too.

Tested

Ubuntu 24.04, GCC 13.3.0, Release, on main ca82d7d88.

  • New RegularFile case in guest_kqueue: data in the middle and at the end of the file, negative data past the end, NOTE_FILE_POLL turned on and off by adding the event again, NOTE_LOWAT ignored, a blocking kevent woken by a write through a second descriptor 50 ms later, a truncation, EV_CLEAR throwing, the write event, and the events going away on close. It aborts on main with the error above and passes with this change.
  • Without the WaitAny change the test still passes, but takes about 80 ms of CPU instead of 10 ms (/usr/bin/time, 3 runs each) because the 50 ms wait spins.
  • guest_equeue_events, guest_kqueue, guest_poll, guest_select, guest_select_wait, the guest_socket_* tests, guest_tcp, guest_udp, guest_sce_net, the guest_rudp_* tests and guest_net_ctl: 22/22 pass.
  • python3 tools/check_conventions.py --base origin/main passes.
  • Not tested: Windows and macOS (only Linux was built here), a console, a title.

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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

EVFILT_READ and EVFILT_WRITE on a regular file threw. They now follow
FreeBSD 11's vfs_kqfilter, filt_vfsread and filt_vfswrite: a read event
reports the file size minus the descriptor's offset and fires when that
is not zero or NOTE_FILE_POLL is set; a write event always fires with
data 0. A waiting kevent is woken by the host watch EVFILT_VNODE uses,
and the equeue wait no longer polls descriptors that are neither
sockets nor pipes, which the host reports ready at once.
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 4630 docs/dev/debt/libkernel.md
PR 5021 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 6044 core/libs/tests/GuestKqueue.cpp
PR 6149 docs/dev/debt/libkernel.md
PR 6446 docs/dev/debt/libkernel.md
PR 6469 docs/dev/debt/libkernel.md
PR 6519 docs/dev/debt/libkernel.md
PR 6586 docs/dev/debt/libkernel.md
PR 6658 docs/dev/debt/libkernel.md
PR 6737 docs/dev/debt/libkernel.md
PR 6740 docs/dev/debt/libkernel.md
PR 6825 docs/dev/debt/libkernel.md
PR 6826 docs/dev/debt/libkernel.md
PR 6830 docs/dev/debt/libkernel.md
PR 6833 docs/dev/debt/libkernel.md
PR 6836 docs/dev/debt/libkernel.md
PR 6879 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 6893 docs/dev/debt/libkernel.md
PR 6908 docs/dev/debt/libkernel.md
PR 6915 docs/dev/debt/libkernel.md
PR 6936 docs/dev/debt/libkernel.md
PR 6977 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7039 docs/dev/debt/libkernel.md
PR 7049 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
core/libs/tests/GuestKqueue.cpp
docs/dev/debt/libkernel.md
PR 7070 docs/dev/debt/libkernel.md
PR 7075 core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
core/libs/prx/libkernel/Socket/src/Udp.cpp
core/libs/tests/GuestKqueue.cpp
docs/dev/debt/libkernel.md
PR 7090 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 core/libs/prx/libkernel/Equeue/Equeue.hpp
core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
core/libs/tests/GuestKqueue.cpp
docs/dev/debt/libkernel.md
PR 7129 core/libs/prx/libkernel/Equeue/Equeue.hpp
core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
core/libs/prx/libkernel/Socket/src/Udp.cpp
core/libs/tests/GuestKqueue.cpp
docs/dev/debt/libkernel.md
PR 7135 docs/dev/debt/libkernel.md
PR 7149 docs/dev/debt/libkernel.md
PR 7192 docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7299 docs/dev/debt/libkernel.md
PR 7332 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7403 docs/dev/debt/libkernel.md
PR 7437 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7447 docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7475 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7485 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7492 core/libs/tests/GuestKqueue.cpp
PR 7502 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7507 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7509 core/libs/prx/libkernel/Equeue/Equeue.hpp
docs/dev/debt/libkernel.md
PR 7511 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7530 docs/dev/debt/libkernel.md
PR 7532 docs/dev/debt/libkernel.md
PR 7539 docs/dev/debt/libkernel.md
PR 7541 docs/dev/debt/libkernel.md
PR 7542 docs/dev/debt/libkernel.md
PR 7543 docs/dev/debt/libkernel.md
PR 7551 docs/dev/debt/libkernel.md
PR 7554 docs/dev/debt/libkernel.md
PR 7556 docs/dev/debt/libkernel.md
PR 7559 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7565 docs/dev/debt/libkernel.md
PR 7605 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7611 docs/dev/debt/libkernel.md
PR 7622 core/libs/prx/libkernel/Equeue/src/FileEvent.cpp
PR 7653 core/libs/prx/libkernel/Equeue/src/Kqueue.cpp
core/libs/tests/GuestKqueue.cpp
docs/dev/debt/libkernel.md
PR 7654 docs/dev/debt/libkernel.md
PR 7670 core/libs/tests/GuestKqueue.cpp
PR 7675 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7804 docs/dev/debt/libkernel.md
PR 7807 docs/dev/debt/libkernel.md
PR 7813 docs/dev/debt/libkernel.md
PR 7842 docs/dev/debt/libkernel.md

@blawesom

Copy link
Copy Markdown
Contributor

Rebase handoff — rebased onto current main (authorship unchanged). The conflicts reconcile main's NOTE_LOWAT evolution with this PR's regular-file read/write events:

  • Kqueue.cpp: main's DescriptorLowWater kept alongside your IsRegularFile, and ModifyKnote carries both the file classification and the low-water mark; your NOTE_LOWAT stub is superseded by main's real support
  • GuestKqueue.cpp: your NoteFilePoll constant added (the duplicate NoteLowat from the merge removed — main already had it)

Verified on Linux (CachyOS, kernel 7.2.9, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, AMD RX 9070 XT / RADV): builds; conventions pass; the kqueue tests pass; full suite green apart from the known main regression (agc_driver_tessellation segfault — Mesa, #5444), not this PR.

To pick it up:

git fetch https://github.com/blawesom/AnyPS5 rebase/7088
git checkout <your branch>
git merge --ff-only FETCH_HEAD

Head is 7e4d7a696.

@thuanquang

Copy link
Copy Markdown
Contributor

The new RegularFile in GuestKqueue.cpp takes start after it creates the writer thread that acts after 50 ms, then requires waited >= 40 ms. Under parallel load the main thread can be preempted between creating that thread and reading the clock, so waited comes out under 40 ms although kevent did wait for the write. PipeRead in the same file has this order and aborts that way on Windows: on our RX 6700 XT PC, 9 of 120 runs under load. A timing probe caught 4 such aborts, and in each the main thread took start 39 to 68 ms after creating the writer, with kevent returning 0.3 to 1.4 ms after the write. #7670 moves start before the thread there and in guest_socket_events. Taking start before creating the writer here keeps the new check from flaking the same way.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs libkernel needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants