Alamofire multipartFormData Restart uploading when respond take longer then 1 min

Created on 28 Feb 2019  Â·  6Comments  Â·  Source: Alamofire/Alamofire

What did you do?

ℹ I am Uploading a video file using multipartFormData, with timeoutIntervalForRequest set to 500,
the reason for timeout being 500 is, after uploading the video my server take around 2 min to encode and respond

What did you expect to happen?

ℹ I was expecting success respond when the server is done with encode and respond with JSON data.

What happened instead?

ℹ Whenever the response takes longer then 1 min. Alamofire restarts uploading again instead of waiting for a response or showing the JSON response.

PS: This problem occurs only when I use background SessionManager

Alamofire Environment

*Alamofire version:(4.8.0)*
*Xcode version:10*
*Swift version:4.2*
*Platform(s) running Alamofire:iOS 12*

Demo Project

func uploadInBackground(fileInData: Data) {        
        let headers: [String : String] = [ "Authorization": "key"]

        Networking.sharedInstance.backgroundSessionManager.upload(multipartFormData: { (multipartFormData) in
            multipartFormData.append(fileInData, withName: "file", mimeType: "video/mp4")

        }, usingThreshold: UInt64.init(), to: url, method: .post, headers: headers)
        { (result) in
            switch result {
            case .success(let upload, _, _):

                upload.uploadProgress(closure: { (progress) in
                    //Print progress
                    let value =  Int(progress.fractionCompleted * 100)
                    print("\(value) %")
                })

                upload.responseJSON { response in
                    //print response.result
                    print(response.description)
                    let res = response.response?.statusCode
                    print(res)
                }

            case .failure(let encodingError):
                //print encodingError.description
                print(encodingError.localizedDescription)
            }
        }
    }.
class Networking {
    static let sharedInstance = Networking()
    public var sessionManager: Alamofire.SessionManager // most of your web service clients will call through sessionManager
    public var backgroundSessionManager: Alamofire.SessionManager // your web services you intend to keep running when the system backgrounds your app will use this
    private init() {
        let defaultConfig = URLSessionConfiguration.default
        defaultConfig.timeoutIntervalForRequest = 500


        let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.lava.app.backgroundtransfer")
        backgroundConfig.timeoutIntervalForRequest = 500

//        self.sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
        self.sessionManager = Alamofire.SessionManager(configuration: defaultConfig)
        self.backgroundSessionManager = Alamofire.SessionManager(configuration: backgroundConfig)

    }
}
support

Most helpful comment

I have the same issue like @lohenyumnam
I try to set timeoutIntervalForRequest to 150 seconds.

private func alamofireManagerInit() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 150
        self.manager = Alamofire.SessionManager(configuration: configuration)
}

and when I make request:

@discardableResult func request(_ uri: String, httpType: HTTPMethod,
                                    parameters : [String : AnyObject]?,
                                    encoding: ParameterEncoding = JSONEncoding.default,
                                    isRefreshing: Bool = false,
                                    ignoreDomain: Bool = false,
                                    shouldCancelPreviousRequests: Bool = false,
                                    completion: @escaping (_ result: Result<(json: [String : AnyObject], code: Int)>) -> (Void)) -> DataRequest? {
  //...

  let request = manager?.request(
                                 domain + uri,
                                 method: httpType,
                                 parameters: parameters,
                                 encoding: encoding,
                                 headers: headers
                                )
                        .responseJSON(completionHandler: { [weak self] (response) in
                        //...
  })

  print(request?.request!.timeoutInterval)

  return request
}

this request?.request.timeoutInterval always 60 seconds

All 6 comments

Could you be more specific about what you're seeing? From what you've posted it sounds like the network transfer restarts but it isn't clear. Does the encoding completion get called? Is the application in the background when this happens?

As an aside, Alamofire doesn't really support background sessions where the app is killed in the background, so that's important to keep in mind.

Could you be more specific about what you're seeing? From what you've posted it sounds like the network transfer restarts but it isn't clear.

Yes, you can say network transfer restarts again.
thing is, when video upload is complete. my server will encode the video, which takes around 2 to 3 min depending upon the length of video, when the encoding is completed the server will response with a JSON which I am suppose to catch with this block of code

upload.responseJSON { response in
                    //print response.result
                    print(response.description)
                    let res = response.response?.statusCode
                    print(res)
                }

The problem is when ever my server respone take longer then 1 min. this block of code execute again

