Skip to content

Commit bac9047

Browse files
authored
Fix crash when using WebSockets with URLSession.shared (swiftlang#5128) (swiftlang#5131)
1 parent 22af71d commit bac9047

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
@@ -2148,7 +2148,51 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable {
21482148
XCTAssertEqual(delegate.callbacks.count, callbacks.count)
21492149
XCTAssertEqual(delegate.callbacks, callbacks, "Callbacks for \(#function)")
21502150
}
2151-
2151+
2152+
func test_webSocketShared() async throws {
2153+
guard #available(macOS 12, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
2154+
guard URLSessionWebSocketTask.supportsWebSockets else {
2155+
print("libcurl lacks WebSockets support, skipping \(#function)")
2156+
return
2157+
}
2158+
2159+
let urlString = "ws://127.0.0.1:\(TestURLSession.serverPort)/web-socket"
2160+
let url = try XCTUnwrap(URL(string: urlString))
2161+
2162+
let task = URLSession.shared.webSocketTask(with: url)
2163+
task.resume()
2164+
2165+
// 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
2166+
try await task.send(.string("Hello"))
2167+
2168+
let stringMessage = try await task.receive()
2169+
switch stringMessage {
2170+
case .string(let str):
2171+
XCTAssert(str == "Hello")
2172+
default:
2173+
XCTFail("Unexpected String Message")
2174+
}
2175+
2176+
try await task.send(.data(Data([0x20, 0x22, 0x10, 0x03])))
2177+
2178+
let dataMessage = try await task.receive()
2179+
switch dataMessage {
2180+
case .data(let data):
2181+
XCTAssert(data == Data([0x20, 0x22, 0x10, 0x03]))
2182+
default:
2183+
XCTFail("Unexpected Data Message")
2184+
}
2185+
2186+
do {
2187+
try await task.sendPing()
2188+
// Server hasn't closed the connection yet
2189+
} catch {
2190+
// Server closed the connection before we could process the pong
2191+
let urlError = try XCTUnwrap(error as? URLError)
2192+
XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost)
2193+
}
2194+
}
2195+
21522196
func test_webSocketSpecificProtocol() async throws {
21532197
guard #available(macOS 12, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
21542198
guard URLSessionWebSocketTask.supportsWebSockets else {

0 commit comments

Comments
 (0)