Skip to content

winio: load the msg extension functions from Haskell, and fix a spurious EOF - #626

Merged
kazu-yamamoto merged 4 commits into
haskell:masterfrom
kazu-yamamoto:winio-extra
Sep 21, 2026
Merged

kazu-yamamoto merged 4 commits into
haskell:masterfrom
kazu-yamamoto:winio-extra

Conversation

@kazu-yamamoto

Copy link
Copy Markdown
Collaborator

Two WinIO changes, on top of #624.

1. Load WSASendMsg/WSARecvMsg from Haskell (9b78ba9)

Picked from #611. cbits/cmsg.c defined C wrappers named WSASendMsg and
WSARecvMsg that fetched the extension function pointer on first use behind an
unsynchronised static, and carried this comment:

/* Sadly we can't perform this async for now as C code can't wait for
   completion events from the Haskell RTS.  This needs to be moved to
   Haskell on a re-designed async Network.  */

The lookup now lives in 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;
the C side is reduced to loadWSASendMsg/loadWSARecvMsg, which just return the
pointer. The WSAEMSGSIZE control-buffer fix-up moves to Haskell with it.

Two deliberate differences from #611:

  • The cache holds the FunPtr rather than the wrapped function, because master
    selects between MIO and WinIO with <!> and so needs both a safe and an
    unsafe wrapper for the same pointer.
  • The cache is reset to Unloaded if the lookup fails. Add experimental WinIO support #611 leaves it at
    Loading, which would block any other thread in retry forever.

2. Do not shortcut a synchronous recv completion (c22363a)

This is a bug fix, independent of the above.

recvBufFromWinIO, recvBufWinIO and recvBufMsgWinIO returned
CbDone Nothing when the WSARecv* call 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:

bytes <- if isJust rdata then return rdata
         else FFI.getOverlappedResult h lpol False
case bytes of
  Just res -> completionCB 0 res
  Nothing  -> do err      <- FFI.overlappedIOStatus lpol
                 numBytes <- FFI.overlappedIONumBytes lpol
                 completionCB err' (fromIntegral numBytes)

If the packet has not landed yet, the caller is handed 0 bytes, which reads as
EOF. Network.Socket.ByteString.Lazy.getContents then returns "" instead of
the data.

Returning CbPending instead lets the I/O manager resolve the completion with a
blocking getOverlappedResult. The three send paths (sendBufToWinIO,
sendBufWinIO, sendBufMsgWinIO) already did this; only the three recv paths
did not.

Testing

CI does not exercise WinIO, so a green CI run does not validate either
change.
Two reasons:

  • configure.ac matches case "$host_os" in mingw*), but cabal runs
    configure under an MSYS shell, so autoconf resolves the host to
    x86_64-pc-msys and config.log says
    checking whether to use native Windows I/O manager RTS option ... no, not Windows.
    -with-rtsopts=--io-manager=native is never added.
  • No released GHC defines __IO_MANAGER_WINIO__ >= 2 yet. The backport
    (Event/Windows.hsc: rethrow exceptions in overlapped IO) landed on
    ghc-9.12/ghc-9.14 in April 2026, after 9.14.1 was released, so CI's
    GHC 9.14 still reports 1 and the WinIO code is compiled out.

Measured locally instead, with GHC 9.12.5-rc3 (9.12.4.20260713, which does
define __IO_MANAGER_WINIO__ = 2) on Windows 11 arm64, running the whole suite
20 times per I/O manager:

--io-manager=native --io-manager=posix
master (923f7d5) 13/20 runs failed 0/20
this branch 0/20 runs failed 0/20

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 compiled
out) passes, and macOS builds.

Caveat: only the recvBufWinIO path was reproduced and re-measured. The same
change was applied to recvBufFromWinIO and recvBufMsgWinIO because they had
the 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

kazu-yamamoto and others added 4 commits September 21, 2026 14:43
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>
@kazu-yamamoto
kazu-yamamoto merged commit 58e2e16 into haskell:master Sep 21, 2026
19 checks passed
@kazu-yamamoto
kazu-yamamoto deleted the winio-extra branch September 21, 2026 07:48
@kazu-yamamoto kazu-yamamoto mentioned this pull request Sep 21, 2026
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