Skip to content

fix(windows): preserve data-path backpressure - #3704

Open
mozharovsky wants to merge 1 commit into
apple:mainfrom
rorkai:fix/windows-data-path-backpressure
Open

fix(windows): preserve data-path backpressure#3704
mozharovsky wants to merge 1 commit into
apple:mainfrom
rorkai:fix/windows-data-path-backpressure

Conversation

@mozharovsky

@mozharovsky mozharovsky commented Aug 15, 2026

Copy link
Copy Markdown

Treat WSAEWOULDBLOCK as nonblocking backpressure across the existing Windows data path.

Motivation:

Nonblocking Winsock operations report backpressure by returning SOCKET_ERROR and setting WSAEWOULDBLOCK. The existing accept and connect wrappers already classify this condition as nonfatal, and #3699 does the same for its new sendmmsg implementation. The existing recv, send, writev, recvmsg, and sendmsg wrappers instead throw IOError before the channel can wait for readiness.

In a downstream production workload, this surfaced as Windows error 10035 and an uncleanShutdown about two seconds into bulk TLS uploads, while low-volume traffic remained unaffected. The same mapping has been shipping in a downstream fork and eliminates the failure.

Fixes #3697.
Fixes #3702.

Modifications:

  • Added two internal winsockSyscall(where:_:) overloads that mirror Posix.syscall and use the same where function: String = #function convention.
  • The single-closure overload owns the Winsock call and reads WSAGetLastError() immediately when the call returns SOCKET_ERROR.
  • The transferred-count overload additionally owns the DWORD out-parameter. It reads the count only after success and ignores any value a failed call leaves in that slot.
  • Routed recv and send through the single-closure overload, and routed recvmsg, sendmsg, and writev through the transferred-count overload without changing any public API.
  • Kept accept and connect unchanged. Unifying their nonblocking handling is a separate NFC follow-up.
  • Added focused Windows tests covering success, would-block, and other-error behavior for both overloads, including proof that a failed transferred-count call never exposes garbage from its out-parameter.
  • Kept the cross-platform TCP loopback regression test unchanged. It constrains the socket buffers, queues four MiB while reads are paused, verifies that the write encounters kernel backpressure, resumes reads, and compares the complete payload byte for byte.

Neither overload has an EINTR retry loop. WSAEINTR only arises from the legacy WSACancelBlockingCall, which does not apply to the nonblocking sockets used by these data paths.

Using #function deliberately changes the five IOError reason strings from bare implementation names such as "recv" and "WSASend" to the full caller signatures such as "recv(socket:buffer:length:)" and "writev(socket:iovecs:)". The error codes and public API are unchanged.

This complements #3699 and #3700. I am happy to rebase over them when they land and fold the sendmmsg call into the shared wrapper as a follow-up.

Result:

The stream regression test fails on unpatched Windows with WSAEWOULDBLOCK surfaced as A non-blocking socket operation could not be completed immediately. Both wrapper overloads and the unchanged stream regression pass with this change on Windows x64 using the current development toolchain:

Swift version 6.5-dev (LLVM 22dd59a69519855, Swift 20e4c30c4a5c55c)
Target: x86_64-unknown-windows-msvc
Build config: +assertions

Test Suite 'BSDSocketAPIWindowsTests' passed
Executed 5 tests, with 0 failures (0 unexpected) in 0.005 seconds

Test Case 'SocketChannelTest.testStreamWritesSurviveSocketBackpressure' passed (0.418 seconds)
Executed 1 test, with 0 failures (0 unexpected) in 0.418 seconds

The full macOS suite passes with 2,460 XCTest tests and 276 Swift Testing tests. The Swift 6.2 formatter check, API breakage check, unacceptable-language check, license-header check, and broken-symlink check all pass locally.

Mirror Posix.syscall for Winsock data calls and cover both wrapper variants plus full stream delivery under forced backpressure.
@mozharovsky
mozharovsky force-pushed the fix/windows-data-path-backpressure branch from 92c33bd to b320d90 Compare August 17, 2026 17:54
@Budoman

Budoman commented Aug 21, 2026

Copy link
Copy Markdown

Independent verification from the Hummingbird side, on a clean windows-2022 GitHub runner (Swift 6.3.3).

Applied this branch (rorkai/swift-nio @ fix/windows-data-path-backpressure, b320d903) as Hummingbird PR #747's swift-nio dependency, then ran the two tests that opened #3697:

  • testStreamBody (HummingbirdCore): passed
  • testConsumeWithCancellationOnInboundClose (Hummingbird): passed

Both were failing with "stream ended at an unexpected time" before this change. With it, the full streamed-body set is green (testStreamBody, testConsumeWithCancellationOnInboundClose, testStreamBodySlowStream, testStreamBodyWriteSlow; 4 tests / 2 suites, all passing). So the data-path backpressure fix clears the user-visible Hummingbird failures, not just the low-level case. Thanks for the quick turnaround.

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.

Windows data path treats WSAEWOULDBLOCK as fatal (recv/send/writev) [Windows] WSASend error in bi-directional stream tests of Hummingbird

2 participants