Skip to content

fix(libSceNet): keep MSG_DONTWAIT and aborted stream sends short on macOS hosts - #9733

Open
mugurc wants to merge 1 commit into
boykopovar:mainfrom
mugurc:fix/libscenet-stream-sends-macos
Open

mugurc wants to merge 1 commit into
boykopovar:mainfrom
mugurc:fix/libscenet-stream-sends-macos

Conversation

@mugurc

@mugurc mugurc commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

What

The seven net_dontwait send tests, new on main this week, fail on macOS. Two stream send behaviours of XNU differ from FreeBSD and Linux. Part of the macOS series announced on #1 (#1 (comment)).

  • MSG_DONTWAIT. On a blocking socket, XNU's MSG_DONTWAIT only skips the wait for the send buffer's lock; a stream send still waits for buffer space. A 16 MiB send to a slow peer blocks until the peer goes away, and net_dontwait_send, _sendto, _sendmsg and _duplex fail with "MSG_DONTWAIT send blocked behind a slow peer". A MSG_DONTWAIT transfer now also passes XNU's MSG_NBIO (0x20000), which makes that one call non-blocking: the send queues what fits and returns that count, or EAGAIN when nothing fits. The flag is not in the SDK headers, so the debt entry records it.
  • A blocking send that fails part-way. When sceNetSocketAbort shuts down a socket during a blocking stream send that has queued data, XNU returns only the error, where Linux returns the bytes it queued. net_dontwait_blocking_send, _sendto and _sendmsg fail with "error discarded queued bytes". A blocking stream send now goes to the host in sends of 64 KiB, and when one fails after others completed, the result is the bytes of those. This is the way feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658 does it, credited to @iamthehimansh in the commit; the bytes the failing 64 KiB send queued are not counted, as the debt entry says.

Every change is in an __APPLE__ branch. MSG_NBIO is added where the guest flags become host flags, so it reaches receives too, which XNU's MSG_DONTWAIT already keeps from waiting. The 64 KiB sends are in sceNetSend and sceNetSendto, which sceNetSendmsg goes through; datagram sends, guest non-blocking sockets and MSG_DONTWAIT sends take one host call as before.

Tested

macOS 26.5.1, Apple M5 Max, Apple clang 21, x86-64 build under Rosetta 2. The libraries do not link on macOS without the rest of the macOS series, so the run is on main 7e2ba73 with the series (ci/macos-series-2 on my fork) and the libSceSsl change of #7332; main has not changed these files since.

  • A probe, built for x86-64 under Rosetta and for arm64: a 16 MiB send with MSG_DONTWAIT on a blocking loopback socket with 4 KiB buffers blocks until the peer shuts down 2 s later and then fails with EPIPE. With MSG_DONTWAIT | 0x20000, or on an O_NONBLOCK socket, it returns 65,328 at once.
  • Without this PR the seven tests fail; with it all eleven net_dontwait tests pass, 10 runs of 10.
  • The other socket tests of libSceNet and libkernel (guest_tcp, guest_udp, guest_kqueue, guest_socket_*, the other net_* tests) give the same results with and without it. guest_sce_net, guest_poll and guest_socket_pair fail either way; fix(libSceNet): match FreeBSD's inet_pton and socket errors on macOS #9231, @011010's announced guest_poll change and fix(libkernel): treat a socket direction that is already shut as shut on macOS hosts #9625 cover them.
  • Linux and Windows: not run. Every change is in an __APPLE__ branch.

