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

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.

@mozharovsky
mozharovsky force-pushed the fix/windows-data-path-backpressure branch 2 times, most recently 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.

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 b320d90 to 15c10a3 Compare August 25, 2026 20:37
@mozharovsky

Copy link
Copy Markdown
Author

Rebased onto current main after #3699 landed, the conflict was the expected overlap in BSDSocketAPIWindows.swift. The two wrappers, the five routed data-path sites and the tests are functionally unchanged from b320d90 that @Budoman verified. #3700 is still open, I'll rebase again if needed once it lands.

@Budoman

Budoman commented Aug 25, 2026

Copy link
Copy Markdown

Re-verified the rebased branch (15c10a3) from the Hummingbird side, same clean windows-2022 runner + Swift 6.3.3 as the earlier full-suite run.

Targeted the streaming family that was previously red: swift test --filter "testStreamBody|testConsumeWithCancellationOnInboundClose" with Hummingbird PR #747's head and this branch as the swift-nio dependency. Result: 4 of 4 pass, including testStreamBodySlowStream and the inbound-close cancellation test, both of which failed in my August 21 full-suite run on b320d903 (they were the two #3702-class failures noted there). So the rebase onto post-#3699 main plus this branch clears the family entirely on Windows.

Pin for reproducibility: Package.resolved recorded rorkai/swift-nio @ 15c10a30cb37b282e93a63c9cf5b6f82b7e83a9e.

@mozharovsky

Copy link
Copy Markdown
Author

Thanks for re-running it on the rebase @Budoman!

@jakepetroules this should be ready for a look whenever you get a chance - rebased over #3699 and Hummingbird has verified both the original branch and the rebase independently on a clean runner.

@jakepetroules

Copy link
Copy Markdown
Member

This looks reasonable to me. @fabianfett / @glbrntt can we get a maintainer review please?

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

3 participants