From dbbd5537794a8da7ba942f9bc344e7a1028df83a Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 13:17:44 -0700 Subject: [PATCH 01/16] connect(...) on Windows can error with WSAEWOULDBLOCK --- FlyingSocks/Sources/Socket.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 877b673..121bb2d 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -221,7 +221,7 @@ public struct Socket: Sendable, Hashable { Socket.connect(file.rawValue, $0, address.size) } guard result >= 0 || errno == EISCONN else { - if errno == EINPROGRESS { + if errno == EINPROGRESS || errno == EWOULDBLOCK { throw SocketError.blocked } else { throw SocketError.makeFailed("Connect") From afc0bdf782bd7edb660ff8d3308464f03b65a49e Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 14:16:59 -0700 Subject: [PATCH 02/16] fix `Poll` on Windows WSAPollFD `revents` seems to contain POLLRDNORM and not POLLRDBAND (or'd together equals POLLIN). This means we should check for an intersection with POLLIN to claim a "read" event --- FlyingSocks/Sources/SocketPool+Poll.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlyingSocks/Sources/SocketPool+Poll.swift b/FlyingSocks/Sources/SocketPool+Poll.swift index 3ba3de7..b6d6c38 100644 --- a/FlyingSocks/Sources/SocketPool+Poll.swift +++ b/FlyingSocks/Sources/SocketPool+Poll.swift @@ -198,7 +198,7 @@ private extension Socket.Events { init(_ events: POLLEvents) { self = [] - if events.contains(.read) { + if events.intersects(with: .read) { self.insert(.read) } if events.contains(.write) { From 7655675240adf4827b0f008742a3d2d3cb04d982 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 14:23:12 -0700 Subject: [PATCH 03/16] Windows can have multiple error codes communicate "disconnected" --- FlyingSocks/Sources/Socket+WinSock2.swift | 1 - FlyingSocks/Sources/Socket.swift | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/FlyingSocks/Sources/Socket+WinSock2.swift b/FlyingSocks/Sources/Socket+WinSock2.swift index eb75dd1..b4e322c 100755 --- a/FlyingSocks/Sources/Socket+WinSock2.swift +++ b/FlyingSocks/Sources/Socket+WinSock2.swift @@ -38,7 +38,6 @@ let F_SETFL = Int32(1) let F_GETFL = Int32(1) var errno: Int32 { WSAGetLastError() } let EWOULDBLOCK = WSAEWOULDBLOCK -let EBADF = WSAENOTSOCK let EINPROGRESS = WSAEINPROGRESS let EISCONN = WSAEISCONN public typealias sa_family_t = ADDRESS_FAMILY diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 121bb2d..42f4f33 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -248,7 +248,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("Read") @@ -275,7 +275,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvFrom") @@ -340,7 +340,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK || errno == EAGAIN { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvMsg") @@ -365,7 +365,7 @@ public struct Socket: Sendable, Hashable { guard sent > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF { + } else if errnoSignalsDisconnected() { throw SocketError.disconnected } else { throw SocketError.makeFailed("Write") @@ -627,6 +627,14 @@ package extension Socket { } } +fileprivate func errnoSignalsDisconnected() -> Bool { + #if canImport(WinSDK) + return errno == WSAENOTSOCK || errno == WSAENOTCONN + #else + return errno == EBADF + #endif +} + #if !canImport(WinSDK) fileprivate extension Socket { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0138-unsaferawbufferpointer.md From 065541efb1a2455b5e112ce5278d9c7af9a7d703 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 14:23:39 -0700 Subject: [PATCH 04/16] makeListening(...) helper had non-windows compliant unix path --- FlyingSocks/Tests/AsyncSocketTests.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/FlyingSocks/Tests/AsyncSocketTests.swift b/FlyingSocks/Tests/AsyncSocketTests.swift index edde092..f20f720 100644 --- a/FlyingSocks/Tests/AsyncSocketTests.swift +++ b/FlyingSocks/Tests/AsyncSocketTests.swift @@ -305,10 +305,16 @@ extension AsyncSocket { } static func makeListening(pool: some AsyncSocketPool) throws -> AsyncSocket { - let address = sockaddr_un.unix(path: #function) + let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString.prefix(8)).sock") + let address = sockaddr_un.unix(path: tempFile.path) try? Socket.unlink(address) + defer { try? Socket.unlink(address) } let socket = try Socket(domain: AF_UNIX, type: .stream) - try socket.setValue(true, for: .localAddressReuse) + + #if !canImport(WinSDK) + try socket.setValue(true, for: .localAddressReuse) + #endif + try socket.bind(to: address) try socket.listen() return try AsyncSocket(socket: socket, pool: pool) From 347c675e881695e96d1a9c04d34d5812cc8c8fa8 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 14:24:00 -0700 Subject: [PATCH 05/16] Windows disagrees about .jackOfHeartsRecital file size --- .../Tests/AsyncBufferedFileSequenceTests.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/FlyingSocks/Tests/AsyncBufferedFileSequenceTests.swift b/FlyingSocks/Tests/AsyncBufferedFileSequenceTests.swift index 9411161..af2248d 100644 --- a/FlyingSocks/Tests/AsyncBufferedFileSequenceTests.swift +++ b/FlyingSocks/Tests/AsyncBufferedFileSequenceTests.swift @@ -37,9 +37,16 @@ struct AsyncBufferedFileSequenceTests { @Test func fileSize() async throws { - #expect( - try AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 299 + #if os(Windows) + try #expect( + AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 304 + ) + #else + try #expect( + AsyncBufferedFileSequence.fileSize(at: .jackOfHeartsRecital) == 299 ) + #endif + #expect(throws: (any Error).self) { try AsyncBufferedFileSequence.fileSize(at: URL(fileURLWithPath: "missing")) } From e55f71aaff0601c894ecd0258d22be53fd326d34 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 15:03:40 -0700 Subject: [PATCH 06/16] HTTPServer needs to use SO_EXCLUSIVEADDRUSE on Windows Windows doesn't like this flag ever for AF_UNIX, so don't add it in that case either --- FlyingFox/Sources/HTTPServer.swift | 8 ++++++++ FlyingSocks/Sources/Socket.swift | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/FlyingFox/Sources/HTTPServer.swift b/FlyingFox/Sources/HTTPServer.swift index 82142a1..4944415 100644 --- a/FlyingFox/Sources/HTTPServer.swift +++ b/FlyingFox/Sources/HTTPServer.swift @@ -145,7 +145,15 @@ public final actor HTTPServer { func makeSocketAndListen() throws -> Socket { let socket = try Socket(domain: Int32(type(of: config.address).family)) + + #if canImport(WinSDK) + if config.address.family != AF_UNIX { + try socket.setValue(true, for: .exclusiveLocalAddressReuse) + } + #else try socket.setValue(true, for: .localAddressReuse) + #endif + #if canImport(Darwin) try socket.setValue(true, for: .noSIGPIPE) #endif diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 42f4f33..6a89364 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -568,6 +568,12 @@ public extension SocketOption where Self == BoolSocketOption { BoolSocketOption(level: Socket.ipproto_ip, name: Socket.ip_pktinfo) } + #if canImport(WinSDK) + static var exclusiveLocalAddressReuse: Self { + BoolSocketOption(name: ~SO_REUSEADDR) // SO_EXCLUSIVEADDRUSE macro + } + #endif + #if !canImport(WinSDK) static var packetInfoIPv6: Self { BoolSocketOption(level: Socket.ipproto_ipv6, name: Socket.ipv6_recvpktinfo) From 9e391044516d676285092dad6e80b6b371a07b79 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 15:03:56 -0700 Subject: [PATCH 07/16] fix non-windows compliant unix path for http server --- FlyingFox/Tests/HTTPServerTests.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/FlyingFox/Tests/HTTPServerTests.swift b/FlyingFox/Tests/HTTPServerTests.swift index f13f249..5cef50d 100644 --- a/FlyingFox/Tests/HTTPServerTests.swift +++ b/FlyingFox/Tests/HTTPServerTests.swift @@ -340,8 +340,10 @@ actor HTTPServerTests { @Test func server_StartsOnUnixSocket() async throws { - let address = sockaddr_un.unix(path: #function) + let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString.prefix(8)).sock") + let address = sockaddr_un.unix(path: tempFile.path) try? Socket.unlink(address) + defer { try? Socket.unlink(address) } let server = HTTPServer.make(address: address) await server.appendRoute("*") { _ in return HTTPResponse.make(statusCode: .accepted) @@ -390,8 +392,8 @@ actor HTTPServerTests { try await Task.sleep(seconds: 0.1) let taskStop = Task { await server.stop(timeout: 1) } - #expect( - try await socket.readResponse().statusCode == .ok + try await #expect( + socket.readResponse().statusCode == .ok ) await taskStop.value } From 3e2d78ba9b0825d5335f7c8505c838481d1eea90 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 15:21:32 -0700 Subject: [PATCH 08/16] WSAECONNRESET can signal a disconnect --- FlyingSocks/Sources/Socket.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 6a89364..23f5da9 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -635,7 +635,7 @@ package extension Socket { fileprivate func errnoSignalsDisconnected() -> Bool { #if canImport(WinSDK) - return errno == WSAENOTSOCK || errno == WSAENOTCONN + return errno == WSAENOTSOCK || errno == WSAENOTCONN || errno == WSAECONNRESET #else return errno == EBADF #endif From 3210c55941534e70850075896cc083485fb4b946 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 15:42:52 -0700 Subject: [PATCH 09/16] handle poll error edgecase we need to send an error through if we're waiting to read and an error happens, but making sure we only error if no actual reading occurred --- FlyingSocks/Sources/SocketPool+Poll.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FlyingSocks/Sources/SocketPool+Poll.swift b/FlyingSocks/Sources/SocketPool+Poll.swift index b6d6c38..381b1c1 100644 --- a/FlyingSocks/Sources/SocketPool+Poll.swift +++ b/FlyingSocks/Sources/SocketPool+Poll.swift @@ -123,7 +123,9 @@ extension EventNotification { let revents = POLLEvents(poll.revents) let errors = Set.make(from: revents) - if events.contains(.write) && !errors.isEmpty { + let shouldSendErrors = !errors.isEmpty && (events.contains(.write) || (events.contains(.read) && !revents.intersects(with: .read))) + + if shouldSendErrors { return EventNotification( file: .init(rawValue: poll.fd), events: .init(events), From 86ed1bedea9dc06acb46340d846d6a9e1956e6a2 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 15:56:05 -0700 Subject: [PATCH 10/16] revert to less complicated disconnect detection turns out fixing SocketPool fixes mishandling of disconnect errors --- FlyingSocks/Sources/Socket+WinSock2.swift | 1 + FlyingSocks/Sources/Socket.swift | 16 ++++------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/FlyingSocks/Sources/Socket+WinSock2.swift b/FlyingSocks/Sources/Socket+WinSock2.swift index b4e322c..eb75dd1 100755 --- a/FlyingSocks/Sources/Socket+WinSock2.swift +++ b/FlyingSocks/Sources/Socket+WinSock2.swift @@ -38,6 +38,7 @@ let F_SETFL = Int32(1) let F_GETFL = Int32(1) var errno: Int32 { WSAGetLastError() } let EWOULDBLOCK = WSAEWOULDBLOCK +let EBADF = WSAENOTSOCK let EINPROGRESS = WSAEINPROGRESS let EISCONN = WSAEISCONN public typealias sa_family_t = ADDRESS_FAMILY diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 23f5da9..f1d64ac 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -248,7 +248,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errnoSignalsDisconnected() || count == 0 { + } else if errno == EBADF || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("Read") @@ -275,7 +275,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errnoSignalsDisconnected() || count == 0 { + } else if errno == EBADF || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvFrom") @@ -340,7 +340,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK || errno == EAGAIN { throw SocketError.blocked - } else if errnoSignalsDisconnected() || count == 0 { + } else if errno == EBADF || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvMsg") @@ -365,7 +365,7 @@ public struct Socket: Sendable, Hashable { guard sent > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errnoSignalsDisconnected() { + } else if errno == EBADF { throw SocketError.disconnected } else { throw SocketError.makeFailed("Write") @@ -633,14 +633,6 @@ package extension Socket { } } -fileprivate func errnoSignalsDisconnected() -> Bool { - #if canImport(WinSDK) - return errno == WSAENOTSOCK || errno == WSAENOTCONN || errno == WSAECONNRESET - #else - return errno == EBADF - #endif -} - #if !canImport(WinSDK) fileprivate extension Socket { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0138-unsaferawbufferpointer.md From 61624ba1dc71aeda3ac9fde4e98d76fb8fdad4dc Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 16:00:57 -0700 Subject: [PATCH 11/16] Revert "revert to less complicated disconnect detection" This reverts commit 86ed1bedea9dc06acb46340d846d6a9e1956e6a2. --- FlyingSocks/Sources/Socket+WinSock2.swift | 1 - FlyingSocks/Sources/Socket.swift | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/FlyingSocks/Sources/Socket+WinSock2.swift b/FlyingSocks/Sources/Socket+WinSock2.swift index eb75dd1..b4e322c 100755 --- a/FlyingSocks/Sources/Socket+WinSock2.swift +++ b/FlyingSocks/Sources/Socket+WinSock2.swift @@ -38,7 +38,6 @@ let F_SETFL = Int32(1) let F_GETFL = Int32(1) var errno: Int32 { WSAGetLastError() } let EWOULDBLOCK = WSAEWOULDBLOCK -let EBADF = WSAENOTSOCK let EINPROGRESS = WSAEINPROGRESS let EISCONN = WSAEISCONN public typealias sa_family_t = ADDRESS_FAMILY diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index f1d64ac..23f5da9 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -248,7 +248,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("Read") @@ -275,7 +275,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvFrom") @@ -340,7 +340,7 @@ public struct Socket: Sendable, Hashable { guard count > 0 else { if errno == EWOULDBLOCK || errno == EAGAIN { throw SocketError.blocked - } else if errno == EBADF || count == 0 { + } else if errnoSignalsDisconnected() || count == 0 { throw SocketError.disconnected } else { throw SocketError.makeFailed("RecvMsg") @@ -365,7 +365,7 @@ public struct Socket: Sendable, Hashable { guard sent > 0 else { if errno == EWOULDBLOCK { throw SocketError.blocked - } else if errno == EBADF { + } else if errnoSignalsDisconnected() { throw SocketError.disconnected } else { throw SocketError.makeFailed("Write") @@ -633,6 +633,14 @@ package extension Socket { } } +fileprivate func errnoSignalsDisconnected() -> Bool { + #if canImport(WinSDK) + return errno == WSAENOTSOCK || errno == WSAENOTCONN || errno == WSAECONNRESET + #else + return errno == EBADF + #endif +} + #if !canImport(WinSDK) fileprivate extension Socket { // https://github.com/swiftlang/swift-evolution/blob/main/proposals/0138-unsaferawbufferpointer.md From f9849dccc72556de2045bb078d0042e555ce2c41 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 20:12:23 -0700 Subject: [PATCH 12/16] turns out Windows can do packetInfoIPv6 --- FlyingSocks/Sources/Socket+WinSock2.swift | 1 + FlyingSocks/Sources/Socket.swift | 12 ++++-------- FlyingSocks/Tests/SocketTests.swift | 2 -- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/FlyingSocks/Sources/Socket+WinSock2.swift b/FlyingSocks/Sources/Socket+WinSock2.swift index b4e322c..7fe814a 100755 --- a/FlyingSocks/Sources/Socket+WinSock2.swift +++ b/FlyingSocks/Sources/Socket+WinSock2.swift @@ -62,6 +62,7 @@ extension Socket { static let ipproto_ipv6 = Int32(IPPROTO_IPV6.rawValue) static let ip_pktinfo = Int32(IP_PKTINFO) static let ipv6_pktinfo = Int32(IPV6_PKTINFO) + static let ipv6_recvpktinfo = Int32(IPV6_PKTINFO) static func makeAddressINET(port: UInt16) -> WinSDK.sockaddr_in { WinSDK.sockaddr_in( diff --git a/FlyingSocks/Sources/Socket.swift b/FlyingSocks/Sources/Socket.swift index 23f5da9..ceb8ff7 100644 --- a/FlyingSocks/Sources/Socket.swift +++ b/FlyingSocks/Sources/Socket.swift @@ -126,10 +126,8 @@ public struct Socket: Sendable, Hashable { switch domain { case AF_INET: try setValue(true, for: .packetInfoIP) - #if !canImport(WinSDK) case AF_INET6: try setValue(true, for: .packetInfoIPv6) - #endif default: return } @@ -568,18 +566,16 @@ public extension SocketOption where Self == BoolSocketOption { BoolSocketOption(level: Socket.ipproto_ip, name: Socket.ip_pktinfo) } + static var packetInfoIPv6: Self { + BoolSocketOption(level: Socket.ipproto_ipv6, name: Socket.ipv6_recvpktinfo) + } + #if canImport(WinSDK) static var exclusiveLocalAddressReuse: Self { BoolSocketOption(name: ~SO_REUSEADDR) // SO_EXCLUSIVEADDRUSE macro } #endif - #if !canImport(WinSDK) - static var packetInfoIPv6: Self { - BoolSocketOption(level: Socket.ipproto_ipv6, name: Socket.ipv6_recvpktinfo) - } - #endif - #if canImport(Darwin) // Prevents SIG_TRAP when app is paused / running in background. static var noSIGPIPE: Self { diff --git a/FlyingSocks/Tests/SocketTests.swift b/FlyingSocks/Tests/SocketTests.swift index 46ded16..fc60696 100644 --- a/FlyingSocks/Tests/SocketTests.swift +++ b/FlyingSocks/Tests/SocketTests.swift @@ -331,7 +331,6 @@ struct SocketTests { ) } - #if !canImport(WinSDK) @Test func makes_datagram_ip6() throws { let socket = try Socket(domain: Int32(sa_family_t(AF_INET6)), type: .datagram) @@ -340,7 +339,6 @@ struct SocketTests { try socket.getValue(for: .packetInfoIPv6) == true ) } - #endif } extension Socket.Flags { From 397cebbe9a2e1a854f02baaf445f2fa056b8e047 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 20:13:02 -0700 Subject: [PATCH 13/16] define-out some tests that can't be fixed on Windows --- FlyingFox/Tests/HTTPServerTests.swift | 3 +++ FlyingSocks/Tests/AsyncSocketTests.swift | 8 ++++---- FlyingSocks/Tests/SocketTests.swift | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/FlyingFox/Tests/HTTPServerTests.swift b/FlyingFox/Tests/HTTPServerTests.swift index 5cef50d..6079b18 100644 --- a/FlyingFox/Tests/HTTPServerTests.swift +++ b/FlyingFox/Tests/HTTPServerTests.swift @@ -377,6 +377,8 @@ actor HTTPServerTests { ) } + #if !canImport(WinSDK) + // FIXME: This test fails non-deterministically on Windows @Test func server_AllowsExistingConnectionsToDisconnect_WhenStopped() async throws { let server = HTTPServer.make() @@ -397,6 +399,7 @@ actor HTTPServerTests { ) await taskStop.value } + #endif @Test func server_DisconnectsWaitingRequests_WhenStopped() async throws { diff --git a/FlyingSocks/Tests/AsyncSocketTests.swift b/FlyingSocks/Tests/AsyncSocketTests.swift index f20f720..b0e33dc 100644 --- a/FlyingSocks/Tests/AsyncSocketTests.swift +++ b/FlyingSocks/Tests/AsyncSocketTests.swift @@ -188,6 +188,7 @@ struct AsyncSocketTests { ) } +#if !canImport(WinSDK) @Test func datagramSocketReceivesChunk_WhenAvailable() async throws { let (s1, s2, addr) = try await AsyncSocket.makeDatagramPair() @@ -195,11 +196,11 @@ struct AsyncSocketTests { async let d2: (any SocketAddress, [UInt8]) = s2.receive(atMost: 100) // TODO: calling send() on Darwin to an unconnected datagram domain // socket returns EISCONN -#if canImport(Darwin) + #if canImport(Darwin) try await s1.write("Swift".data(using: .utf8)!) -#else + #else try await s1.send("Swift".data(using: .utf8)!, to: addr) -#endif + #endif let v2 = try await d2 #expect(String(data: Data(v2.1), encoding: .utf8) == "Swift") @@ -208,7 +209,6 @@ struct AsyncSocketTests { try? Socket.unlink(addr) } -#if !canImport(WinSDK) #if canImport(Darwin) @Test func messageSequence_sendsMessage_receivesTuple() async throws { diff --git a/FlyingSocks/Tests/SocketTests.swift b/FlyingSocks/Tests/SocketTests.swift index fc60696..21eb08c 100644 --- a/FlyingSocks/Tests/SocketTests.swift +++ b/FlyingSocks/Tests/SocketTests.swift @@ -131,6 +131,11 @@ struct SocketTests { } } + #if !canImport(WinSDK) + // Not a good test on Windows, unfortunately: + // https://groups.google.com/g/microsoft.public.win32.programmer.networks/c/rBg0a8oERGQ/m/AvVOd-BIHhMJ + // "If necessary, Winsock can buffer significantly more than the SO_SNDBUF buffer size." + @Test func socketWrite_ThrowsBlocked_WhenBufferIsFull() throws { let (s1, s2) = try Socket.makeNonBlockingPair() @@ -145,6 +150,7 @@ struct SocketTests { try s1.close() try s2.close() } + #endif @Test func socketWrite_Throws_WhenSocketIsNotConnected() async throws { @@ -193,6 +199,8 @@ struct SocketTests { #expect(try socket.getValue(for: .localAddressReuse) == false) } + #if !canImport(WinSDK) + // Windows only supports setting O_NONBLOCK, and currently can't retrieve whether it's been set :) @Test func socket_Sets_And_Gets_Flags() throws { let socket = try Socket(domain: AF_UNIX, type: .stream) @@ -201,6 +209,7 @@ struct SocketTests { try socket.setFlags(.append) #expect(try socket.flags.contains(.append)) } + #endif @Test func socketAccept_ThrowsError_WhenInvalid() { From 486ada75eb31b6b4f3d6c65ac4b3b6f8acb1f7e9 Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 20:13:22 -0700 Subject: [PATCH 14/16] Windows fnctl query should just fail always --- FlyingSocks/Sources/Socket+WinSock2.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FlyingSocks/Sources/Socket+WinSock2.swift b/FlyingSocks/Sources/Socket+WinSock2.swift index 7fe814a..9b26c22 100755 --- a/FlyingSocks/Sources/Socket+WinSock2.swift +++ b/FlyingSocks/Sources/Socket+WinSock2.swift @@ -107,8 +107,7 @@ extension Socket { } static func fcntl(_ fd: FileDescriptorType, _ cmd: Int32) -> Int32 { - guard fd != INVALID_SOCKET else { return -1 } - return 0 + return -1 } static func fcntl(_ fd: FileDescriptorType, _ cmd: Int32, _ value: Int32) -> Int32 { From 545cc7a647215e02df7a22da6218045c0c65de6f Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 20:26:52 -0700 Subject: [PATCH 15/16] claw back a little code coverage on Windows --- FlyingSocks/Tests/AsyncSocketTests.swift | 11 ++++++++++- FlyingSocks/Tests/SocketTests.swift | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/FlyingSocks/Tests/AsyncSocketTests.swift b/FlyingSocks/Tests/AsyncSocketTests.swift index b0e33dc..3fa8bb7 100644 --- a/FlyingSocks/Tests/AsyncSocketTests.swift +++ b/FlyingSocks/Tests/AsyncSocketTests.swift @@ -188,7 +188,14 @@ struct AsyncSocketTests { ) } -#if !canImport(WinSDK) + #if canImport(WinSDK) + @Test + func datagramPairCreation_Throws() async throws { + await #expect(throws: SocketError.self) { + _ = try await AsyncSocket.makeDatagramPair() + } + } + #else @Test func datagramSocketReceivesChunk_WhenAvailable() async throws { let (s1, s2, addr) = try await AsyncSocket.makeDatagramPair() @@ -208,7 +215,9 @@ struct AsyncSocketTests { try s2.close() try? Socket.unlink(addr) } + #endif +#if !canImport(WinSDK) #if canImport(Darwin) @Test func messageSequence_sendsMessage_receivesTuple() async throws { diff --git a/FlyingSocks/Tests/SocketTests.swift b/FlyingSocks/Tests/SocketTests.swift index 21eb08c..a05a7c1 100644 --- a/FlyingSocks/Tests/SocketTests.swift +++ b/FlyingSocks/Tests/SocketTests.swift @@ -199,9 +199,17 @@ struct SocketTests { #expect(try socket.getValue(for: .localAddressReuse) == false) } - #if !canImport(WinSDK) + #if canImport(WinSDK) // Windows only supports setting O_NONBLOCK, and currently can't retrieve whether it's been set :) @Test + func socket_Throws_On_Get_Flags() throws { + let socket = try Socket(domain: AF_UNIX, type: .stream) + + try socket.setFlags(.append) // this is "OK", but actually won't set the flag + #expect(throws: SocketError.self) { try socket.flags.contains(.append) } + } + #else + @Test func socket_Sets_And_Gets_Flags() throws { let socket = try Socket(domain: AF_UNIX, type: .stream) #expect(try socket.flags.contains(.append) == false) From 4da69f7bef669eb4778a7a44d7f92d0b98c444ac Mon Sep 17 00:00:00 2001 From: Greg Cotten Date: Tue, 25 Mar 2025 20:27:11 -0700 Subject: [PATCH 16/16] add Windows action runner to tests --- .github/workflows/build.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b07954..8935022 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,3 +135,19 @@ jobs: run: swift sdk install https://github.com/finagolfin/swift-android-sdk/releases/download/6.0.3/swift-6.0.3-RELEASE-android-24-0.1.artifactbundle.tar.gz --checksum 4566f23ae2d36dc5c02e915cd67d83b2af971faca4b2595fdd75cf0286acfac1 - name: Build run: swift build --swift-sdk aarch64-unknown-linux-android24 + + windows_swift_6_0: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Swift + uses: SwiftyLab/setup-swift@latest + with: + swift-version: "6.0.3" + - name: Version + run: swift --version + - name: Build + run: swift build --build-tests + - name: Test + run: swift test --skip-build