Skip to content

fix(libkernel): report a stream whose peer closed as readable in poll on Windows - #9492

Closed
EagleBearerYS wants to merge 2 commits into
boykopovar:mainfrom
EagleBearerYS:fix/libkernel-poll-closed-stream-2
Closed

EagleBearerYS wants to merge 2 commits into
boykopovar:mainfrom
EagleBearerYS:fix/libkernel-poll-closed-stream-2

Conversation

@EagleBearerYS

@EagleBearerYS EagleBearerYS commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Opened again from #5403: title-less conformance fixes of this kind are merged now, for example #7365 and #8411.

What

When the peer of a stream socket closes, Winsock's WSAPoll reports POLLHUP without POLLRDNORM, whether or not the socket's own sending side is shut down. poll passes that on, so on Windows a guest socket whose peer has closed comes back as POLLHUP alone and never as POLLIN.

FreeBSD's sopoll_generic reports a socket that can receive no more as readable, and adds POLLHUP only once it can send no more either. Linux's poll does the same, so the Linux path already behaves this way.

On Windows, a stream socket's POLLHUP without POLLERR now reports POLLRDNORM when reading was asked for. It keeps POLLHUP only after shutdown has shut the socket's own sending side, the Socket::sendShut state #4903 added. The two commits:

  • 984e3b5 is the fix and its test;
  • dd117fa updates the socketpair note in docs/dev/debt/libkernel.md. Its stream ends are loopback TCP connections, so after the peer's shutdown(SHUT_WR) they now report POLLIN, as the console does. After the peer's close the console also reports POLLHUP, which the loopback connection cannot tell apart from a shutdown.

