Selaa lähdekoodia

Make MultipartUpload more thread-safe. (#3421)

Jon Shier 4 vuotta sitten
vanhempi
commit
5a625bcaa1
3 muutettua tiedostoa jossa 61 lisäystä ja 19 poistoa
  1. 14 13
      Source/MultipartUpload.swift
  2. 2 4
      Source/Session.swift
  3. 45 2
      Tests/UploadTests.swift

+ 14 - 13
Source/MultipartUpload.swift

@@ -28,29 +28,24 @@ import Foundation
 final class MultipartUpload {
     lazy var result = Result { try build() }
 
-    let isInBackgroundSession: Bool
-    let multipartFormData: MultipartFormData
+    @Protected
+    private(set) var multipartFormData: MultipartFormData
     let encodingMemoryThreshold: UInt64
     let request: URLRequestConvertible
     let fileManager: FileManager
 
-    init(isInBackgroundSession: Bool,
-         encodingMemoryThreshold: UInt64,
+    init(encodingMemoryThreshold: UInt64,
          request: URLRequestConvertible,
          multipartFormData: MultipartFormData) {
-        self.isInBackgroundSession = isInBackgroundSession
         self.encodingMemoryThreshold = encodingMemoryThreshold
         self.request = request
         fileManager = multipartFormData.fileManager
         self.multipartFormData = multipartFormData
     }
 
-    func build() throws -> (request: URLRequest, uploadable: UploadRequest.Uploadable) {
-        var urlRequest = try request.asURLRequest()
-        urlRequest.setValue(multipartFormData.contentType, forHTTPHeaderField: "Content-Type")
-
+    func build() throws -> UploadRequest.Uploadable {
         let uploadable: UploadRequest.Uploadable
-        if multipartFormData.contentLength < encodingMemoryThreshold && !isInBackgroundSession {
+        if multipartFormData.contentLength < encodingMemoryThreshold {
             let data = try multipartFormData.encode()
 
             uploadable = .data(data)
@@ -73,16 +68,22 @@ final class MultipartUpload {
             uploadable = .file(fileURL, shouldRemove: true)
         }
 
-        return (request: urlRequest, uploadable: uploadable)
+        return uploadable
     }
 }
 
 extension MultipartUpload: UploadConvertible {
     func asURLRequest() throws -> URLRequest {
-        try result.get().request
+        var urlRequest = try request.asURLRequest()
+
+        $multipartFormData.read { multipartFormData in
+            urlRequest.headers.add(.contentType(multipartFormData.contentType))
+        }
+
+        return urlRequest
     }
 
     func createUploadable() throws -> UploadRequest.Uploadable {
-        try result.get().uploadable
+        try result.get()
     }
 }

+ 2 - 4
Source/Session.swift

@@ -907,8 +907,7 @@ open class Session {
                                                           headers: headers,
                                                           requestModifier: requestModifier)
 
-        let multipartUpload = MultipartUpload(isInBackgroundSession: session.configuration.identifier != nil,
-                                              encodingMemoryThreshold: encodingMemoryThreshold,
+        let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold,
                                               request: convertible,
                                               multipartFormData: multipartFormData)
 
@@ -947,8 +946,7 @@ open class Session {
                      usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                      interceptor: RequestInterceptor? = nil,
                      fileManager: FileManager = .default) -> UploadRequest {
-        let multipartUpload = MultipartUpload(isInBackgroundSession: session.configuration.identifier != nil,
-                                              encodingMemoryThreshold: encodingMemoryThreshold,
+        let multipartUpload = MultipartUpload(encodingMemoryThreshold: encodingMemoryThreshold,
                                               request: request,
                                               multipartFormData: multipartFormData)
 

+ 45 - 2
Tests/UploadTests.swift

@@ -266,8 +266,6 @@ final class UploadDataTestCase: BaseTestCase {
 // MARK: -
 
 final class UploadMultipartFormDataTestCase: BaseTestCase {
-    // MARK: Tests
-
     func testThatUploadingMultipartFormDataSetsContentTypeHeader() {
         // Given
         let url = Endpoint.method(.post).url
@@ -307,6 +305,51 @@ final class UploadMultipartFormDataTestCase: BaseTestCase {
         }
     }
 
+    func testThatAccessingMultipartFormDataURLIsThreadSafe() {
+        // Given
+        let url = Endpoint.method(.post).url
+        let uploadData = Data("upload_data".utf8)
+
+        let expectation = self.expectation(description: "multipart form data upload should succeed")
+
+        var formData: MultipartFormData?
+        var generatedURL: URL?
+        var response: DataResponse<Data?, AFError>?
+
+        // When
+        let upload = AF.upload(multipartFormData: { multipartFormData in
+                                   multipartFormData.append(uploadData, withName: "upload_data")
+                                   formData = multipartFormData
+                               },
+                               to: url)
+
+        // Access will produce a thread-sanitizer issue if it isn't safe.
+        generatedURL = upload.convertible.urlRequest?.url
+
+        upload.response { resp in
+            response = resp
+            expectation.fulfill()
+        }
+
+        waitForExpectations(timeout: timeout)
+
+        // Then
+        XCTAssertNotNil(response?.request)
+        XCTAssertNotNil(response?.response)
+        XCTAssertNotNil(response?.data)
+        XCTAssertNil(response?.error)
+
+        if
+            let request = response?.request,
+            let multipartFormData = formData,
+            let contentType = request.value(forHTTPHeaderField: "Content-Type") {
+            XCTAssertEqual(contentType, multipartFormData.contentType)
+            XCTAssertEqual(url, generatedURL)
+        } else {
+            XCTFail("Content-Type header value should not be nil")
+        }
+    }
+
     func testThatCustomBoundaryCanBeSetWhenUploadingMultipartFormData() throws {
         // Given
         let uploadData = Data("upload_data".utf8)