Checklist

  • Based on current main; no other open PR implements the same functions (feat: macOS host support (Apple Silicon via Rosetta 2, Vulkan on MoltenVK) #8658 carries a version inside its full port)
  • 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 (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

…acOS hosts

Two stream send behaviours of XNU differ from FreeBSD and Linux:

- MSG_DONTWAIT does not keep a stream send on a blocking socket from
  waiting for buffer space; XNU only skips the wait for the send buffer
  lock. A 16 MiB send to a slow peer blocked until the peer went away.
  A MSG_DONTWAIT transfer now also passes XNU's MSG_NBIO (0x20000), which
  makes one call non-blocking: the send queues what fits and returns that
  count, or EAGAIN when nothing fits.
- A blocking stream send that fails after queuing data, for example when
  sceNetSocketAbort shuts the socket down, returns only the error, where
  Linux returns the bytes queued. A blocking stream send now goes to the
  host in sends of 64 KiB and reports the bytes of the sends that
  completed when a later one fails, as boykopovar#8658 does.

The debt entry records the undocumented flag and the bytes that a failing
64 KiB send queued but does not report.

Co-authored-by: iamthehimansh <67262056+iamthehimansh@users.noreply.github.com>

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

Second Mac, newer macOS. Head 6b47a3636.

Machine: Apple M2 Max, macOS 27.2 (26B5112a), Apple clang 21. The libraries do not link on main alone, so the base is #8658 @ fbb251c25 (merge-base 4777caca3, the same as this PR). I replaced #8658's own macOS send code (its SO_NWRITE room check and chunked send) with this PR's: MSG_NBIO in native_message_flags, and the 64 KiB loop without the EINTR retry that #8658 has. x86-64 build under Rosetta 2.

  • Without either fix, 7 of the 11 net_dontwait tests fail with the messages in the description ("MSG_DONTWAIT send blocked behind a slow peer…", "error discarded queued bytes"). With this PR, all 11 pass, 10 runs of 10 (ctest --repeat until-fail:10).
  • 0x20000 still works on macOS 27.2. A standalone probe gives the same results built for arm64 and for x86-64 under Rosetta. The setup is loopback TCP, 4 KiB SO_SNDBUF/SO_RCVBUF, a 16 MiB send, and a peer that closes after 2 s:
    • send with MSG_DONTWAIT: blocks 2005 ms, then fails with EPIPE.
    • send with MSG_DONTWAIT | 0x20000: returns 65328 at once. sendmsg with the same flags also returns 65328, and returns EAGAIN on the next call with the buffer full.
    • The flag passes on the other paths this PR sends it to: UDP sendto and recv accept it without EINVAL, and an empty TCP or UDP receive returns EAGAIN at once, as with MSG_DONTWAIT alone.
    • A blocking 16 MiB send, where another thread calls shutdown (SHUT_RDWR or SHUT_WR) on the socket after 300 ms: one host send returns -1 EPIPE. Sends of 64 KiB report 131072 bytes queued before the EPIPE.
  • Export.cpp from the PR head compiles with -fsyntax-only for arm64 and x86-64.
  • Not run: the guest socket tests (guest_tcp, guest_udp, ...), Linux, Windows.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@github-actions

github-actions Bot commented Oct 22, 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 5836 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 7201 core/libs/prx/libSceNet/Export.cpp
PR 7490 core/libs/prx/libSceNet/Export.cpp
PR 8225 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8232 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8234 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8235 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8254 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8362 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 8686 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9231 core/libs/prx/libSceNet/Export.cpp
PR 9357 docs/dev/debt/libSceNet.md
PR 9493 core/libs/prx/libSceNet/Export.cpp
PR 9494 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9506 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9515 core/libs/prx/libSceNet/Export.cpp
PR 9523 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9525 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9542 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9543 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9578 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9651 docs/dev/debt/libSceNet.md
PR 9715 core/libs/prx/libSceNet/Export.cpp
PR 9727 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9930 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9931 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9932 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 9948 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.md
PR 10097 docs/dev/debt/libSceNet.md
PR 10187 core/libs/prx/libSceNet/Export.cpp
PR 10502 core/libs/prx/libSceNet/Export.cpp
docs/dev/debt/libSceNet.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.

The two reported macOS runs (M5 Max and the independent M2 Max rerun) are useful evidence, and I found no issue with the short-send behavior in the exercised cases.

I reviewed head 6b47a36368eeb1a0e97d3bcdc4d0d42d7cbc0eea. Local checks:

  • Exact-head Export.cpp passed g++ -std=c++20 -fsyntax-only on the Linux path; this does not compile the __APPLE__ branch.
  • A temporary harness extracted the exact helper and passed full chunking, progress followed by an error, and zero-length cases. It does not exercise Darwin socket semantics or detect all undefined behavior.
  • git diff --check and python3 tools/check_conventions.py --base upstream/main --head refs/review/pr9733 passed.
  • I could not run the Darwin socket suite in this Linux environment.

Before merge, please refresh the PR from current main (its base is 4777caca, 54 commits behind b7d1d47a) and rerun the focused macOS comparison on the refreshed head. The current open #8658 also changes the same Export.cpp send paths and contains a 64 KiB chunk helper. CONTRIBUTING requires agreement with the overlapping PR author on landing order and recording the dependency; this PR currently says Depends on: none. Please coordinate with #8658’s author and update the checklist to reflect the agreed order.

One edge case in the helper: the public functions permit buf == nullptr when len == 0, but the blocking-stream do loop still evaluates data + transferred (nullptr + 0) before calling the host send. Please bypass the pointer addition for zero length (while preserving the zero-length send) and add coverage. This is a C++ undefined-behavior concern; the temporary runtime harness passed but cannot rule it out.

@Gonic0

Gonic0 commented Oct 22, 2026

Copy link
Copy Markdown

Second data point on a different macOS version: M5 Pro, macOS 27.2 (26B5086k), Apple clang 21, x86-64 under Rosetta 2, Release. My tree is main with the community macOS series merged (65b2331e6), and I applied this PR's diff at 6b47a3636 on top.

  • Without this PR: the same seven fail with the same messages ("MSG_DONTWAIT send blocked behind a slow peer or an ordinary receive" for send/sendto/sendmsg/duplex, "error discarded queued bytes" for the three blocking_*).
  • With it: all eleven net_dontwait tests pass, 10 runs of 10.
  • Together with fix(libSceNet): match FreeBSD's inet_pton and socket errors on macOS #9231 on top as well: guest_sce_net passes 5/5 and the eleven net_dontwait still pass 10/10, so the two compose. Across the 67 network tests (net|socket|udp|tcp|refused|datagram|poll|select) the only failure is guest_udp, which already fails in my tree without either PR.

So MSG_NBIO behaves the same on 27.2 as on 26.5.1.

AI-assisted: yes

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 needs rebase Conflicts with main, rebase needed prx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants