Skip to content

fix(libSceNet): report EAGAIN when a stream receive times out on Windows - #9106

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
Retr0G28:fix/scenet-stream-recv-timeout-eagain
Oct 22, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
Retr0G28:fix/scenet-stream-recv-timeout-eagain

Conversation

@Retr0G28

@Retr0G28 Retr0G28 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

On Windows, sceNetRecv, sceNetRecvfrom and sceNetRecvmsg on a stream socket whose SO_RCVTIMEO expired failed with ETIMEDOUT (0x8041013C, errno 60). FreeBSD 11.4's soreceive_generic waits in sbwait (sys/kern/uipc_socket.c, sys/kern/uipc_sockbuf.c), and its msleep_sbt on so_rcv.sb_timeo returns EWOULDBLOCK (35) for every socket type. Linux already gives EAGAIN here.

Since #8653 the Windows host socket is always non-blocking and native_transfer enforces the stored timeout itself, but on expiry it set WSAETIMEDOUT. receive_error turned that into EAGAIN only for datagram sockets. #4092 left streams out because Winsock leaves a connection in an indeterminate state after a timeout on a blocking socket. That no longer applies: the host socket is non-blocking, so the timeout never reaches Winsock. native_transfer now sets WSAEWOULDBLOCK when a receive reaches its timeout, so every receive reports EAGAIN, and receive_error is removed. A WSAETIMEDOUT from the host, such as a dropped connection, still maps to ETIMEDOUT, as FreeBSD returns it from so_error. Send timeouts are unchanged. libkernel's own recv already returns 35 when its receive timeout expires. The sentence about this gap is cut from the sceNetSetsockopt entry in docs/dev/debt/libSceNet.md.

Test changes:

  • guest_sce_net (CheckStreamReceiveTimeout): a connected TCP socket with a 50 ms SO_RCVTIMEO and no data fails sceNetRecv, sceNetRecvfrom and sceNetRecvmsg with EAGAIN after at least 40 ms. The peer then sends, and a receive on the same socket returns the data. Sending in the other direction still works.

Tested

Windows 11, WinLibs MinGW-w64 GCC 15.2.0 r7, CMake + Ninja, Release, -DBUILD_TESTING=ON, on main d099bc6.

  • Before the fix, guest_sce_net with the new check fails (exit 0xc0000409). A temporary probe showed the stream receive returning 0x8041013C with errno 60. With this PR it passes.
  • ctest net_*, guest_datagram_*, guest_refused_connect*, guest_socket_host_write_*, guest_sce_net and guest_ssl_transport_*: 33 of 33 pass when run serially.
  • python tools/check_conventions.py --base origin/main reports no errors.
  • Second Windows machine: @ycbuzi ran the PR merged onto main 745b394 on Windows 11 (same toolchain): guest_sce_net passes on two repeats (comment below).
  • Not tested on Linux. The Windows wait is not built there, and a Linux recv that reaches SO_RCVTIMEO already fails with EAGAIN, so the new check should pass there unchanged.
  • No title or homebrew run.

Overlap: tools/pr_overlap.py reports no overlap with open pull requests. #8686 changes zero-length stream receives in the same file, not timeouts.

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

FreeBSD 11.4's soreceive_generic waits in sbwait (sys/kern/uipc_socket.c,
uipc_sockbuf.c), whose msleep_sbt on so_rcv.sb_timeo returns EWOULDBLOCK
(35) for every socket type when SO_RCVTIMEO expires. On Windows the host
socket is non-blocking and native_transfer enforces the timeout itself,
but it reported WSAETIMEDOUT, which only datagram receives turned into
EAGAIN; a stream receive failed with ETIMEDOUT (60). A receive that
reaches the stored timeout now reports WSAEWOULDBLOCK, so every
receive returns EAGAIN, and a host WSAETIMEDOUT still maps to ETIMEDOUT.
@github-actions

github-actions Bot commented Oct 21, 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
PR 9651 docs/dev/debt/libSceNet.md L32-33

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 5836 core/libs/prx/libSceNet/Export.cpp
core/libs/tests/GuestSceNet.cpp
docs/dev/debt/libSceNet.md
PR 6044 core/libs/tests/GuestSceNet.cpp
PR 6879 core/libs/prx/libSceNet/Export.cpp
PR 7201 core/libs/prx/libSceNet/Export.cpp
PR 8254 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md

@ycbuzi

ycbuzi commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Windows run of 3509bb5 merged onto main 745b394 (2ea9305b71, clean), the platform the description lists as not tested. Windows 11 Home 10.0.26200, MinGW-w64 GCC 15.2.0 (ucrt-posix-seh r7), Release, BUILD_TESTING=ON.

Built libSceNet and guest_sce_net_tests with no errors or warnings. ctest --repeat until-fail:2 on guest_sce_net: passes on both repeats. Not run: a title.

@oneandonlydean oneandonlydean self-assigned this Oct 22, 2026
@oneandonlydean
oneandonlydean merged commit 5d40eb3 into boykopovar:main Oct 22, 2026
13 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.

3 participants