upload.uploadProgress(closure: { (progress) in
                    //Print progress
                    let value =  Int(progress.fractionCompleted * 100)
                    print("\(value) %")
                })

which means the upload begain again. just to make sure I also check on server. it gets uploaded again. and the funny part is it will keep reuploading if I don't stop it

Does the encoding completion get called?

Encoding completion get called if the server respond with in a min. if it takes longer then 1 min it won't

Is the application in the background when this happens?

No While I was testing. The app was not in the background.
PS: when i put the app in background the upload still progress

As an aside, Alamofire doesn't really support background sessions where the app is killed in the background, so that's important to keep in mind.

So I can't use this ?
Networking.sharedInstance.backgroundSessionManager.upload
I am stuck with this then ?
Networking.sharedInstance. sessionManager.upload

Ultimately your issue here is that URLSessionUploadTask, which UploadRequest uses under the hood, when used from a background session, automatically retries after a timeout. According to Apple's docs:

Any upload or download tasks created by a background session are automatically retried if the original request fails due to a timeout. To configure how long an upload or download task should be allowed to be retried or transferred, use the timeoutIntervalForResource property.

In the end, your request times out because the URLRequest created by upload(multipartFormData:) has a timeout of 60 seconds. It's then automatically retried by the background session. To work around this you could either manually build your URLRequest using the same multipart encoder that underlies upload(multipartFormData:), or you could create a RequestAdapter for the background session that sets the URLRequest timeouts to something more than 60 seconds.

Additionally, when I said that Alamofire doesn't support background sessions, it's mostly that there will be no automatic reconnection to your responseJSON implementation if the app is killed in the background and removed from memory, as Alamofire has no way to persist that logic. Generally I wouldn't recommend waiting like this, as it's bad for user experience and for their device's battery. A push notification when processing is done, or a periodic check for when its finished, would work better. Ultimately I don't think this is going to work the way you want. If you do want to use a background session that restores automatically you'll want to use URLSession directly.

I tried setting

backgroundConfig.timeoutIntervalForRequest = 50000
backgroundConfig.timeoutIntervalForResource = 50000

in the Networking class but it does not work. maybe I have to avoid the background session for now.

I have the same issue like @lohenyumnam
I try to set timeoutIntervalForRequest to 150 seconds.

private func alamofireManagerInit() {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 150
        self.manager = Alamofire.SessionManager(configuration: configuration)
}

and when I make request:

@discardableResult func request(_ uri: String, httpType: HTTPMethod,
                                    parameters : [String : AnyObject]?,
                                    encoding: ParameterEncoding = JSONEncoding.default,
                                    isRefreshing: Bool = false,
                                    ignoreDomain: Bool = false,
                                    shouldCancelPreviousRequests: Bool = false,
                                    completion: @escaping (_ result: Result<(json: [String : AnyObject], code: Int)>) -> (Void)) -> DataRequest? {
  //...

  let request = manager?.request(
                                 domain + uri,
                                 method: httpType,
                                 parameters: parameters,
                                 encoding: encoding,
                                 headers: headers
                                )
                        .responseJSON(completionHandler: { [weak self] (response) in
                        //...
  })

  print(request?.request!.timeoutInterval)

  return request
}

this request?.request.timeoutInterval always 60 seconds

timeoutIntervalForRequest does not affect the timeouts of URLRequests issued using the URLSession. From the documentation for timeoutIntervalForRequest:

This property determines the request timeout interval for all tasks within sessions based on this configuration. The request timeout interval controls how long (in seconds) a task should wait for additional data to arrive before giving up. The timer associated with this value is reset whenever new data arrives. When the request timer reaches the specified interval without receiving any new data, it triggers a timeout.

From the documentation for URLRequest.timeoutInterval:

If during a connection attempt the request remains idle for longer than the timeout interval, the request is considered to have timed out. The default timeout interval is 60 seconds.

As you can see, these are two different scenarios: timeoutIntervalForRequest can control timeouts during a connection (i.e. between received packets) while timeoutInterval controls the amount of time to wait for a connection to be established (i.e. initial server response). timeoutIntervalForRequest is _not_ a way to override the per-URLRequest timoutInterval setting. If you want to customize that timeout, you _must_ create your own URLRequest values with the appropriate setting before passing them to Alamofire. You can achieve this either my creating them manually, or by using types which conform to our URLRequestConvertible protocol.

Was this page helpful?
0 / 5 - 0 ratings