1

I am using the AWS SDK for iOS in a swift application for iOS 12. My app has to list files in a AWS S3 bucket and download some of them. The list files operation works well and I succeeded in having control of its timeout. I did not succeed to do that for the download task. My code is the following:

let credentialProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: "<pool-id>")
let configuration = AWSServiceConfiguration(region: AWSRegionType.APSoutheast2, credentialsProvider: credentialProvider)

configuration?.timeoutIntervalForRequest = 30.0
configuration?.timeoutIntervalForResource = 86400


let transferUtilityConfiguration = AWSS3TransferUtilityConfiguration.init()

transferUtilityConfiguration.timeoutIntervalForResource = 86400
transferUtilityConfiguration.retryLimit = 1

AWSS3TransferUtility.register(with: configuration!, transferUtilityConfiguration: transferUtilityConfiguration, forKey: "com.mykey")

transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "com.mykey")


let bucket = "com.mybucket"


transferUtility.configuration.maxRetryCount = 1

let urlForSavingFile = URL.init(fileURLWithPath: "")


transferUtility.download(to: urlForSavingFile, bucket: bucket, key: self.latestFileOnServer.key, expression: expression, completionHandler: self.completionHandler).continueWith { (task) -> AnyObject? in
            if let error = task.error {
                NSLog("Error: %@",error.localizedDescription);
                DispatchQueue.main.async(execute: {
                    statusLabel.text = "Failed"
                })
            }

            if let _ = task.result {

                self.refDownloadTask = task.result
                self.refDownloadTask?.setCompletionHandler(self.completionHandler!)
                methodStart = Date.init()

                let formatter = DateFormatter.init()

                formatter.dateFormat = "dd' 'MMM' 'YYYY' - 'HH:mm:ss"
                formatter.locale = Locale.init(identifier: "Europe / Rome")
                let italyDate = formatter.string(from: methodStart)

                print("Download started at \(italyDate)")
                DispatchQueue.main.async(execute: {
                    //statusLabel.text = "Downloading..."
                })
                NSLog("Download Starting!")
                // Do something with uploadTask.
            }
            return nil;
        }

The completion handler is properly called if I wait for the file to finish downloading, but if I turn off the network with the network link conditioner the transfer hangs forever and the completion handler is never called. Any help is much appreciated. Thanks

Alfonso Tesauro
  • 1,730
  • 13
  • 21
  • Reporting the same issue here. Mine is related to uploads, rather than downloads, but the timeout does not seem respected and, if the timeout is reached, the completion handler is never called. – ZbadhabitZ Apr 26 '19 at 21:39
  • Hello, I have provided a solution, thanks for your comment, please tell me if it works for you. Thanks – Alfonso Tesauro Apr 27 '19 at 22:08

1 Answers1

4

After several tests, I succeeded in finding a solution and make the SDK respect the timeout. During my tests, initially, I was a little confused about the naming convention:

timeoutIntervalForResource is the maximum time that the transfer can take, regardless of any success / failure. This works out of the box with the AWS SDK.

timeoutIntervalForRequest is the value we are interested in. It is the time interval that the task waits for additional data after a network loss. Its timer is scheduled since the last packet received. As you can read in the question and comments, it does not appear to be respected by the AWS SDK.

This naming convention is the same as Apple Cocoa USURLSession family of classes. During my research, I came across this answer. It appears that this behaviour has been consistently experienced for URLSessions that have their configuration instantiated as a background configuration. It is not a misbehaviour, it is just the new standard for background downloads since iOS 8.

Now, the solution. By debugging around in the AWS SDK Objective-C code (Yes they provide the full code) I found the spot. If you open the AWSS3TransferUtility.m file, at line around 400, you will find, in this method:

- (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfiguration
     transferUtilityConfiguration:(AWSS3TransferUtilityConfiguration *)transferUtilityConfiguration
                       identifier:(NSString *)identifier
                completionHandler: (void (^)(NSError *_Nullable error)) completionHandler {}

Those statements:

//Create the NS URL session
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:_sessionIdentifier];
configuration.allowsCellularAccess = serviceConfiguration.allowsCellularAccess;
configuration.timeoutIntervalForResource = transferUtilityConfiguration.timeoutIntervalForResource;

In order to make it work, replace the first statement with:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

Now, in your code:

let credentialProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: "<pool-id>")
let configuration = AWSServiceConfiguration(region: AWSRegionType.APSoutheast2, credentialsProvider: credentialProvider)

configuration?.timeoutIntervalForRequest = 30.0
configuration?.timeoutIntervalForResource = 86400

The SDK will fire the completion handler with a -1001 error after 30 seconds of network loss.

I only tested on a transferutilitydownloadtask, but it should work on upload too. I know this solution is not bullet proof as it does not take into account session configuration identifiers, but I will work on it. Also, the solution implies changing the AWS SDK code, but for a single line of code. The change simply makes the AWSS3TransferUtility create standard URLSessionConfigurations instead of background ones. This has probably other side effects to consider.

Alfonso Tesauro
  • 1,730
  • 13
  • 21