Skip to content

Commit 8c0a11e

Browse files
authored
Fix crash when using WebSockets with URLSession.shared (#5128)
1 parent 9cc5d4c commit 8c0a11e

File tree

2 files changed

+55
-4
lines changed

2 files changed

+55
-4
lines changed

Sources/FoundationNetworking/URLSession/WebSocket/WebSocketURLProtocol.swift

+10-3
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,16 @@ internal class _WebSocketURLProtocol: _HTTPURLProtocol {
129129
guard let t = self.task else {
130130
fatalError("Cannot notify")
131131
}
132-
guard case .taskDelegate = t.session.behaviour(for: self.task!),
133-
let task = self.task as? URLSessionWebSocketTask else {
134-
fatalError("WebSocket internal invariant violated")
132+
switch t.session.behaviour(for: t) {
133+
case .noDelegate:
134+
break
135+
case .taskDelegate:
136+
break
137+
default:
138+
fatalError("Unexpected behaviour for URLSessionWebSocketTask")
139+
}
140+
guard let task = t as? URLSessionWebSocketTask else {
141+
fatalError("Cast to URLSessionWebSocketTask failed")
135142
}
136143

137144
// Buffer the response message in the task

Tests/Foundation/TestURLSession.swift

+45-1
Original file line numberDiff line numberDiff line change
@@ -2155,7 +2155,51 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable {
21552155
XCTAssertEqual(delegate.callbacks.count, callbacks.count)
21562156
XCTAssertEqual(delegate.callbacks, callbacks, "Callbacks for \(#function)")
21572157
}
2158-
2158+
2159+
func test_webSocketShared() async throws {
2160+
guard #available(macOS 12, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
2161+
guard URLSessionWebSocketTask.supportsWebSockets else {
2162+
print("libcurl lacks WebSockets support, skipping \(#function)")
2163+
return
2164+
}
2165+
2166+
let urlString = "ws://127.0.0.1:\(TestURLSession.serverPort)/web-socket"
2167+
let url = try XCTUnwrap(URL(string: urlString))
2168+
2169+
let task = URLSession.shared.webSocketTask(with: url)
2170+
task.resume()
2171+
2172+
// We interleave sending and receiving, as the test HTTPServer implementation is barebones, and can't handle receiving more than one frame at a time. So, this back-and-forth acts as a gating mechanism
2173+
try await task.send(.string("Hello"))
2174+
2175+
let stringMessage = try await task.receive()
2176+
switch stringMessage {
2177+
case .string(let str):
2178+
XCTAssert(str == "Hello")
2179+
default:
2180+
XCTFail("Unexpected String Message")
2181+
}
2182+
2183+
try await task.send(.data(Data([0x20, 0x22, 0x10, 0x03])))
2184+
2185+
let dataMessage = try await task.receive()
2186+
switch dataMessage {
2187+
case .data(let data):
2188+
XCTAssert(data == Data([0x20, 0x22, 0x10, 0x03]))
2189+
default:
2190+
XCTFail("Unexpected Data Message")
2191+
}
2192+
2193+
do {
2194+
try await task.sendPing()
2195+
// Server hasn't closed the connection yet
2196+
} catch {
2197+
// Server closed the connection before we could process the pong
2198+
let urlError = try XCTUnwrap(error as? URLError)
2199+
XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost)
2200+
}
2201+
}
2202+
21592203
func test_webSocketCompletions() async throws {
21602204
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
21612205
guard URLSessionWebSocketTask.supportsWebSockets else {

0 commit comments

Comments
 (0)