Skip to content

[Windows] Report the errno equivalent of winsock errors - #3700

Open
jakepetroules wants to merge 2 commits into
apple:mainfrom
jakepetroules:windows-winsock-errno-stacked
Open

[Windows] Report the errno equivalent of winsock errors#3700
jakepetroules wants to merge 2 commits into
apple:mainfrom
jakepetroules:windows-winsock-errno-stacked

Conversation

@jakepetroules

@jakepetroules jakepetroules commented Aug 13, 2026

Copy link
Copy Markdown
Member

Motivation:

Depends on #3699. That PR is the first commit on this branch. Please review only the second commit, or wait until #3699 is merged.

IOError.errnoCode called fatalError when the error came from Winsock. NIO's own code reads errnoCode to classify errors, so it walked into that trap:

  • PendingDatagramWritesManager compares the error with EMSGSIZE and EHOSTUNREACH. It uses the result to decide if a datagram write error can be recovered.
  • BaseSocket compares it with EAFNOSUPPORT when it turns off IPV6_V6ONLY. This code runs for every IPv6 socket NIO creates.

The tests also compare errnoCode with errno values in about 38 places.

Modifications:

A Winsock code means the same thing as its errno counterpart, so this translates it. WSAEMSGSIZE reports EMSGSIZE, and so on for about 34 codes. Code that checks errno then works on Windows with no changes at the comparison sites.

A code with no errno counterpart is returned unchanged instead of trapping. This is safe, not only convenient: errno values on Windows are never higher than 140, and Winsock codes start at 10004. A test checks this.

One detail for reviewers: on Windows, EWOULDBLOCK and EAGAIN have different values. Winsock reports would-block as WSAEWOULDBLOCK, so it maps to EWOULDBLOCK. All of NIO's EAGAIN checks are in POSIX-only code today.

Result:

With this and #3699, DatagramChannelTests runs to the end on Windows for the first time: 74 tests, 38 pass, 19 fail, 17 skipped. Before, the test process stopped in the middle.

The 19 failures are separate gaps, so the suite stays skipped. This PR removes no skips.

Tested on a Windows ARM64 machine (Swift 6.3.2): a full swift test passes (2360 tests). NIOCore still builds on macOS.

`CNIOWindows_sendmmsg` was a stub that asserted and then called `abort()`, so any
code path reaching it took the whole process down. That is the datagram write
path whenever more than one write is pending, which made
`DatagramChannelTests` and `RawSocketBootstrapTests` unrunnable: both aborted
partway through, taking the rest of the test process with them.

Winsock has no `sendmmsg`, so emulate it by sending the messages one at a time,
which is what the Darwin shim does for the same reason. Implement it in Swift
alongside the existing `sendmsg`, rather than filling in the C shim, since
everything needed is callable directly from Swift; the now-unused C stub and its
declaration are removed. The `WSASendMsg` extension function is resolved once
per batch rather than once per message, so the lookup is hoisted into a helper
shared with `sendmsg`.

The error semantics match the other implementations: a failure on the first
message is reported to the caller, with `WSAEWOULDBLOCK` reported as
`.wouldBlock` the way the POSIX implementation's `syscall(blocking: true)`
wrapper does, while a failure after some messages have been sent is reported as
a short send for the caller to retry.

This does not yet make either test suite pass, so no skips are removed here.
Both now get further before failing for unrelated reasons: the datagram tests
reach a `fatalError` in `PendingDatagramWritesManager`, which reads `errnoCode`
on an `IOError` that carries a winsock-domain code, and the raw socket tests
fail with "Already closed". Crucially, neither aborts the test process any more.
Depends on apple#3699.

`IOError.errnoCode` trapped with `fatalError("IOError domain is not errno")`
whenever the error carried a winsock- or windows-domain code, which NIO's own
error classification then walked straight into. `PendingDatagramWritesManager`
compares against `EMSGSIZE` and `EHOSTUNREACH` to decide whether a datagram
write error is recoverable, and `BaseSocket` compares against `EAFNOSUPPORT`
when it turns off `IPV6_V6ONLY` -- the latter on the path of every IPv6 socket
NIO creates.

Winsock's error codes mean the same things as their errno counterparts, so
translate them rather than trapping: `WSAEMSGSIZE` reports `EMSGSIZE`, and so
on. Error handling written against errno then keeps working on Windows, which
also matters for the tests, where there are ~38 comparisons against errno
constants.

Codes with no errno counterpart, and windows-domain codes, are reported
unchanged rather than trapping. That is unambiguous: Windows CRT errno values
never exceed 140, while winsock and Win32 codes are far larger, so such a value
cannot be mistaken for an errno.

Note that the Windows CRT gives `EWOULDBLOCK` and `EAGAIN` distinct values,
unlike the platforms where they are synonyms, so `WSAEWOULDBLOCK` is paired with
`EWOULDBLOCK`.

With this and apple#3699, `DatagramChannelTests` runs to completion on Windows for
the first time (74 tests: 38 pass, 19 fail, 17 skipped) rather than aborting the
test process. The remaining failures are unrelated gaps, so the suite stays
skipped for now and no skips are removed here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant