Skip to content

Commit 6a436de

Browse files
dnys1diegocstn
andauthored
fix(API): Memory leak in GraphQLOperation (#1562)
* Fix memory leaks * chore(api): prevent unsafe usage of removePair Co-authored-by: Diego Costantino <[email protected]>
1 parent 67009a9 commit 6a436de

File tree

4 files changed

+40
-38
lines changed

4 files changed

+40
-38
lines changed

AmplifyPlugins/API/AWSAPICategoryPlugin/Operation/AWSAPIOperation+APIOperation.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ extension AWSRESTOperation: APIOperation {
4545
return
4646
}
4747

48+
mapper.removePair(for: self)
49+
4850
let apiOperationResponse = APIOperationResponse(error: error, response: response)
4951
do {
5052
try apiOperationResponse.validate()

AmplifyPlugins/API/AWSAPICategoryPlugin/Operation/AWSGraphQLOperation+APIOperation.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ extension AWSGraphQLOperation: APIOperation {
4545
return
4646
}
4747

48+
mapper.removePair(for: self)
49+
4850
let apiOperationResponse = APIOperationResponse(error: error, response: response)
4951
do {
5052
try apiOperationResponse.validate()

AmplifyPlugins/API/AWSAPICategoryPlugin/URLSessionBehavior/OperationTaskMapper.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,14 @@ class OperationTaskMapper {
7474

7575
/// Not inherently thread safe--this must be called from `concurrencyQueue`
7676
private func removePair(operationId: UUID?, taskId: Int?) {
77-
OperationTaskMapper.concurrencyQueue.sync {
78-
if let operationId = operationId {
79-
operations[operationId] = nil
80-
taskIdsByOperationId[operationId] = nil
81-
}
82-
if let taskId = taskId {
83-
tasks[taskId] = nil
84-
operationIdsByTaskId[taskId] = nil
85-
}
77+
dispatchPrecondition(condition: .onQueue(Self.concurrencyQueue))
78+
if let operationId = operationId {
79+
operations[operationId] = nil
80+
taskIdsByOperationId[operationId] = nil
81+
}
82+
if let taskId = taskId {
83+
tasks[taskId] = nil
84+
operationIdsByTaskId[taskId] = nil
8685
}
8786
}
8887
}

AmplifyPlugins/API/AWSAPICategoryPluginTests/Operation/AWSGraphQLOperationTests.swift

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,32 @@ import XCTest
1010
@testable import AmplifyTestCommon
1111
@testable import AWSAPICategoryPlugin
1212

13-
class AWSGraphQLOperationTests: XCTestCase {
14-
15-
// TODO: Complete the implementation below
16-
17-
// override func setUp() {
18-
// }
19-
//
20-
// override func tearDown() {
21-
// }
22-
//
23-
// func testGraphQLOperationSuccess() {
24-
// XCTFail("Not yet implemented.")
25-
// }
26-
//
27-
// func testGraphQLOperationValidationError() {
28-
// XCTFail("Not yet implemented.")
29-
// }
30-
//
31-
// func testGraphQLOperationEndpointConfigurationError() {
32-
// XCTFail("Not yet implemented.")
33-
// }
34-
//
35-
// func testGraphQLOperationJSONSerializationError() {
36-
// XCTFail("Not yet implemented.")
37-
// }
38-
//
39-
// func testGraphQLOperationInterceptorError() {
40-
// XCTFail("Not yet implemented.")
41-
// }
13+
@available(iOS 13.0, *)
14+
class AWSGraphQLOperationTests: AWSAPICategoryPluginTestBase {
15+
16+
/// Tests that upon completion, the operation is removed from the task mapper.
17+
func testOperationCleanup() {
18+
let request = GraphQLRequest(apiName: apiName,
19+
document: testDocument,
20+
variables: nil,
21+
responseType: JSONValue.self)
22+
23+
let operation = apiPlugin.query(request: request, listener: nil)
24+
25+
guard let operation = operation as? AWSGraphQLOperation else {
26+
XCTFail("Operation is not an AWSGraphQLOperation")
27+
return
28+
}
29+
30+
let receivedCompletion = expectation(description: "Received completion")
31+
let sink = operation.resultPublisher.sink { _ in
32+
receivedCompletion.fulfill()
33+
} receiveValue: { _ in }
34+
defer { sink.cancel() }
35+
36+
wait(for: [receivedCompletion], timeout: 1)
37+
let task = operation.mapper.task(for: operation)
38+
XCTAssertNil(task)
39+
}
40+
4241
}

0 commit comments

Comments
 (0)