aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.68k stars 879 forks source link

Troubleshooting reconfiguration of download tasks spanning app termination and relaunch #4347

Closed AlexGingell closed 1 year ago

AlexGingell commented 1 year ago

State your question

I'm having issues handling the scenario when an S3 download spans termination by iOS and relaunch by the user. The first point to make is that I'm running the app in debug mode through Xcode, and stopping execution in Xcode as a proxy for iOS background termination - I'm assuming this is not the same as the user force quitting because I understand that NSURLSessions are cancelled in the latter case. I believe I'm correct as I have been able to continue a download when stopping execution in Xcode, whereas force quitting should always cancel download sessions.

With that out of the way, let me explain further. I'm having problems consistently obtaining pre-existing download tasks in order to reconnect handler blocks. The documentation says that AWSS3TransferUtility automatically looks for and reconnects to prior downloads when AWSS3TransferUtility is configured via registerS3TransferUtilityWithConfiguration:. It is then recommended to run enumerateToAssignBlocks: in order to reconnect progress and completion handlers, which cannot be stored on disk.

The crux of the issue is that I would like to use an alternative approach: to obtain download tasks using getDownloadTasks.result and then to enumerate those tasks manually, set pausingHandler, resumingHandler and cancellationHandler on the progress object of the download task, and only then set the progress and completion handlers on the task itself. I believe there is no reason why this should not work fine:

NSArray<AWSS3TransferUtilityDownloadTask *> *downloadTasks = [[[self transferUtilityOSSEast1] getDownloadTasks] result];
for (AWSS3TransferUtilityDownloadTask *downloadTask in downloadTasks)
{
    NSURLSessionTask *sessionTask = [downloadTask sessionTask];
    RLogInfo(@"[RemoteFileStore] Found download task with session task %@", analyticsStringFromSessionTask(sessionTask));
    [self configureAndTrackDownloadTask:downloadTask];
}

configureAndTrackDownloadTask: sets the task.progress pausingHandler, resumingHandler and cancellationHandler, and then sets the progressHandler and completionHandler of the download task. It also begins tracking the progress object internally.

Using this code I find that it is rare for any pre-existing download to be logged on relaunching the app, either by simply stopping execution and restarting in Xcode while the download is running (in foreground or background), or by turning on airplane mode first to ensure the download does not complete before relaunch.

If I add the following code before attempting this then I find that I have better luck:

AWSS3TransferUtilityBlocks *blocks = [[AWSS3TransferUtilityBlocks alloc] initWithUploadProgress:nil
                                                                        multiPartUploadProgress:nil
                                                                               downloadProgress:nil
                                                                                uploadCompleted:nil
                                                                       multiPartUploadCompleted:nil
                                                                              downloadCompleted:nil];
[[self transferUtilityOSSEast1] enumerateToAssignBlocks:blocks];

Bear in mind that I need to set the task.progress handlers before I set the handlers on the task itself for internal logic related to the fact that the task completionHandler does not execute for cancelled downloads. That is why I'm passing nil - examination of AWSS3TransferUtility code leads me to believe that setting a non-nil completionHandler will fire it immediately if the task already completed which is undesirable in my case because I need the task.progress handlers to be set before the completionHandler can be run.

To recap, my question is this: on app launch, will it suffice to register the transfer utility and then enumerate the result of getDownloadTasks and set all block handlers manually, or is there some other magic connected to using enumerateToAssignBlocks: which I have failed to recognise looking through the source code? It just seems like the first block of code often fails to find download tasks that should be present during testing whereas I have better luck when executing enumerateToAssignBlocks:blocks. However the latter doesn't provide a way to assign task.progress-centric handler blocks prior to setting handler objects on the task object itself (unless you pass nil for those blocks and run enumerate first, which is somewhat messy).

Which AWS Services are you utilizing?

AWS S3

Provide code snippets (if applicable)

See description.

Environment(please complete the following information):

Device Information (please complete the following information):

royjit commented 1 year ago

Thank you for reaching out, here are some context about background execution of TransferUtility - https://docs.amplify.aws/sdk/storage/transfer-utility/q/platform/ios/#background-transfers .

It looks like both api should work, make sure that you invoke the apis after the recovery steps are completed ie inside the completion handler of transferutility:

AWSS3TransferUtility.default(completionHandler: )