poll looks the socket up by descriptor, and only when it reports a hang-up without an error. A libSceNet socket waited on through poll (#4933) keeps the flags WSAPoll reports.

Unchanged:

  • A reset or refused connection (POLLHUP with POLLERR) keeps WSAPoll's flags.
  • select already counts a hang-up as readable.

No title is claimed.

Overlap: #8483 makes the same poll change, in poll's collect step, with a new poll debt entry. #5403, where this change was first opened on 2026-10-13, was closed on 10-14 for having no title. Either can go in; the other then closes.

#7605 changes the same poll loop for kqueues, next to the line this PR adds after WSAPoll, and the two compose. Merge order: this PR goes after #7605 (merge order only) and rebases on it.

Tested

On GitHub's runners from my fork, on main at a6dcea9 (run, of the same tree as these two commits):

guest_poll gains three checks:

  • after a client shuts its own sending side while the server stays open, a POLLIN poll of the client reports nothing;
  • after the client closes and the server reads the end of the stream, the server's socket reports exactly POLLIN | POLLOUT;
  • after the server shuts its sending side too, a POLLIN poll reports POLLIN | POLLHUP.

Results:

  • Windows: with main's Udp.cpp the test aborts. With the change, guest_poll passes 10 runs in a row, and every test registered under core/libs/prx/libc (142) and core/libs/prx/libkernel (207) passes.
  • Linux: guest_poll passes with main's code and, 10 runs in a row, with the change. The libkernel suite (197) passes. In libc (115), the timing test precise_wait failed with and without the change in this run.
  • tools/check_conventions.py --base origin/main passes for both commits.
  • Not tested: macOS, a console, any title.

Checklist

… on Windows

When the peer of a stream socket closes, Winsock's WSAPoll reports
POLLHUP without POLLRDNORM, whether or not the socket's own sending
side is shut down. poll passed that on, so on Windows a guest socket
whose peer had closed came back as POLLHUP alone and never as POLLIN.
FreeBSD's sopoll_generic reports a socket that can receive no more as
readable, and adds POLLHUP only once it can send no more either. Linux
does the same, so the Linux path already behaved this way.

On Windows, a stream socket's POLLHUP without POLLERR now reports
POLLRDNORM when reading was asked for, and keeps POLLHUP only after
shutdown has shut the socket's own sending side, as recorded in
Socket::sendShut. sceNetEpollWait gets the same treatment in boykopovar#5376.
On Windows a socketpair stream end used to report POLLHUP without
POLLIN after its peer's shutdown(SHUT_WR) or close. With the previous
commit it reports POLLIN, as the console does after a shutdown. After a
close the console also reports POLLHUP, which the loopback connection
behind the pair cannot tell from a shutdown.

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

This PR implements the same Windows poll behavior as the still-open #8483, and the two PRs change the same Udp.cpp poll path. The body already identifies #8483 and says only one should go in. Please resolve that overlap with the other contributor and maintainers, then update or close one PR before this duplicate work proceeds. I have not assessed which implementation should be selected; both should not be merged.

@github-actions

github-actions Bot commented Oct 21, 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 6044 core/libs/tests/GuestPoll.cpp
PR 6149 docs/dev/debt/libkernel.md
PR 6469 docs/dev/debt/libkernel.md
PR 6586 docs/dev/debt/libkernel.md
PR 6740 docs/dev/debt/libkernel.md
PR 6830 docs/dev/debt/libkernel.md
PR 6879 docs/dev/debt/libkernel.md
PR 6936 docs/dev/debt/libkernel.md
PR 7049 docs/dev/debt/libkernel.md
PR 7088 core/libs/prx/libkernel/Socket/src/Udp.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 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
core/libs/tests/GuestPoll.cpp
PR 7299 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 7502 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7507 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7509 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 7565 docs/dev/debt/libkernel.md
PR 7653 docs/dev/debt/libkernel.md
PR 7804 docs/dev/debt/libkernel.md
PR 7853 docs/dev/debt/libkernel.md
PR 7937 docs/dev/debt/libkernel.md
PR 7965 docs/dev/debt/libkernel.md
PR 8010 docs/dev/debt/libkernel.md
PR 8344 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8359 docs/dev/debt/libkernel.md
PR 8373 docs/dev/debt/libkernel.md
PR 8408 docs/dev/debt/libkernel.md
PR 8448 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8463 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 8520 docs/dev/debt/libkernel.md
PR 8533 docs/dev/debt/libkernel.md
PR 8537 docs/dev/debt/libkernel.md
PR 8544 docs/dev/debt/libkernel.md
PR 8547 core/libs/tests/GuestPoll.cpp
docs/dev/debt/libkernel.md
PR 8553 docs/dev/debt/libkernel.md
PR 8605 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8641 docs/dev/debt/libkernel.md
PR 8658 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8670 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8700 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 8725 docs/dev/debt/libkernel.md
PR 8726 docs/dev/debt/libkernel.md
PR 8728 docs/dev/debt/libkernel.md
PR 8742 docs/dev/debt/libkernel.md
PR 8866 docs/dev/debt/libkernel.md
PR 8872 docs/dev/debt/libkernel.md
PR 8890 docs/dev/debt/libkernel.md
PR 8894 docs/dev/debt/libkernel.md
PR 8906 docs/dev/debt/libkernel.md
PR 8910 docs/dev/debt/libkernel.md
PR 8916 docs/dev/debt/libkernel.md
PR 8921 docs/dev/debt/libkernel.md
PR 8977 docs/dev/debt/libkernel.md
PR 8985 docs/dev/debt/libkernel.md
PR 8986 docs/dev/debt/libkernel.md
PR 8988 docs/dev/debt/libkernel.md
PR 9001 docs/dev/debt/libkernel.md
PR 9005 docs/dev/debt/libkernel.md
PR 9059 docs/dev/debt/libkernel.md
PR 9060 docs/dev/debt/libkernel.md
PR 9074 docs/dev/debt/libkernel.md
PR 9095 docs/dev/debt/libkernel.md
PR 9099 docs/dev/debt/libkernel.md
PR 9101 docs/dev/debt/libkernel.md
PR 9104 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9105 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9108 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9109 docs/dev/debt/libkernel.md
PR 9123 docs/dev/debt/libkernel.md
PR 9127 docs/dev/debt/libkernel.md
PR 9133 docs/dev/debt/libkernel.md
PR 9134 docs/dev/debt/libkernel.md
PR 9153 docs/dev/debt/libkernel.md
PR 9156 docs/dev/debt/libkernel.md
PR 9160 docs/dev/debt/libkernel.md
PR 9164 docs/dev/debt/libkernel.md
PR 9165 docs/dev/debt/libkernel.md
PR 9167 docs/dev/debt/libkernel.md
PR 9169 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9187 docs/dev/debt/libkernel.md
PR 9199 docs/dev/debt/libkernel.md
PR 9218 docs/dev/debt/libkernel.md
PR 9239 docs/dev/debt/libkernel.md
PR 9242 docs/dev/debt/libkernel.md
PR 9256 docs/dev/debt/libkernel.md
PR 9263 docs/dev/debt/libkernel.md
PR 9265 docs/dev/debt/libkernel.md
PR 9273 docs/dev/debt/libkernel.md
PR 9284 docs/dev/debt/libkernel.md
PR 9293 docs/dev/debt/libkernel.md
PR 9297 docs/dev/debt/libkernel.md
PR 9298 docs/dev/debt/libkernel.md
PR 9302 docs/dev/debt/libkernel.md
PR 9314 docs/dev/debt/libkernel.md
PR 9326 docs/dev/debt/libkernel.md
PR 9359 docs/dev/debt/libkernel.md
PR 9363 docs/dev/debt/libkernel.md
PR 9365 docs/dev/debt/libkernel.md
PR 9378 docs/dev/debt/libkernel.md
PR 9389 docs/dev/debt/libkernel.md
PR 9392 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9461 docs/dev/debt/libkernel.md
PR 9482 docs/dev/debt/libkernel.md
PR 9506 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9509 core/libs/prx/libkernel/Socket/src/Udp.cpp
docs/dev/debt/libkernel.md
PR 9557 docs/dev/debt/libkernel.md
PR 9561 docs/dev/debt/libkernel.md
PR 9566 docs/dev/debt/libkernel.md
PR 9573 docs/dev/debt/libkernel.md
PR 9575 docs/dev/debt/libkernel.md
PR 9582 docs/dev/debt/libkernel.md
PR 9625 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9669 docs/dev/debt/libkernel.md
PR 9682 docs/dev/debt/libkernel.md
PR 9684 docs/dev/debt/libkernel.md
PR 9693 docs/dev/debt/libkernel.md
PR 9732 docs/dev/debt/libkernel.md
PR 9736 docs/dev/debt/libkernel.md
PR 9770 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9794 docs/dev/debt/libkernel.md

@github-actions github-actions Bot added the fix error Closes an issue (Fixes #N) label Oct 21, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

Thanks, @MarcZX10. I've raised it on #8483 (comment) and will settle with its author and the maintainers which of the two stays; the other then closes.

@MarcZX10

Copy link
Copy Markdown
Contributor

Thanks for raising the duplicate with #8483. I still do not see an agreement from that PR author or maintainers on which implementation will proceed, so the duplicate remains unresolved. Also, #7605 edits the same poll loop and the PR body describes the changes as composing; please list the required merge order under Depends on and preserve that rebase sequence. Once the duplicate choice is settled, update or close the superseded PR, update the checklist, and rebase the surviving branch on current main before requesting another review. I have not rerun tests locally.

@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

Thanks, @MarcZX10. Depends on now lists #7605, and the Overlap section says this PR goes after it and rebases on it. On #8483, the question of which one stays is with its author and the maintainers. Once it's settled, the other closes, and the surviving branch gets its checklist updated and is brought up to date with current main before another review.

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

The Windows poll translation is scoped to stream POLLHUP without POLLERR, and its regression checks half-close, peer close/EOF, and the both-directions-shut state. The PR correctly records #7605 as a prerequisite and includes Fixes: #8482; #8482 remains open. However, duplicate work #8483 is also still open and implements the same behavior. The PR itself says one should close, but there is still no author/maintainer decision. Please resolve that choice with #8483’s author and maintainers, update/close the superseded PR, and preserve the #7605 order. This branch and its tests are based on a6dcea9, while current upstream main is ab347d1; rebase the surviving PR and rerun guest_poll, conventions and overlap checks on current main. Also preserve the #8344/#8483/#8670 debt edits and manually compare #5021’s Udp.cpp warning. Hosted product jobs are skipped; I did not run local tests.

@github-actions github-actions Bot added libkernel docs needs rebase Conflicts with main, rebase needed labels Oct 22, 2026
@EagleBearerYS

Copy link
Copy Markdown
Contributor Author

Closing this one: #8483 landed the same change to poll for a stream whose peer closed (093149c), and as adriadam10 noted there, whichever merged first, the other closes. Thanks, @xXPBSXx and @adriadam10.

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

Labels

docs fix error Closes an issue (Fixes #N) libkernel needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

libkernel: poll on Windows reports a stream whose peer closed as POLLHUP only, never as POLLIN

2 participants