Skip to content

Commit d78ad56

Browse files
author
Anthony Guella
committed
[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: #37
1 parent 1046735 commit d78ad56

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

Sources/AWSAppSyncApolloExtensions/Websocket/AppSyncWebSocketClient.swift

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,24 @@ public class AppSyncWebSocketClient: NSObject, ApolloWebSocket.WebSocketClient,
1717

1818
// MARK: - ApolloWebSocket.WebSocketClient
1919

20-
public var request: URLRequest
2120
public var delegate: ApolloWebSocket.WebSocketClientDelegate?
2221
public var callbackQueue: DispatchQueue
2322

23+
private let requestLock = NSLock()
24+
private var _request: URLRequest
25+
public var request: URLRequest {
26+
get {
27+
requestLock.lock()
28+
defer { requestLock.unlock() }
29+
return _request
30+
}
31+
set {
32+
requestLock.lock()
33+
defer { requestLock.unlock() }
34+
_request = newValue
35+
}
36+
}
37+
2438
// MARK: - Public
2539

2640
public var publisher: AnyPublisher<AppSyncWebSocketEvent, Never> {
@@ -71,7 +85,7 @@ public class AppSyncWebSocketClient: NSObject, ApolloWebSocket.WebSocketClient,
7185
callbackQueue: DispatchQueue,
7286
authorizer: AppSyncAuthorizer
7387
) {
74-
self.request = URLRequest(url: appSyncRealTimeEndpoint(endpointURL))
88+
self._request = URLRequest(url: appSyncRealTimeEndpoint(endpointURL))
7589
self.delegate = delegate
7690
self.callbackQueue = callbackQueue
7791
self.authorizer = authorizer

Tests/AWSAppSyncApolloExtensionsTests/Websocket/AppSyncWebSocketClientTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,37 @@ final class AppSyncWebSocketClientTests: XCTestCase {
119119
await fulfillment(of: [messageReceivedExpectation], timeout: 5)
120120
}
121121

122+
func testConcurrentURLRequestAccess() {
123+
let endpoint = URL(string: "https://abc.appsync-api.us-east-1.amazonaws.com/graphql")!
124+
125+
let websocket = AppSyncWebSocketClient(
126+
endpointURL: endpoint,
127+
authorizer: APIKeyAuthorizer(apiKey: "apiKey"))
128+
129+
let expectation = XCTestExpectation(description: "Race condition test")
130+
expectation.expectedFulfillmentCount = 2
131+
132+
let iterations = 1000
133+
134+
// Thread 1: Continuous reading
135+
DispatchQueue.global(qos: .userInitiated).async {
136+
for _ in 0..<iterations {
137+
_ = websocket.request.value(forHTTPHeaderField: "test-header")
138+
}
139+
expectation.fulfill()
140+
}
141+
142+
// Thread 2: Continuous writing
143+
DispatchQueue.global(qos: .userInitiated).async {
144+
for i in 0..<iterations {
145+
websocket.request.setValue("value-\(i)", forHTTPHeaderField: "test-header")
146+
}
147+
expectation.fulfill()
148+
}
149+
150+
wait(for: [expectation], timeout: 5.0)
151+
}
152+
122153
private func verifyConnected(
123154
_ webSocketClient: AppSyncWebSocketClient,
124155
autoConnectOnNetworkStatusChange: Bool = false,
@@ -138,6 +169,7 @@ final class AppSyncWebSocketClientTests: XCTestCase {
138169
webSocketClient.connect()
139170
await fulfillment(of: [connectedExpectation], timeout: 5)
140171
}
172+
141173
}
142174

143175
fileprivate class MockAppSyncAuthorizer: AppSyncAuthorizer {

0 commit comments

Comments
 (0)