Skip to content

fix(libkernel): report POLLHUP on macOS only once both directions of a stream have ended - #11858

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
jotitadev:fix/macos-poll-hangup-both-directions
Oct 28, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
jotitadev:fix/macos-poll-hangup-both-directions

Conversation

@jotitadev

Copy link
Copy Markdown
Contributor

What

guest_poll aborts on macOS in PollsAStreamWhosePeerClosed: after the peer's shutdown(SHUT_WR), poll on the accepted socket returns POLLIN|POLLHUP (0x11) for POLLIN and for POLLIN|POLLOUT, where the test expects 0x1 and 0x5.

XNU's poll is built on kqueue: poll_callback sets POLLHUP on EV_EOF from either filter, so it reports POLLHUP as soon as either direction of a stream has ended, adds POLLPRI|POLLRDBAND to a read-side EOF, and then drops POLLOUT. FreeBSD 11.4's sopoll_generic reports POLLHUP only once both the receive and the send side are shut, and Linux does the same. Measured with a plain C probe on macOS 27.2 (arm64 and x86-64 under Rosetta give the same), events POLLIN / POLLOUT / POLLIN|POLLOUT:

stream state macOS host Linux host PS5 (#11247's console run)
peer shut its sending side / closed 0x11 / 0x4 / 0x11 0x1 / 0x4 / 0x5 0x1 / - / 0x5
own sending side shut 0 / 0x10 / 0x10 0 / 0x4 / 0x4 0 / - / 0x4
both shut 0x11 / 0x10 / 0x11 0x11 / 0x14 / 0x15 0x11 / - / 0x11

On macOS, when the host reports POLLHUP (without POLLERR) on a guest stream socket, poll now asks the host about each direction alone (a zero-timeout POLLIN and POLLOUT poll, whose POLLHUP means that direction ended), keeps POLLHUP only when both have ended, reports the requested POLLOUT while the sending side is shut or writable, and drops the POLLPRI|POLLRDBAND that XNU adds to an EOF; real urgent data still comes from the existing SIOCATMARK check. It is the macOS counterpart of the Windows ClosedStreamEvents, which already does this from sendShut. Linux and Windows are unchanged.

The test gets two checks in PollsAStreamWhosePeerClosed: the socket that shut its own sending side reports only POLLOUT for POLLIN|POLLOUT, and (not on Windows, where POLLPRI is asked as POLLRDBAND) POLLIN|POLLPRI|POLLRDBAND on the half-closed socket reports POLLIN only. The macOS sockets debt entry records the remaining gap: a call that asks for no event gets nothing from XNU, where FreeBSD reports POLLHUP.

Tested

Overlap: #11247 and #11223 edit the same poll code in Udp.cpp. git merge-tree merges this branch with #11247 cleanly. #11223 and #5021 already conflict with main in Udp.cpp; with this branch they conflict at the same hunks (shifted by the 13 added lines), none of them in ClosedStreamEvents or the collect step this PR touches. #5021's Udp.cpp hunks are in the includes, the equeue wait, NativePair, Ready and ioctl, disjoint from these.

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 for the change. Building the test on macOS needs the open macOS series listed above.
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

…a stream have ended

XNU's poll reports POLLHUP as soon as either direction of a stream socket has ended, adds POLLPRI and POLLRDBAND to it and then drops POLLOUT. FreeBSD's sopoll_generic reports POLLHUP only once the receive and the send side are both shut, and Linux does the same, so on macOS a guest saw POLLIN|POLLHUP for a stream whose peer had only shut its sending side, and POLLHUP for one that had only shut its own.

When the host reports POLLHUP on a guest stream socket, ask it about each direction alone and keep POLLHUP only when both have ended, report the requested POLLOUT while the sending side is shut or writable, and leave urgent data to SIOCATMARK.
@github-actions

github-actions Bot commented Oct 27, 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/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 6149 docs/dev/debt/libkernel.md
PR 6936 docs/dev/debt/libkernel.md
PR 7088 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 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 7447 docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7509 docs/dev/debt/libkernel.md
PR 7804 docs/dev/debt/libkernel.md
PR 8605 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9156 docs/dev/debt/libkernel.md
PR 9242 docs/dev/debt/libkernel.md
PR 9293 docs/dev/debt/libkernel.md
PR 9297 docs/dev/debt/libkernel.md
PR 9302 docs/dev/debt/libkernel.md
PR 9389 docs/dev/debt/libkernel.md
PR 9557 docs/dev/debt/libkernel.md
PR 9566 docs/dev/debt/libkernel.md
PR 10319 docs/dev/debt/libkernel.md
PR 10442 docs/dev/debt/libkernel.md
PR 11002 docs/dev/debt/libkernel.md
PR 11215 docs/dev/debt/libkernel.md
PR 11625 docs/dev/debt/libkernel.md
PR 11849 docs/dev/debt/libkernel.md
PR 11864 docs/dev/debt/libkernel.md
PR 11872 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 11969 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md

@MarcZX10 MarcZX10 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.

I reviewed the macOS-only normalization against the existing Windows counterpart, the poll result collection path (including the later SIOCATMARK urgent-data check), and the new half-close assertions. The branch is based on 801d5b9bc1; I checked that current main has not changed the touched Udp.cpp, GuestPoll.cpp or debt lines since then, and the current-base diff is whitespace-clean. The reported macOS before/after runs and 97-test comparison are detailed, but I cannot independently run the macOS-specific runtime case on this Linux host. I found no additional code defect in the reviewed diff; this is a COMMENTED review rather than an approval because that platform-specific behavior remains author-reported here.

@Gonic0

Gonic0 commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Same result on a second Mac: Apple M5 Pro, macOS 27.2, Apple clang 21, x86-64 Release under Rosetta 2.

Tree: this PR's base, main 801d5b9bc1, plus the nine PRs from your description (#7631, #11249, #11358, #10669, #10552, #7804, #11447, #11437, #11554). On that main, #7804 conflicts in Process.cpp and Time.cpp. I kept main's code and added #7804's lines on top. Only 8bed63610b was toggled.

guest_poll 60 socket, poll, select, kqueue, equeue and libSceNet tests
without this PR aborts, 3 of 3 3 fail: guest_poll, guest_sce_net, guest_socket_port_zero
with this PR passes, 3 of 3 2 fail: guest_sce_net, guest_socket_port_zero

So the only test that changes is guest_poll, and the two that fail both ways are the same ones you list.

AI-assisted: yes

@adriadam10 adriadam10 self-assigned this Oct 28, 2026
@adriadam10
adriadam10 merged commit 027989a into boykopovar:main Oct 28, 2026
11 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the macOS ClosedStreamEvents against the Windows counterpart and the collect step, the per-direction poll results for POLLIN, POLLOUT and POLLIN|POLLOUT after a peer shutdown and an own-side shutdown, the new PollsAStreamWhosePeerClosed checks and the libkernel debt entry; macOS runs confirmed by @Gonic0 on a second Mac; merged.

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