Skip to content

Commit 398eb88

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 398eb88

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,37 @@ final class AppSyncWebSocketClientTests: XCTestCase {
138138
webSocketClient.connect()
139139
await fulfillment(of: [connectedExpectation], timeout: 5)
140140
}
141+
142+
func testConcurrentURLRequestAccess() {
143+
let endpoint = URL(string: "https://abc.appsync-api.us-east-1.amazonaws.com/graphql")!
144+
145+
let websocket = AppSyncWebSocketClient(
146+
endpointURL: endpoint,
147+
authorizer: APIKeyAuthorizer(apiKey: "apiKey"))
148+
149+
let expectation = XCTestExpectation(description: "Race condition test")
150+
expectation.expectedFulfillmentCount = 2
151+
152+
let iterations = 1000
153+
154+
// Thread 1: Continuous reading
155+
DispatchQueue.global(qos: .userInitiated).async {
156+
for _ in 0..<iterations {
157+
_ = websocket.request.value(forHTTPHeaderField: "test-header")
158+
}
159+
expectation.fulfill()
160+
}
161+
162+
// Thread 2: Continuous writing
163+
DispatchQueue.global(qos: .userInitiated).async {
164+
for i in 0..<iterations {
165+
websocket.request.setValue("value-\(i)", forHTTPHeaderField: "test-header")
166+
}
167+
expectation.fulfill()
168+
}
169+
170+
wait(for: [expectation], timeout: 5.0)
171+
}
141172
}
142173

143174
fileprivate class MockAppSyncAuthorizer: AppSyncAuthorizer {

0 commit comments

Comments
 (0)