2
0
Эх сурвалжийг харах

Fixed cancellation loop in response serializer completion (#2778)

* Fixed localization issue in project picked up by Xcode

* Fixed issue when cancelling request inside response serializer completion

* Pulled execution of response serializer completions outside of mutableState lock
Christian Noon 6 жил өмнө
parent
commit
4df5912df4

+ 0 - 1
Alamofire.xcodeproj/project.pbxproj

@@ -1104,7 +1104,6 @@
 			developmentRegion = en;
 			hasScannedForEncodings = 0;
 			knownRegions = (
-				English,
 				en,
 				Base,
 			);

+ 15 - 2
Source/Request.swift

@@ -396,8 +396,21 @@ open class Request {
     /// Processes the next response serializer and calls all completions if response serialization is complete.
     func processNextResponseSerializer() {
         guard let responseSerializer = nextResponseSerializer() else {
-            // Execute all response serializer completions
-            protectedMutableState.directValue.responseSerializerCompletions.forEach { $0() }
+            // Execute all response serializer completions and clear them
+            var completions: [() -> Void] = []
+
+            protectedMutableState.write { mutableState in
+                completions = mutableState.responseSerializerCompletions
+
+                // Clear out all response serializers and response serializer completions in mutable state since the
+                // request is complete. It's important to do this prior to calling the completion closures in case
+                // the completions call back into the request triggering a re-processing of the response serializers.
+                // An example of how this can happen is by calling cancel inside a response completion closure.
+                mutableState.responseSerializers.removeAll()
+                mutableState.responseSerializerCompletions.removeAll()
+            }
+
+            completions.forEach { $0() }
 
             // Cleanup the request
             cleanup()

+ 37 - 0
Tests/SessionTests.swift

@@ -1435,6 +1435,43 @@ class SessionTestCase: BaseTestCase {
         // Then
         assertErrorIsAFError(error) { XCTAssertTrue($0.isSessionDeinitializedError) }
     }
+
+    // MARK: Tests - Request Cancellation
+
+    func testThatSessionOnlyCallsResponseSerializerCompletionWhenCancellingInsideCompletion() {
+        // Given
+        let handler = RequestHandler()
+        let session = Session()
+
+        let expectation = self.expectation(description: "request should complete")
+        var response: DataResponse<Any>?
+        var completionCallCount = 0
+
+        // When
+        let request = session.request("https://httpbin.org/get", interceptor: handler)
+        request.validate()
+
+        request.responseJSON { resp in
+            request.cancel()
+
+            response = resp
+            completionCallCount += 1
+
+            DispatchQueue.main.after(0.01) { expectation.fulfill() }
+        }
+
+        waitForExpectations(timeout: timeout, handler: nil)
+
+        // Then
+        XCTAssertEqual(handler.adaptCalledCount, 1)
+        XCTAssertEqual(handler.adaptedCount, 1)
+        XCTAssertEqual(handler.retryCalledCount, 0)
+        XCTAssertEqual(handler.retryCount, 0)
+        XCTAssertEqual(request.retryCount, 0)
+        XCTAssertEqual(response?.result.isSuccess, true)
+        XCTAssertTrue(session.requestTaskMap.isEmpty)
+        XCTAssertEqual(completionCallCount, 1)
+    }
 }
 
 // MARK: -