winio: load the msg extension functions from Haskell, and fix a spurious EOF - #626
Merged
Merged
Conversation
Winsock exposes WSASendMsg and WSARecvMsg only through WSAIoctl with SIO_GET_EXTENSION_FUNCTION_POINTER, and cmsg.c wrapped them in C functions of the same name that fetched the pointer on first use behind an unsynchronised static. C cannot wait for a completion event from the RTS, so the call could not be issued asynchronously from there; that was the TODO cmsg.c carried. Move the lookup into Network.Socket.Win32.Load, which caches the pointer in a TVar and hands out a safe wrapper for MIO and an unsafe one for WinIO, and call it from Buffer.hsc and ByteString/IO.hsc. The control buffer fix-up on WSAEMSGSIZE moves to Haskell along with it. Picked from haskell#611. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
recvBufFromWinIO, recvBufWinIO and recvBufMsgWinIO returned CbDone Nothing when WSARecv/WSARecvFrom/WSARecvMsg completed synchronously. An overlapped socket still queues a completion packet in that case, and withOverlappedEx answers CbDone Nothing by trying getOverlappedResult with bWait = False and, when that yields nothing, reading the OVERLAPPED structure directly. If the packet has not landed yet the caller is handed 0 bytes, which reads as EOF: getContents returned "" instead of the data in 13 of 20 runs of the test suite under --io-manager=native. Return CbPending instead, as the three send paths already do, and let the I/O manager resolve the completion with a blocking getOverlappedResult. 0 of 20 runs fail afterwards; MIO is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two WinIO changes, on top of #624.
1. Load
WSASendMsg/WSARecvMsgfrom Haskell (9b78ba9)Picked from #611.
cbits/cmsg.cdefined C wrappers namedWSASendMsgandWSARecvMsgthat fetched the extension function pointer on first use behind anunsynchronised
static, and carried this comment:The lookup now lives in
Network.Socket.Win32.Load, which caches the pointer ina
TVarand hands out asafewrapper for MIO and anunsafeone for WinIO;the C side is reduced to
loadWSASendMsg/loadWSARecvMsg, which just return thepointer. The
WSAEMSGSIZEcontrol-buffer fix-up moves to Haskell with it.Two deliberate differences from #611:
FunPtrrather than the wrapped function, because masterselects between MIO and WinIO with
<!>and so needs both asafeand anunsafewrapper for the same pointer.Unloadedif the lookup fails. Add experimental WinIO support #611 leaves it atLoading, which would block any other thread inretryforever.2. Do not shortcut a synchronous recv completion (
c22363a)This is a bug fix, independent of the above.
recvBufFromWinIO,recvBufWinIOandrecvBufMsgWinIOreturnedCbDone Nothingwhen theWSARecv*call completed synchronously. An overlappedsocket still queues a completion packet in that case, and
withOverlappedExanswers
CbDone Nothingby tryinggetOverlappedResultwithbWait = Falseand, when that yields nothing, reading the
OVERLAPPEDstructure directly:If the packet has not landed yet, the caller is handed 0 bytes, which reads as
EOF.
Network.Socket.ByteString.Lazy.getContentsthen returns""instead ofthe data.
Returning
CbPendinginstead lets the I/O manager resolve the completion with ablocking
getOverlappedResult. The three send paths (sendBufToWinIO,sendBufWinIO,sendBufMsgWinIO) already did this; only the three recv pathsdid not.
Testing
CI does not exercise WinIO, so a green CI run does not validate either
change. Two reasons:
configure.acmatchescase "$host_os" in mingw*), but cabal runsconfigureunder an MSYS shell, so autoconf resolves the host tox86_64-pc-msysandconfig.logsayschecking whether to use native Windows I/O manager RTS option ... no, not Windows.-with-rtsopts=--io-manager=nativeis never added.__IO_MANAGER_WINIO__ >= 2yet. The backport(
Event/Windows.hsc: rethrow exceptions in overlapped IO) landed onghc-9.12/ghc-9.14in April 2026, after 9.14.1 was released, so CI'sGHC 9.14 still reports
1and the WinIO code is compiled out.Measured locally instead, with GHC 9.12.5-rc3 (
9.12.4.20260713, which doesdefine
__IO_MANAGER_WINIO__ = 2) on Windows 11 arm64, running the whole suite20 times per I/O manager:
--io-manager=native--io-manager=posix923f7d5)The failure was always
Network.Socket.ByteString.Lazy.getContents works well(
expected: "This is a test message." but got: "").Also checked: GHC 9.8.2 on Windows (
__IO_MANAGER_WINIO__ = 1, WinIO compiledout) passes, and macOS builds.
Caveat: only the
recvBufWinIOpath was reproduced and re-measured. The samechange was applied to
recvBufFromWinIOandrecvBufMsgWinIObecause they hadthe identical pattern, but neither was shown to fail on its own.
Refs #602, #611. Thanks to @mrkline for the loader work in #611.
🤖 Generated with Claude Code