diff --git a/Sources/StreamCore/WebSocket/Client/WebSocketClient.swift b/Sources/StreamCore/WebSocket/Client/WebSocketClient.swift index 299b5cb..73c0087 100644 --- a/Sources/StreamCore/WebSocket/Client/WebSocketClient.swift +++ b/Sources/StreamCore/WebSocket/Client/WebSocketClient.swift @@ -35,9 +35,14 @@ public class WebSocketClient: @unchecked Sendable { public weak var connectionStateDelegate: ConnectionStateDelegate? - public var connectRequest: URLRequest? + public var connectRequest: URLRequest? { + get { _connectRequest.value } + set { _connectRequest.value = newValue } + } + + private let _connectRequest = AllocatedUnfairLock(nil) - var requiresAuth: Bool + let requiresAuth: Bool /// If true, health check event is processed by the event notification center before setting connection status to connected, otherwise the order is reversed. /// Compatibility reasons for chat which has to set it to true. let healthCheckBeforeConnected: Bool @@ -89,7 +94,7 @@ public class WebSocketClient: @unchecked Sendable { self.sessionConfiguration = sessionConfiguration self.webSocketClientType = webSocketClientType self.eventDecoder = eventDecoder - self.connectRequest = connectRequest + self._connectRequest.value = connectRequest self.eventNotificationCenter = eventNotificationCenter self.healthCheckBeforeConnected = healthCheckBeforeConnected self.requiresAuth = requiresAuth @@ -123,7 +128,9 @@ public class WebSocketClient: @unchecked Sendable { } guard let connectRequest else { return } - engine = createEngineIfNeeded(for: connectRequest) + engineQueue.sync { + engine = createEngineIfNeeded(for: connectRequest) + } connectionState = .connecting diff --git a/Tests/StreamCoreTests/Mocks/WebSocketPingController_Mock.swift b/Tests/StreamCoreTests/Mocks/WebSocketPingController_Mock.swift index 0852118..deaec90 100644 --- a/Tests/StreamCoreTests/Mocks/WebSocketPingController_Mock.swift +++ b/Tests/StreamCoreTests/Mocks/WebSocketPingController_Mock.swift @@ -7,11 +7,11 @@ import Foundation import XCTest final class WebSocketPingController_Mock: WebSocketPingController, @unchecked Sendable { - var connectionStateDidChange_connectionStates: [WebSocketConnectionState] = [] - var pongReceivedCount = 0 + @Atomic var connectionStateDidChange_connectionStates: [WebSocketConnectionState] = [] + @Atomic var pongReceivedCount = 0 override func connectionStateDidChange(_ connectionState: WebSocketConnectionState) { - connectionStateDidChange_connectionStates.append(connectionState) + _connectionStateDidChange_connectionStates.mutate { $0.append(connectionState) } super.connectionStateDidChange(connectionState) } diff --git a/Tests/StreamCoreTests/WebSockets/WebSocketClient/WebSocketClient_Tests.swift b/Tests/StreamCoreTests/WebSockets/WebSocketClient/WebSocketClient_Tests.swift index 4d68471..3aba918 100644 --- a/Tests/StreamCoreTests/WebSockets/WebSocketClient/WebSocketClient_Tests.swift +++ b/Tests/StreamCoreTests/WebSockets/WebSocketClient/WebSocketClient_Tests.swift @@ -351,6 +351,15 @@ final class WebSocketClient_Tests: XCTestCase, @unchecked Sendable { // Assert completion called wait(for: [expectation], timeout: defaultTimeout) } + + func test_recreatingEngineConcurrently() { + DispatchQueue.concurrentPerform(iterations: 10000, execute: { _ in + guard let url = URL(string: "ws:///\(String.unique)") else { return } + self.webSocketClient.connectRequest = URLRequest(url: url) + self.webSocketClient.initialize() + self.webSocketClient.connect() + }) + } } private final class HealthCheckEvent: @unchecked Sendable, Event, Codable, Hashable {