From 829be6a4723cad357cc7208ebc0973482c21658f Mon Sep 17 00:00:00 2001 From: Anthony Guella Date: Wed, 2 Jul 2025 14:16:49 -0400 Subject: [PATCH] [Issue-37] Fix URLRequest Race Condition [Changes] - Wrapped the AppSyncWebSocketClient request in a lock, providing thread safe access. This prevents a potential crash from concurrent read/writes. [Testing] - Verified solution is working via a unit test. Motivation: https://github.com/aws-amplify/aws-appsync-apollo-extensions-swift/issues/37 --- .../Websocket/AppSyncWebSocketClient.swift | 18 +++++++++-- .../AppSyncWebSocketClientTests.swift | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Sources/AWSAppSyncApolloExtensions/Websocket/AppSyncWebSocketClient.swift b/Sources/AWSAppSyncApolloExtensions/Websocket/AppSyncWebSocketClient.swift index fc6aebb..1265bb6 100644 --- a/Sources/AWSAppSyncApolloExtensions/Websocket/AppSyncWebSocketClient.swift +++ b/Sources/AWSAppSyncApolloExtensions/Websocket/AppSyncWebSocketClient.swift @@ -17,10 +17,24 @@ public class AppSyncWebSocketClient: NSObject, ApolloWebSocket.WebSocketClient, // MARK: - ApolloWebSocket.WebSocketClient - public var request: URLRequest public var delegate: ApolloWebSocket.WebSocketClientDelegate? public var callbackQueue: DispatchQueue + private let requestLock = NSLock() + private var _request: URLRequest + public var request: URLRequest { + get { + requestLock.lock() + defer { requestLock.unlock() } + return _request + } + set { + requestLock.lock() + defer { requestLock.unlock() } + _request = newValue + } + } + // MARK: - Public public var publisher: AnyPublisher { @@ -71,7 +85,7 @@ public class AppSyncWebSocketClient: NSObject, ApolloWebSocket.WebSocketClient, callbackQueue: DispatchQueue, authorizer: AppSyncAuthorizer ) { - self.request = URLRequest(url: appSyncRealTimeEndpoint(endpointURL)) + self._request = URLRequest(url: appSyncRealTimeEndpoint(endpointURL)) self.delegate = delegate self.callbackQueue = callbackQueue self.authorizer = authorizer diff --git a/Tests/AWSAppSyncApolloExtensionsTests/Websocket/AppSyncWebSocketClientTests.swift b/Tests/AWSAppSyncApolloExtensionsTests/Websocket/AppSyncWebSocketClientTests.swift index 75cc9a5..13a374b 100644 --- a/Tests/AWSAppSyncApolloExtensionsTests/Websocket/AppSyncWebSocketClientTests.swift +++ b/Tests/AWSAppSyncApolloExtensionsTests/Websocket/AppSyncWebSocketClientTests.swift @@ -119,6 +119,37 @@ final class AppSyncWebSocketClientTests: XCTestCase { await fulfillment(of: [messageReceivedExpectation], timeout: 5) } + func testConcurrentURLRequestAccess() async { + let endpoint = URL(string: "https://abc.appsync-api.us-east-1.amazonaws.com/graphql")! + + let websocket = AppSyncWebSocketClient( + endpointURL: endpoint, + authorizer: APIKeyAuthorizer(apiKey: "apiKey")) + + let raceConditionExpectation = expectation(description: "Race condition test") + raceConditionExpectation.expectedFulfillmentCount = 2 + + let iterations = 1000 + + // Thread 1: Continuous reading + DispatchQueue.global(qos: .userInitiated).async { + for _ in 0..