From 9a261f1a4cfc43ccaa1421b192a7c40c72d99988 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 18 Mar 2022 14:55:46 -0700 Subject: [PATCH 01/11] feat(S3): adds suspend and resume features for multipart upload tasks #4167 --- AWSS3/AWSS3TransferUtility.h | 24 ++ AWSS3/AWSS3TransferUtility.m | 278 +++++++++++++++++---- AWSS3/AWSS3TransferUtilityDatabaseHelper.m | 3 + AWSS3/AWSS3TransferUtilityTasks.h | 14 ++ AWSS3/AWSS3TransferUtilityTasks.m | 25 +- AWSS3/AWSS3TransferUtility_private.h | 4 +- 6 files changed, 288 insertions(+), 60 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.h b/AWSS3/AWSS3TransferUtility.h index 177c6578353..58c8d93d3e2 100644 --- a/AWSS3/AWSS3TransferUtility.h +++ b/AWSS3/AWSS3TransferUtility.h @@ -620,6 +620,30 @@ handleEventsForBackgroundURLSession:(NSString *)identifier expression:(nullable AWSS3TransferUtilityDownloadExpression *)expression completionHandler:(nullable AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler; +/** + Suspends multipart upload + @param multipartUploadTask task + */ +- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask; + +/** + Suspends all multipart uploads. + @param completionHandler completion handler + */ +- (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler; + +/** + Resumes a multipart upload. + @param multiPartUploadTask The task to resume + */ +- (nullable NSError *)resumeMultipartUpload:(nonnull AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask; + +/** + Resumes all multipart uploads. + @param completionHandler completion handler + */ +- (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler; + /** Assigns progress feedback and completion handler blocks. This method should be called when the app was suspended while the transfer is still happening. diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 812348597b2..e130cebfe25 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -321,8 +321,11 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur //Setup internal Data Structures - _taskDictionary = [AWSSynchronizedMutableDictionary new]; - _completedTaskDictionary = [AWSSynchronizedMutableDictionary new]; + AWSSynchronizedMutableDictionary *taskDictionary = [AWSSynchronizedMutableDictionary new]; + AWSSynchronizedMutableDictionary *completedTaskDictionary = taskDictionary.syncedDictionary; + + _taskDictionary = taskDictionary; + _completedTaskDictionary = completedTaskDictionary; //Instantiate the Database Helper self.databaseQueue = [AWSS3TransferUtilityDatabaseHelper createDatabase:self.cacheDirectoryPath]; @@ -566,11 +569,22 @@ - (void) markTransferAsCompleted:(AWSS3TransferUtilityTask *) transferUtilityTas AWSDDLogError(@"Task [%lu] has an error status [%@]. Marking transfer status as Error", (unsigned long)transferUtilityTask.taskIdentifier, taskError); transferUtilityTask.error = taskError; } - [self.completedTaskDictionary setObject:transferUtilityTask forKey:transferUtilityTask.transferID]; - [self.taskDictionary removeObjectForKey:@(transferUtilityTask.taskIdentifier)]; + + [self completeTransferUtilityTask:transferUtilityTask]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityTask.transferID databaseQueue:self->_databaseQueue]; } +- (void)completeTransferUtilityTask:(AWSS3TransferUtilityTask *)transferUtilityTask { + [AWSSynchronizedMutableDictionary mutateSyncedDictionaries:@[self.taskDictionary, self.completedTaskDictionary] withBlock:^(NSUUID * instanceKey, NSMutableDictionary *dictionary) { + if ([instanceKey isEqual:self.completedTaskDictionary.instanceKey]) { + [dictionary setObject:transferUtilityTask forKey:transferUtilityTask.transferID]; + } else if ([instanceKey isEqual:self.taskDictionary.instanceKey]) { + [dictionary removeObjectForKey:@(transferUtilityTask.taskIdentifier)]; + } + }]; +} + - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary { //At this point, we have finished iterating through the tasks present in the NSURLSession and removed all the matching ones from the transferRequests dictionary. @@ -974,7 +988,7 @@ - (NSURLSessionUploadTask *)getURLSessionUploadTaskWithRequest:(NSURLRequest *) } transferUtilityUploadTask.sessionTask = uploadTask; - if ( startTransfer) { + if (startTransfer) { transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusInProgress; } else { @@ -1118,7 +1132,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler { //Validate input parameters. - AWSTask *error = [self validateParameters:bucket key:key fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; + AWSTask *error = [self validateParameters:bucket key:key fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; if (error) { if (temporaryFileCreated) { [self removeFile:[fileURL path]]; @@ -1230,19 +1244,16 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa NSError *subTaskCreationError; //Move to inProgress or Waiting based on concurrency limit - if (i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.inProgressPartsDictionary]; - if(!subTaskCreationError) { - subTask.status = AWSS3TransferUtilityTransferStatusInProgress; - AWSDDLogDebug(@"Added task for part [%@] to inProgress list", subTask.partNumber); - } - } - else { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.waitingPartsDictionary]; - if (!subTaskCreationError ) { - subTask.status = AWSS3TransferUtilityTransferStatusWaiting; - AWSDDLogDebug(@"Added task for part [%@] to Waiting list", subTask.partNumber); - } + BOOL isUnderConcurrencyLimit = i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; + subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO + internalDictionaryToAddSubTaskTo: isUnderConcurrencyLimit ? + transferUtilityMultiPartUploadTask.inProgressPartsDictionary : + transferUtilityMultiPartUploadTask.waitingPartsDictionary]; + if (!subTaskCreationError) { + subTask.status = isUnderConcurrencyLimit ? AWSS3TransferUtilityTransferStatusInProgress : + AWSS3TransferUtilityTransferStatusWaiting; + AWSDDLogDebug(@"Added task for part [%@] to %@ list", subTask.partNumber, isUnderConcurrencyLimit ? + @"inProgress" : @"Waiting"); } if (!subTaskCreationError) { @@ -1263,7 +1274,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa } //Start the subTasks - for(id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { + for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); [subTask.sessionTask resume]; @@ -1507,6 +1518,39 @@ -(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) tra return error; } + +- (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask { + //Call the Multipart completion step here. + AWSTask * finishTask = [self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + [finishTask continueWithBlock:^id (AWSTask *task) { + if (task.error) { + AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); + transferUtilityMultiPartUploadTask.error = task.error; + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; + + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; + } + else { + //Set progress to 100% and call progressBlock. + AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); + transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; + + transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.totalUnitCount; + if (transferUtilityMultiPartUploadTask.expression.progressBlock ) { + transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); + } + } + + [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; + + //Call the callback function is specified. + [self completeTask:transferUtilityMultiPartUploadTask]; + return nil; + }]; + +} + -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask subTask: (AWSS3TransferUtilityUploadSubTask *) subTask startTransfer: (BOOL) startTransfer { @@ -1532,14 +1576,14 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer NSError *subTaskCreationError; - if (inWaitingPartsDictionary ) { + if (inWaitingPartsDictionary) { subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.waitingPartsDictionary]; } else { subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.inProgressPartsDictionary]; } - if ( subTaskCreationError ) { + if (subTaskCreationError) { //cancel the multipart transfer [transferUtilityMultiPartUploadTask cancel]; transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; @@ -1877,10 +1921,157 @@ - (NSMutableArray *) getTasksHelper:(AWSSynchronizedMutableDictionary *)dictiona return tasks; } +#pragma mark - Suspend and Resume + +- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { + // uses NSNumber * for taskIdentifier to set values in inProgressPartsDictionary + // these tasks need to be canceled and cleared out and replaced with tasks which have + // not been started yet so that completed uploads of parts do not cause pending part + // uploads to be started. + + [multipartUploadTask suspend]; + + for (NSNumber *taskIdentifier in multipartUploadTask.inProgressPartsDictionary.allKeys) { + AWSS3TransferUtilityUploadSubTask *inProgressSubTask = multipartUploadTask.inProgressPartsDictionary[taskIdentifier]; + NSCAssert(inProgressSubTask != nil, @"Task is required"); + + // cancel the URLSessionTask + [inProgressSubTask.sessionTask cancel]; + + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; + subTask.transferID = multipartUploadTask.transferID; + subTask.partNumber = inProgressSubTask.partNumber; + subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; + subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; + subTask.totalBytesSent = (long long) 0; + subTask.responseData = @""; + subTask.file = @""; + subTask.eTag = @""; + subTask.status = AWSS3TransferUtilityTransferStatusPaused; + + NSError *error = [self createUploadSubTask:multipartUploadTask + subTask:subTask + startTransfer:NO + internalDictionaryToAddSubTaskTo:multipartUploadTask.waitingPartsDictionary]; + + if (error) { + AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); + return error; + } else { + multipartUploadTask.inProgressPartsDictionary[taskIdentifier] = nil; + + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; + + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:multipartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; + } + } + + return nil; +} + +/// Suspends all active tasks. Downloads will be +- (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + if (task.error) { + AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); + completionHandler(task.error); + return nil; + } else { + AWSDDLogInfo(@"Suspending MultiPartForUpload Tasks: %lu", task.result.count); + NSError *error = nil; + NSArray * tasks = task.result; + for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { + NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); + error = [self suspendMultipartUpload:multipartUploadTask]; + if (error) { + if (completionHandler) { + completionHandler(error); + } + return nil; + } + } + } + + if (completionHandler) { + completionHandler(nil); + } + + return nil; + }]; + }); +} + +- (nullable NSError *)resumeMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask { + [multiPartUploadTask resume]; + + // move parts from waiting to in progress if under the concurrency limit + if (multiPartUploadTask.inProgressPartsDictionary.count < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; + while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Add to inProgress list + [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + + //Remove it from the waitingList + [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); + [nextSubTask.sessionTask resume]; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; + numberOfPartsInProgress++; + continue; + } + break; + } + } + + // Change status from paused to waiting + for (AWSS3TransferUtilityUploadSubTask * nextSubTask in multiPartUploadTask.waitingPartsDictionary.allValues) { + nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; + } + + // Complete multipart upload if in progress and waiting tasks are done + if (multiPartUploadTask.isDone) { + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)multiPartUploadTask.waitingPartsDictionary.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)multiPartUploadTask.inProgressPartsDictionary.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)multiPartUploadTask.completedPartsSet.count); + [self completeMultiPartForUploadTask:multiPartUploadTask]; + } + + return nil; +} + +- (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + if (task.error) { + AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); + completionHandler(task.error); + return nil; + } else { + AWSDDLogInfo(@"Resuming MultiPartForUpload Tasks: %lu", task.result.count); + NSArray * tasks = task.result; + for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { + NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); + [self resumeMultipartUpload:multipartUploadTask]; + } + } + + if (completionHandler) { + completionHandler(nil); + } + + return nil; + }]; + }); +} + #pragma mark - Internal helper methods - (AWSTask *)callFinishMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask { - NSMutableArray *completedParts = [NSMutableArray arrayWithCapacity:[uploadTask.completedPartsSet count]]; NSMutableDictionary *tempDictionary = [NSMutableDictionary new]; @@ -2103,6 +2294,11 @@ - (void)URLSession:(NSURLSession *)session AWSDDLogDebug(@"Unable to find information for task %lu in inProgress Dictionary", (unsigned long)task.taskIdentifier); return; } + + if (subTask.status == AWSS3TransferUtilityTransferStatusPaused) { + AWSDDLogDebug(@"Subtask is paused: %lu", (unsigned long)task.taskIdentifier); + return; + } //Check if the task was cancelled. if (transferUtilityMultiPartUploadTask.cancelled) { @@ -2140,7 +2336,7 @@ - (void)URLSession:(NSURLSession *)session //Error is not retriable. transferUtilityMultiPartUploadTask.error = updatedError; transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; - + //Execute call back if provided. [self completeTask:transferUtilityMultiPartUploadTask]; @@ -2207,7 +2403,7 @@ - (void)URLSession:(NSURLSession *)session } else if ([transferUtilityMultiPartUploadTask.inProgressPartsDictionary count] == 0) { //If there are no more inProgress parts, then we are done. - + //Validate that all the content has been uploaded. int64_t totalBytesSent = 0; for (AWSS3TransferUtilityUploadSubTask *aSubTask in transferUtilityMultiPartUploadTask.completedPartsSet) { @@ -2235,36 +2431,8 @@ - (void)URLSession:(NSURLSession *)session [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return; } - - - //Call the Multipart completion step here. - [[ self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask] continueWithBlock:^id (AWSTask *task) { - if (task.error) { - AWSDDLogError(@"Error finishing up MultiPartForUpload Task[%@]", task.error); - transferUtilityMultiPartUploadTask.error = error; - transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; - - //Abort the request, so the server can clean up any partials. - [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; - - } - else { - //Set progress to 100% and call progressBlock. - AWSDDLogInfo(@"Completed Multipart Transfer: %@", transferUtilityMultiPartUploadTask.uploadID); - transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; - - transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.totalUnitCount; - if (transferUtilityMultiPartUploadTask.expression.progressBlock ) { - transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); - } - } - - [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; - - //Call the callback function is specified. - [self completeTask:transferUtilityMultiPartUploadTask]; - return nil; - }]; + + [self completeMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; } } } diff --git a/AWSS3/AWSS3TransferUtilityDatabaseHelper.m b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m index 76e80a64226..fcdc27aa954 100644 --- a/AWSS3/AWSS3TransferUtilityDatabaseHelper.m +++ b/AWSS3/AWSS3TransferUtilityDatabaseHelper.m @@ -127,6 +127,9 @@ + (void) updateTransferRequestInDB: (NSString *) transferID status: (AWSS3TransferUtilityTransferStatusType) status retry_count: (NSUInteger) retryCount databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { + if (eTag == nil) { + eTag = @""; + } NSString *const AWSS3TransferUtilityUpdateTransferUtilityStatusAndETag = @"UPDATE awstransfer " @"SET status=:status, etag = :etag, session_task_id = :session_task_id, retry_count = :retry_count " @"WHERE transfer_id=:transfer_id and " diff --git a/AWSS3/AWSS3TransferUtilityTasks.h b/AWSS3/AWSS3TransferUtilityTasks.h index 0140e3acbd8..b1a99eac077 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.h +++ b/AWSS3/AWSS3TransferUtilityTasks.h @@ -87,6 +87,20 @@ typedef void (^AWSS3TransferUtilityMultiPartProgressBlock) (AWSS3TransferUtility NSProgress *progress); +/** + The suspend multipart upload completion handler. + + @param error Returns the error object when the suspending failed. Returns `nil` on success. + */ +typedef void (^AWSS3TransferUtilityMultiPartUploadSuspendBlock) (NSError * _Nullable error); + +/** + The resume multipart upload completion handler. + + @param error Returns the error object when the resume failed. Returns `nil` on success. + */ +typedef void (^AWSS3TransferUtilityMultiPartUploadResumeBlock) (NSError * _Nullable error); + #pragma mark - AWSS3TransferUtilityTasks /** diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 0e91e5dfb44..4221db59583 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -64,7 +64,7 @@ - (void)suspend { //Pause called on a transfer that is not in progress. No op. return; } - + [self.sessionTask suspend]; self.status = AWSS3TransferUtilityTransferStatusPaused; [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID @@ -132,6 +132,10 @@ - (instancetype)init { return self; } +- (BOOL)isDone { + return _waitingPartsDictionary.count == 0 && _inProgressPartsDictionary.count == 0; +} + - (AWSS3TransferUtilityMultiPartUploadExpression *)expression { if (!_expression) { _expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; @@ -156,7 +160,10 @@ - (void)cancel { } - (void)resume { - if (self.status != AWSS3TransferUtilityTransferStatusPaused ) { + // no parts should be paused. instead all in progress part uploads should be canceled and + // replaced with a new subtask which has not been started so that resuming will instead + // start up to the number up to the concurrency limit. + if (self.status != AWSS3TransferUtilityTransferStatusPaused) { //Resume called on a transfer that hasn't been paused. No op. return; } @@ -173,6 +180,7 @@ - (void)resume { databaseQueue:self.databaseQueue]; [subTask.sessionTask resume]; } + self.status = AWSS3TransferUtilityTransferStatusInProgress; //Update the Master Record [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID @@ -189,9 +197,18 @@ - (void)suspend { //Pause called on a transfer that is not in progresss. No op. return; } - + for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { + // all in progress tasks should be cancelled and a new subtask should replace it which is + // put in the waiting dictionary and set with that status with a URLSessionTask which + // has not been started. + + // then resuming should start uploading a number of parts up to the concurrency limit. + AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + if (!subTask) { + continue; + } [subTask.sessionTask suspend]; subTask.status = AWSS3TransferUtilityTransferStatusPaused; @@ -214,7 +231,7 @@ - (void)suspend { databaseQueue:self.databaseQueue]; } --(void) setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { +- (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { self.expression.completionHandler = completionHandler; //If the task has already completed successfully diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 1918cabdb3f..7623f950a35 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -20,7 +20,7 @@ @interface AWSS3TransferUtilityTask() @property (strong, nonatomic) NSURLSessionTask *sessionTask; -@property (readwrite) NSUInteger taskIdentifier; +@property (nonatomic, readwrite) NSUInteger taskIdentifier; @property (strong, nonatomic) NSString *transferID; @property (strong, nonatomic) NSString *bucket; @property (strong, nonatomic) NSString *key; @@ -53,12 +53,14 @@ @property (copy) NSString * uploadID; @property BOOL cancelled; @property BOOL temporaryFileCreated; +@property (readonly) BOOL isDone; @property NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableSet *completedPartsSet; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; @property int partNumber; @property NSNumber *contentLength; + @end @interface AWSS3TransferUtilityDownloadTask() From 614a0b7fc02f4ba0249077b05816c831195c3a46 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Thu, 2 Jun 2022 10:01:11 -0700 Subject: [PATCH 02/11] updates CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3168476736..7193bba666e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ ## 2.27.10 -Features for next release +### New features + +- **AWSS3** + + - feat: implements suspend and resume for all multipart uploads (See [PR #4168](https://github.com/aws-amplify/aws-sdk-ios/pull/4168)) + ### Bug Fixes - **AWSCognito** From eb75a5ea7a1ac2514ec26e4ee22e6eb16288fcd1 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Thu, 2 Jun 2022 17:35:05 -0700 Subject: [PATCH 03/11] refactored but not functional --- AWSS3/AWSS3TransferUtility+HeaderHelper.m | 2 +- AWSS3/AWSS3TransferUtility.h | 20 +- AWSS3/AWSS3TransferUtility.m | 224 +++++++++++----------- AWSS3/AWSS3TransferUtilityTasks.h | 5 + AWSS3/AWSS3TransferUtilityTasks.m | 185 +++++++++++++----- AWSS3/AWSS3TransferUtility_private.h | 27 ++- 6 files changed, 284 insertions(+), 179 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility+HeaderHelper.m b/AWSS3/AWSS3TransferUtility+HeaderHelper.m index 36c0b94b444..d26ff04433a 100644 --- a/AWSS3/AWSS3TransferUtility+HeaderHelper.m +++ b/AWSS3/AWSS3TransferUtility+HeaderHelper.m @@ -52,7 +52,7 @@ - (void)assignRequestParameters:(AWSS3GetPreSignedURLRequest *)getPreSignedURLRe @implementation AWSS3TransferUtility (HeaderHelper) --(void) filterAndAssignHeaders:(NSDictionary *) requestHeaders +- (void)filterAndAssignHeaders:(NSDictionary *) requestHeaders getPresignedURLRequest:(AWSS3GetPreSignedURLRequest *) getPresignedURLRequest URLRequest: (NSMutableURLRequest *) URLRequest { diff --git a/AWSS3/AWSS3TransferUtility.h b/AWSS3/AWSS3TransferUtility.h index 58c8d93d3e2..7e8036bf498 100644 --- a/AWSS3/AWSS3TransferUtility.h +++ b/AWSS3/AWSS3TransferUtility.h @@ -620,11 +620,11 @@ handleEventsForBackgroundURLSession:(NSString *)identifier expression:(nullable AWSS3TransferUtilityDownloadExpression *)expression completionHandler:(nullable AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler; -/** - Suspends multipart upload - @param multipartUploadTask task - */ -- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask; +///** +// Suspends multipart upload +// @param multipartUploadTask task +// */ +//- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask; /** Suspends all multipart uploads. @@ -632,11 +632,11 @@ handleEventsForBackgroundURLSession:(NSString *)identifier */ - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler; -/** - Resumes a multipart upload. - @param multiPartUploadTask The task to resume - */ -- (nullable NSError *)resumeMultipartUpload:(nonnull AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask; +///** +// Resumes a multipart upload. +// @param multiPartUploadTask The task to resume +// */ +//- (nullable NSError *)resumeMultipartUpload:(nonnull AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask; /** Resumes all multipart uploads. diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index e130cebfe25..a9baf8495b9 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -68,13 +68,11 @@ - (AWSTask *) validateParameters: (NSString * )bucket key:(NSString *)key accele @end @interface AWSS3TransferUtility (HeaderHelper) --(void) filterAndAssignHeaders:(NSDictionary *) requestHeaders +- (void)filterAndAssignHeaders:(NSDictionary *) requestHeaders getPresignedURLRequest:(AWSS3GetPreSignedURLRequest *) getPresignedURLRequest URLRequest: (NSMutableURLRequest *) URLRequest; @end - - #pragma mark - AWSS3TransferUtility @implementation AWSS3TransferUtility @@ -756,6 +754,7 @@ -( AWSS3TransferUtilityMultiPartUploadTask *) hydrateMultiPartUploadTask: (NSMut databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + [transferUtilityMultiPartUploadTask integrateWithTransferUtility:self]; transferUtilityMultiPartUploadTask.nsURLSessionID = sessionIdentifier; transferUtilityMultiPartUploadTask.databaseQueue = databaseQueue; transferUtilityMultiPartUploadTask.transferType = [task objectForKey:@"transfer_type"]; @@ -1078,6 +1077,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa if (!result) { if (completionHandler) { AWSS3TransferUtilityMultiPartUploadTask *uploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + [uploadTask integrateWithTransferUtility:self]; uploadTask.bucket = bucket; uploadTask.key = key; completionHandler(uploadTask, error); @@ -1154,6 +1154,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa //Create TransferUtility Multipart Upload Task AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; + [transferUtilityMultiPartUploadTask integrateWithTransferUtility:self]; transferUtilityMultiPartUploadTask.nsURLSessionID = self.sessionIdentifier; transferUtilityMultiPartUploadTask.databaseQueue = self.databaseQueue; transferUtilityMultiPartUploadTask.transferType = @"MULTI_PART_UPLOAD"; @@ -1419,19 +1420,16 @@ - (nullable NSURL *)createPartialFile:(NSURL *)fileURL return partialFileURL; } --(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask - subTask: (AWSS3TransferUtilityUploadSubTask *) subTask -internalDictionaryToAddSubTaskTo: (NSMutableDictionary *) internalDictionaryToAddSubTaskTo - -{ +- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask +internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo { return [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES internalDictionaryToAddSubTaskTo:internalDictionaryToAddSubTaskTo]; } --(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask - subTask: (AWSS3TransferUtilityUploadSubTask *) subTask - startTransfer: (BOOL) startTransfer - internalDictionaryToAddSubTaskTo: (NSMutableDictionary *) internalDictionaryToAddSubTaskTo -{ +- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask + startTransfer:(BOOL)startTransfer + internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo { __block NSError *error = nil; //Create a temporary part file if required. if (!(subTask.file || [subTask.file isEqualToString:@""]) || ![[NSFileManager defaultManager] fileExistsAtPath:subTask.file]) { @@ -1494,7 +1492,7 @@ -(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) tra } //Register transferUtilityMultiPartUploadTask into the taskDictionary for easy lookup in the NSURLCallback - [self->_taskDictionary setObject:transferUtilityMultiPartUploadTask forKey:@(subTask.taskIdentifier)]; + [self registerMultipartUploadTask:transferUtilityMultiPartUploadTask taskIdentifier:subTask.taskIdentifier]; //Add to required internal dictionary [internalDictionaryToAddSubTaskTo setObject:subTask forKey:@(subTask.taskIdentifier)]; @@ -1518,7 +1516,6 @@ -(NSError *) createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) tra return error; } - - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask { //Call the Multipart completion step here. AWSTask * finishTask = [self callFinishMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; @@ -1548,7 +1545,6 @@ - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask [self completeTask:transferUtilityMultiPartUploadTask]; return nil; }]; - } -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask @@ -1833,6 +1829,11 @@ - (void)internalEnumerateToAssignBlocksForUploadTask:(void (^)(AWSS3TransferUtil } } +- (void)registerMultipartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask + taskIdentifier:(NSUInteger)taskIdentifier { + [self.taskDictionary setObject:multiPartUploadTask forKey:@(taskIdentifier)]; +} + - (AWSS3TransferUtilityTask *)findTransferUtilityTask:(NSURLSessionTask *)task { id obj = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; if (!obj) { @@ -1923,78 +1924,73 @@ - (NSMutableArray *) getTasksHelper:(AWSSynchronizedMutableDictionary *)dictiona #pragma mark - Suspend and Resume -- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { - // uses NSNumber * for taskIdentifier to set values in inProgressPartsDictionary - // these tasks need to be canceled and cleared out and replaced with tasks which have - // not been started yet so that completed uploads of parts do not cause pending part - // uploads to be started. - - [multipartUploadTask suspend]; - - for (NSNumber *taskIdentifier in multipartUploadTask.inProgressPartsDictionary.allKeys) { - AWSS3TransferUtilityUploadSubTask *inProgressSubTask = multipartUploadTask.inProgressPartsDictionary[taskIdentifier]; - NSCAssert(inProgressSubTask != nil, @"Task is required"); - - // cancel the URLSessionTask - [inProgressSubTask.sessionTask cancel]; - - AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; - subTask.transferID = multipartUploadTask.transferID; - subTask.partNumber = inProgressSubTask.partNumber; - subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; - subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; - subTask.totalBytesSent = (long long) 0; - subTask.responseData = @""; - subTask.file = @""; - subTask.eTag = @""; - subTask.status = AWSS3TransferUtilityTransferStatusPaused; - - NSError *error = [self createUploadSubTask:multipartUploadTask - subTask:subTask - startTransfer:NO - internalDictionaryToAddSubTaskTo:multipartUploadTask.waitingPartsDictionary]; - - if (error) { - AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); - return error; - } else { - multipartUploadTask.inProgressPartsDictionary[taskIdentifier] = nil; - - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; - - [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:multipartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; - } - } - - return nil; -} +//- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { +// // uses NSNumber * for taskIdentifier to set values in inProgressPartsDictionary +// // these tasks need to be canceled and cleared out and replaced with tasks which have +// // not been started yet so that completed uploads of parts do not cause pending part +// // uploads to be started. +// +// for (NSNumber *taskIdentifier in multipartUploadTask.inProgressPartsDictionary.allKeys) { +// AWSS3TransferUtilityUploadSubTask *inProgressSubTask = multipartUploadTask.inProgressPartsDictionary[taskIdentifier]; +// NSCAssert(inProgressSubTask != nil, @"Task is required"); +// +// // cancel the URLSessionTask +// [inProgressSubTask.sessionTask cancel]; +// +// AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; +// subTask.transferID = multipartUploadTask.transferID; +// subTask.partNumber = inProgressSubTask.partNumber; +// subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; +// subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; +// subTask.totalBytesSent = (long long) 0; +// subTask.responseData = @""; +// subTask.file = @""; +// subTask.eTag = @""; +// subTask.status = AWSS3TransferUtilityTransferStatusPaused; +// +// NSError *error = [self createUploadSubTask:multipartUploadTask +// subTask:subTask +// startTransfer:NO +// internalDictionaryToAddSubTaskTo:multipartUploadTask.waitingPartsDictionary]; +// +// if (error) { +// AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); +// return error; +// } else { +// multipartUploadTask.inProgressPartsDictionary[taskIdentifier] = nil; +// +// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; +// +// [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:multipartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; +// } +// } +// +// return nil; +//} /// Suspends all active tasks. Downloads will be - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + NSError *error = nil; if (task.error) { AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); completionHandler(task.error); return nil; } else { AWSDDLogInfo(@"Suspending MultiPartForUpload Tasks: %lu", task.result.count); - NSError *error = nil; NSArray * tasks = task.result; for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); - error = [self suspendMultipartUpload:multipartUploadTask]; - if (error) { - if (completionHandler) { - completionHandler(error); - } - return nil; + [multipartUploadTask suspend]; + if (multipartUploadTask.error != nil) { + error = multipartUploadTask.error; } } } if (completionHandler) { - completionHandler(nil); + completionHandler(error); } return nil; @@ -2002,51 +1998,50 @@ - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferU }); } -- (nullable NSError *)resumeMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask { - [multiPartUploadTask resume]; - - // move parts from waiting to in progress if under the concurrency limit - if (multiPartUploadTask.inProgressPartsDictionary.count < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; - while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { - //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; - - //Add to inProgress list - [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; - - //Remove it from the waitingList - [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; - AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); - [nextSubTask.sessionTask resume]; - nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; - numberOfPartsInProgress++; - continue; - } - break; - } - } - - // Change status from paused to waiting - for (AWSS3TransferUtilityUploadSubTask * nextSubTask in multiPartUploadTask.waitingPartsDictionary.allValues) { - nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; - } - - // Complete multipart upload if in progress and waiting tasks are done - if (multiPartUploadTask.isDone) { - AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)multiPartUploadTask.waitingPartsDictionary.count); - AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)multiPartUploadTask.inProgressPartsDictionary.count); - AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)multiPartUploadTask.completedPartsSet.count); - [self completeMultiPartForUploadTask:multiPartUploadTask]; - } - - return nil; -} +//- (nullable NSError *)resumeMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask { +// // move parts from waiting to in progress if under the concurrency limit +// if (multiPartUploadTask.inProgressPartsDictionary.count < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { +// long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; +// while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { +// if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { +// //Get a part from the waitingList +// AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; +// +// //Add to inProgress list +// [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; +// +// //Remove it from the waitingList +// [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; +// AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); +// [nextSubTask.sessionTask resume]; +// nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; +// numberOfPartsInProgress++; +// continue; +// } +// break; +// } +// } +// +// // Change status from paused to waiting +// for (AWSS3TransferUtilityUploadSubTask * nextSubTask in multiPartUploadTask.waitingPartsDictionary.allValues) { +// nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; +// } +// +// // Complete multipart upload if in progress and waiting tasks are done +// if (multiPartUploadTask.isDone) { +// AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)multiPartUploadTask.waitingPartsDictionary.count); +// AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)multiPartUploadTask.inProgressPartsDictionary.count); +// AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)multiPartUploadTask.completedPartsSet.count); +// [self completeMultiPartForUploadTask:multiPartUploadTask]; +// } +// +// return nil; +//} - (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { + NSError *error = nil; if (task.error) { AWSDDLogError(@"Error getting MultiPartForUpload Tasks [%@]", task.error); completionHandler(task.error); @@ -2056,12 +2051,15 @@ - (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUt NSArray * tasks = task.result; for (AWSS3TransferUtilityMultiPartUploadTask *multipartUploadTask in tasks) { NSCAssert(multipartUploadTask != nil, @"Task cannot be nil"); - [self resumeMultipartUpload:multipartUploadTask]; + [multipartUploadTask resume]; + if (multipartUploadTask.error != nil) { + error = multipartUploadTask.error; + } } } if (completionHandler) { - completionHandler(nil); + completionHandler(error); } return nil; @@ -2463,7 +2461,7 @@ - (void)URLSession:(NSURLSession *)session downloadTask.error = error; } - if(!downloadTask.error ) { + if (!downloadTask.error ) { downloadTask.status = AWSS3TransferUtilityTransferStatusCompleted; } else { diff --git a/AWSS3/AWSS3TransferUtilityTasks.h b/AWSS3/AWSS3TransferUtilityTasks.h index b1a99eac077..d9d51de34b2 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.h +++ b/AWSS3/AWSS3TransferUtilityTasks.h @@ -154,6 +154,11 @@ typedef void (^AWSS3TransferUtilityMultiPartUploadResumeBlock) (NSError * _Nulla */ @property (nullable, readonly) NSHTTPURLResponse *response; +/** + Error after operation. + */ +@property (nullable, readonly) NSError *error; + /** Cancels the task. */ diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 4221db59583..d3302866f7b 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -19,6 +19,7 @@ #import "AWSS3PreSignedURL.h" #import +#import "AWSS3TransferUtility.h" #import "AWSS3TransferUtilityTasks+Completion.h" #import "AWSS3TransferUtility_private.h" @@ -167,29 +168,67 @@ - (void)resume { //Resume called on a transfer that hasn't been paused. No op. return; } + + NSCAssert(self.transferUtility != nil, @"Transfer Utility must be provided."); - for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; - subTask.status = AWSS3TransferUtilityTransferStatusInProgress; - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID - partNumber:subTask.partNumber - taskIdentifier:subTask.taskIdentifier - eTag:subTask.eTag - status:subTask.status - retry_count:self.retryCount - databaseQueue:self.databaseQueue]; - [subTask.sessionTask resume]; +// for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { +// AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; +// subTask.status = AWSS3TransferUtilityTransferStatusInProgress; +// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID +// partNumber:subTask.partNumber +// taskIdentifier:subTask.taskIdentifier +// eTag:subTask.eTag +// status:subTask.status +// retry_count:self.retryCount +// databaseQueue:self.databaseQueue]; +// [subTask.sessionTask resume]; +// } +// +// self.status = AWSS3TransferUtilityTransferStatusInProgress; +// //Update the Master Record +// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID +// partNumber:@0 +// taskIdentifier:0 +// eTag:@"" +// status:self.status +// retry_count:self.retryCount +// databaseQueue:self.databaseQueue]; + + // move parts from waiting to in progress if under the concurrency limit + if (self.inProgressPartsDictionary.count < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + long numberOfPartsInProgress = [self.inProgressPartsDictionary count]; + while (numberOfPartsInProgress < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { + if ([self.waitingPartsDictionary count] > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Add to inProgress list + [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + + //Remove it from the waitingList + [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); + [nextSubTask.sessionTask resume]; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; + numberOfPartsInProgress++; + continue; + } + break; + } } - self.status = AWSS3TransferUtilityTransferStatusInProgress; - //Update the Master Record - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID - partNumber:@0 - taskIdentifier:0 - eTag:@"" - status:self.status - retry_count:self.retryCount - databaseQueue:self.databaseQueue]; + // Change status from paused to waiting + for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingPartsDictionary.allValues) { + nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; + } + + // Complete multipart upload if in progress and waiting tasks are done + if (self.isDone) { + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); + [self.transferUtility completeMultiPartForUploadTask:self]; + } } - (void)suspend { @@ -198,41 +237,79 @@ - (void)suspend { return; } - for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { - // all in progress tasks should be cancelled and a new subtask should replace it which is - // put in the waiting dictionary and set with that status with a URLSessionTask which - // has not been started. + NSCAssert(self.transferUtility != nil, @"Transfer Utility must be provided."); - // then resuming should start uploading a number of parts up to the concurrency limit. +// for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { +// // all in progress tasks should be cancelled and a new subtask should replace it which is +// // put in the waiting dictionary and set with that status with a URLSessionTask which +// // has not been started. +// +// // then resuming should start uploading a number of parts up to the concurrency limit. +// +// AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; +// if (!subTask) { +// continue; +// } +// [subTask.sessionTask suspend]; +// subTask.status = AWSS3TransferUtilityTransferStatusPaused; +// +// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID +// partNumber:subTask.partNumber +// taskIdentifier:subTask.taskIdentifier +// eTag:subTask.eTag +// status:subTask.status +// retry_count:self.retryCount +// databaseQueue:self.databaseQueue]; +// } +// +// self.status = AWSS3TransferUtilityTransferStatusPaused; +// //Update the Master Record +// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID +// partNumber:@0 +// taskIdentifier:0 +// eTag:@"" +// status:self.status +// retry_count:self.retryCount +// databaseQueue:self.databaseQueue]; + + // Cancel session task for all subtasks which are in progress and set status to paused + for (NSNumber *taskIdentifier in self.inProgressPartsDictionary.allKeys) { + AWSS3TransferUtilityUploadSubTask *inProgressSubTask = self.inProgressPartsDictionary[taskIdentifier]; + NSCAssert(inProgressSubTask != nil, @"Task is required"); + + // cancel the URLSessionTask + [inProgressSubTask.sessionTask cancel]; + + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; + subTask.transferID = self.transferID; + subTask.partNumber = inProgressSubTask.partNumber; + subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; + subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; + subTask.totalBytesSent = (long long) 0; + subTask.responseData = @""; + subTask.file = @""; + subTask.eTag = @""; + subTask.status = AWSS3TransferUtilityTransferStatusPaused; - AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; - if (!subTask) { - continue; + NSError *error = [self.transferUtility createUploadSubTask:self + subTask:subTask + startTransfer:NO + internalDictionaryToAddSubTaskTo:self.waitingPartsDictionary]; + + if (error) { + AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); + self.error = error; + } else { + self.inProgressPartsDictionary[taskIdentifier] = nil; + + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; + + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:self subTask:subTask databaseQueue:self.databaseQueue]; } - [subTask.sessionTask suspend]; - subTask.status = AWSS3TransferUtilityTransferStatusPaused; - - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID - partNumber:subTask.partNumber - taskIdentifier:subTask.taskIdentifier - eTag:subTask.eTag - status:subTask.status - retry_count:self.retryCount - databaseQueue:self.databaseQueue]; } - self.status = AWSS3TransferUtilityTransferStatusPaused; - //Update the Master Record - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID - partNumber:@0 - taskIdentifier:0 - eTag:@"" - status:self.status - retry_count:self.retryCount - databaseQueue:self.databaseQueue]; } - (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { - self.expression.completionHandler = completionHandler; //If the task has already completed successfully //Or the task has completed with error, complete the task @@ -241,10 +318,14 @@ - (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandl } } --(void) setProgressBlock:(AWSS3TransferUtilityMultiPartProgressBlock)progressBlock { +- (void)setProgressBlock:(AWSS3TransferUtilityMultiPartProgressBlock)progressBlock { self.expression.progressBlock = progressBlock; } +- (void)integrateWithTransferUtility:(AWSS3TransferUtility *)transferUtility { + self.transferUtility = transferUtility; +} + @end @implementation AWSS3TransferUtilityDownloadTask @@ -256,14 +337,14 @@ - (AWSS3TransferUtilityDownloadExpression *)expression { return _expression; } --(void) cancel { +- (void)cancel { self.cancelled = YES; self.status = AWSS3TransferUtilityTransferStatusCancelled; [self.sessionTask cancel]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:self.transferID databaseQueue:self.databaseQueue]; } --(void) setCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler { +- (void)setCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler { self.expression.completionHandler = completionHandler; //If the task has already completed successfully @@ -273,7 +354,7 @@ -(void) setCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock } } --(void) setProgressBlock:(AWSS3TransferUtilityProgressBlock)progressBlock { +- (void)setProgressBlock:(AWSS3TransferUtilityProgressBlock)progressBlock { self.expression.progressBlock = progressBlock; } diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 7623f950a35..386b4c5e55c 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -17,6 +17,24 @@ #import "AWSS3Service.h" #import "AWSS3PreSignedURL.h" +@class AWSS3TransferUtilityConfiguration; +@class AWSS3PreSignedURLBuilder; + +@interface AWSS3TransferUtility () + +- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask +internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo; + +- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask + startTransfer:(BOOL)startTransfer +internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo; + +- (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask; + +@end + @interface AWSS3TransferUtilityTask() @property (strong, nonatomic) NSURLSessionTask *sessionTask; @@ -26,7 +44,7 @@ @property (strong, nonatomic) NSString *key; @property (strong, nonatomic) NSData *data; @property (strong, nonatomic) NSURL *location; -@property (strong, nonatomic) NSError *error; +@property (readwrite, nonatomic) NSError *error; @property int retryCount; @property (copy) NSString *nsURLSessionID; @property (copy) NSString *file; @@ -54,12 +72,15 @@ @property BOOL cancelled; @property BOOL temporaryFileCreated; @property (readonly) BOOL isDone; -@property NSMutableDictionary *waitingPartsDictionary; -@property (strong, nonatomic) NSMutableSet *completedPartsSet; +@property (strong, nonatomic) NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; +@property (strong, nonatomic) NSMutableSet *completedPartsSet; @property int partNumber; @property NSNumber *contentLength; +@property (weak, nonatomic) AWSS3TransferUtility *transferUtility; + +- (void)integrateWithTransferUtility:(AWSS3TransferUtility *)transferUtility; @end From e02fa6a270b47af5c2b366b4285d324843386784 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 14:09:07 -0700 Subject: [PATCH 04/11] WIP --- AWSS3/AWSS3TransferUtility.m | 84 +++++++++----------------- AWSS3/AWSS3TransferUtilityTasks.m | 90 +++++++++++++++++++--------- AWSS3/AWSS3TransferUtility_private.h | 6 ++ 3 files changed, 98 insertions(+), 82 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index a9baf8495b9..d4efebbb64f 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -2099,7 +2099,7 @@ - (AWSTask *)callFinishMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUplo return [self.s3 completeMultipartUpload:compReq]; } -- (AWSTask *) callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *) uploadTask { +- (AWSTask *)callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask { AWSS3AbortMultipartUploadRequest *abortReq = [AWSS3AbortMultipartUploadRequest new]; abortReq.bucket = uploadTask.bucket; abortReq.uploadId = uploadTask.uploadID; @@ -2377,61 +2377,35 @@ - (void)URLSession:(NSURLSession *)session eTag:subTask.eTag status:subTask.status retry_count:transferUtilityMultiPartUploadTask.retryCount databaseQueue:self.databaseQueue]; + + [transferUtilityMultiPartUploadTask moveTasksToInProgress]; + [transferUtilityMultiPartUploadTask completeIfDone]; //If there are parts waiting to be uploaded, pick from the waiting parts list and move it to inProgress - if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { - long numberOfPartsInProgress = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary count]; - while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { - //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[transferUtilityMultiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; - - //Add to inProgress list - [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; - - //Remove it from the waitingList - [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; - AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), transferUtilityMultiPartUploadTask.uploadID); - [nextSubTask.sessionTask resume]; - numberOfPartsInProgress++; - continue; - } - break; - } - } - else if ([transferUtilityMultiPartUploadTask.inProgressPartsDictionary count] == 0) { - //If there are no more inProgress parts, then we are done. - - //Validate that all the content has been uploaded. - int64_t totalBytesSent = 0; - for (AWSS3TransferUtilityUploadSubTask *aSubTask in transferUtilityMultiPartUploadTask.completedPartsSet) { - totalBytesSent += aSubTask.totalBytesExpectedToSend; - } - - if (totalBytesSent != transferUtilityMultiPartUploadTask.contentLength.longLongValue ) { - NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", - transferUtilityMultiPartUploadTask.contentLength, @(totalBytesSent)]; - AWSDDLogDebug(@"%@", errorMessage); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage - forKey:@"Message"]; - - transferUtilityMultiPartUploadTask.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorClientError - userInfo:userInfo]; - - //Execute call back if provided. - [self completeTask:transferUtilityMultiPartUploadTask]; - - //Abort the request, so the server can clean up any partials. - [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; - - //clean up. - [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; - return; - } - - [self completeMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; - } +// if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { +// long numberOfPartsInProgress = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary count]; +// while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { +// if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { +// //Get a part from the waitingList +// AWSS3TransferUtilityUploadSubTask *nextSubTask = [[transferUtilityMultiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; +// +// //Add to inProgress list +// [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; +// +// //Remove it from the waitingList +// [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; +// AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), transferUtilityMultiPartUploadTask.uploadID); +// [nextSubTask.sessionTask resume]; +// numberOfPartsInProgress++; +// continue; +// } +// break; +// } +// } +// else +// if (transferUtilityMultiPartUploadTask.inProgressPartsDictionary.count == 0) { +// +// } } } else if ([task isKindOfClass:[NSURLSessionDownloadTask class]]) { @@ -2580,7 +2554,7 @@ - (void)completeTask:(AWSS3TransferUtilityTask *)task removeCompletedTask:(BOOL) } -- (void) cleanupForMultiPartUploadTask: (AWSS3TransferUtilityMultiPartUploadTask *) task { +- (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)task { //Add it to list of completed Tasks [self.completedTaskDictionary setObject:task forKey:task.transferID]; diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index d3302866f7b..89941ce3b3d 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -133,6 +133,10 @@ - (instancetype)init { return self; } +- (BOOL)isUnderConcurrencyLimit { + return self.inProgressPartsDictionary.count < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; +} + - (BOOL)isDone { return _waitingPartsDictionary.count == 0 && _inProgressPartsDictionary.count == 0; } @@ -194,41 +198,73 @@ - (void)resume { // retry_count:self.retryCount // databaseQueue:self.databaseQueue]; - // move parts from waiting to in progress if under the concurrency limit - if (self.inProgressPartsDictionary.count < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - long numberOfPartsInProgress = [self.inProgressPartsDictionary count]; - while (numberOfPartsInProgress < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - if ([self.waitingPartsDictionary count] > 0) { - //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; - - //Add to inProgress list - [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; - - //Remove it from the waitingList - [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; - AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); - [nextSubTask.sessionTask resume]; - nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; - numberOfPartsInProgress++; - continue; - } - break; - } - } + [self moveTasksToInProgress]; // Change status from paused to waiting for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingPartsDictionary.allValues) { nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; } + [self completeIfDone]; +} + +- (void)moveTasksToInProgress { + // move parts from waiting to in progress if under the concurrency limit + while (self.isUnderConcurrencyLimit && self.waitingPartsDictionary.count > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Add to inProgress list + [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + + //Remove it from the waitingList + [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); + [nextSubTask.sessionTask resume]; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; + } +} + +- (void)completeIfDone { // Complete multipart upload if in progress and waiting tasks are done - if (self.isDone) { - AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); - AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); - AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); - [self.transferUtility completeMultiPartForUploadTask:self]; + if (!self.isDone) { + return; } + + //If there are no more inProgress parts, then we are done. + + //Validate that all the content has been uploaded. + int64_t totalBytesSent = 0; + for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.completedPartsSet) { + totalBytesSent += aSubTask.totalBytesExpectedToSend; + } + + if (totalBytesSent != self.contentLength.longLongValue ) { + NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", + self.contentLength, @(totalBytesSent)]; + AWSDDLogDebug(@"%@", errorMessage); + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:@"Message"]; + + self.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorClientError + userInfo:userInfo]; + + //Execute call back if provided. + [self.transferUtility completeTask:self]; + + //Abort the request, so the server can clean up any partials. + [self.transferUtility callAbortMultiPartForUploadTask:self]; + + //clean up. + [self.transferUtility cleanupForMultiPartUploadTask:self]; + return; + } + + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); + [self.transferUtility completeMultiPartForUploadTask:self]; } - (void)suspend { diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 386b4c5e55c..965b7d8a294 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -31,6 +31,9 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS startTransfer:(BOOL)startTransfer internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo; +- (void)completeTask:(AWSS3TransferUtilityTask *)task; +- (AWSTask *)callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask; +- (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)task; - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask; @end @@ -71,6 +74,7 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property (copy) NSString * uploadID; @property BOOL cancelled; @property BOOL temporaryFileCreated; +@property (readonly) BOOL isUnderConcurrencyLimit; @property (readonly) BOOL isDone; @property (strong, nonatomic) NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; @@ -81,6 +85,8 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property (weak, nonatomic) AWSS3TransferUtility *transferUtility; - (void)integrateWithTransferUtility:(AWSS3TransferUtility *)transferUtility; +- (void)moveTasksToInProgress; +- (void)completeIfDone; @end From f9cb88124690540206763b9ec7e4f1ede0b57877 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 20:46:36 -0700 Subject: [PATCH 05/11] WIP --- AWSS3/AWSS3TransferUtility.m | 233 +++++++++++---------------- AWSS3/AWSS3TransferUtilityTasks.m | 168 +++++++++++-------- AWSS3/AWSS3TransferUtility_private.h | 5 +- 3 files changed, 199 insertions(+), 207 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index d4efebbb64f..632a691d1b2 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -373,7 +373,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary //If task is completed, no more processing is required. if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID databaseQueue:self.databaseQueue]; continue; } //Lodge in temporary Dictionary @@ -386,7 +386,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary //If task is completed, no more processing is required. if (transferUtilityDownloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { [self.completedTaskDictionary setObject:transferUtilityDownloadTask forKey:transferUtilityDownloadTask.transferID]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID databaseQueue:self.databaseQueue]; continue; } //Lodge in temporary Dictionary for linking @@ -402,7 +402,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary transferUtilityMultiPartUploadTask.status == AWSS3TransferUtilityTransferStatusCancelled || transferUtilityMultiPartUploadTask.status == AWSS3TransferUtilityTransferStatusError) { [self.completedTaskDictionary setObject:transferUtilityMultiPartUploadTask forKey:transferUtilityMultiPartUploadTask.transferID]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityMultiPartUploadTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityMultiPartUploadTask.transferID databaseQueue:self.databaseQueue]; continue; } @@ -418,7 +418,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:subTask.uploadID]; if ( !multiPartUploadTask ) { //Couldn't find the multipart upload master record. Must be an orphan part record. Clean up the DB and continue. - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:subTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:subTask.transferID databaseQueue:self.databaseQueue]; continue; } //Check if the subTask is is already completed. If it is, add it to the completed parts list, update the progress object and go to the next iteration of the loop @@ -570,7 +570,7 @@ - (void) markTransferAsCompleted:(AWSS3TransferUtilityTask *) transferUtilityTas [self completeTransferUtilityTask:transferUtilityTask]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityTask.transferID databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityTask.transferID databaseQueue:self.databaseQueue]; } - (void)completeTransferUtilityTask:(AWSS3TransferUtilityTask *)transferUtilityTask { @@ -598,7 +598,7 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; //Delete the transfer record from the DB - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue ]; AWSDDLogDebug(@"Deleted transfer request from the DB"); } //Check if the transfer is in a paused state and the input file for the transfer exists. @@ -613,7 +613,7 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusUnknown; [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; //Delete the transfer record from the DB - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue ]; AWSDDLogDebug(@"Deleted transfer request from the DB"); } } @@ -631,7 +631,7 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask if (downloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue]; AWSDDLogDebug(@"Deleted transfer request from DB"); } else if (downloadTask.status == AWSS3TransferUtilityTransferStatusPaused) { @@ -644,7 +644,7 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask downloadTask.status = AWSS3TransferUtilityTransferStatusUnknown; [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; - [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self->_databaseQueue ]; + [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue]; AWSDDLogDebug(@"Deleted transfer request from DB"); } } @@ -913,7 +913,7 @@ - (AWSS3TransferUtilityUploadSubTask * ) hydrateMultiPartUploadSubTask:(NSMutabl transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusInProgress; //Add to Database - [AWSS3TransferUtilityDatabaseHelper insertUploadTransferRequestInDB:transferUtilityUploadTask databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertUploadTransferRequestInDB:transferUtilityUploadTask databaseQueue:self.databaseQueue]; return [self createUploadTask:transferUtilityUploadTask]; } @@ -1006,7 +1006,7 @@ - (NSURLSessionUploadTask *)getURLSessionUploadTaskWithRequest:(NSURLRequest *) eTag:@"" status:transferUtilityUploadTask.status retry_count:transferUtilityUploadTask.retryCount - databaseQueue:self->_databaseQueue]; + databaseQueue:self.databaseQueue]; if (startTransfer) { [uploadTask resume]; } @@ -1124,13 +1124,12 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa } - (AWSTask *)internalUploadFileUsingMultiPart:(NSURL *)fileURL - bucket:(NSString *)bucket - key:(NSString *)key - contentType:(NSString *)contentType - expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression - temporaryFileCreated: (BOOL) temporaryFileCreated - completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler { - + bucket:(NSString *)bucket + key:(NSString *)key + contentType:(NSString *)contentType + expression:(AWSS3TransferUtilityMultiPartUploadExpression *)expression + temporaryFileCreated: (BOOL) temporaryFileCreated + completionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock) completionHandler { //Validate input parameters. AWSTask *error = [self validateParameters:bucket key:key fileURL:fileURL accelerationModeEnabled:self.transferUtilityConfiguration.isAccelerateModeEnabled]; if (error) { @@ -1139,19 +1138,19 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa } return error; } - + //Create Expression if required and set values on the object if (!expression) { expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; } - //Override the content type value set in the expression object with the passed in parameter value. + //Override the content type value set in the expression object with the passed in parameter value. if (contentType) { - [expression setValue:contentType forRequestHeader:@"Content-Type"]; + [expression setValue:contentType forRequestHeader:@"Content-Type"]; } - + expression.completionHandler = completionHandler; - + //Create TransferUtility Multipart Upload Task AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; [transferUtilityMultiPartUploadTask integrateWithTransferUtility:self]; @@ -1170,7 +1169,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa //Get the size of the file and calculate the number of parts. NSError *nsError = nil; NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] - error:&nsError]; + error:&nsError]; if (!attributes) { if (transferUtilityMultiPartUploadTask.temporaryFileCreated) { [self removeFile:transferUtilityMultiPartUploadTask.file]; @@ -1192,7 +1191,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa uploadRequest.key = key; [AWSS3CreateMultipartUploadRequest propagateHeaderInformation:uploadRequest requestHeaders:transferUtilityMultiPartUploadTask.expression.requestHeaders]; - + //Initiate the multi part return [[self.s3 createMultipartUpload:uploadRequest] continueWithBlock:^id(AWSTask *task) { //Initiation of multi part failed. @@ -1216,22 +1215,22 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa code:AWSS3TransferUtilityErrorServerError userInfo:userInfo]; return [AWSTask taskWithError:error]; - } + } transferUtilityMultiPartUploadTask.uploadID = output.uploadId; - + //Save the Multipart Upload in the DB - [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestInDB:transferUtilityMultiPartUploadTask databaseQueue:self->_databaseQueue]; - + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestInDB:transferUtilityMultiPartUploadTask databaseQueue:self.databaseQueue]; + AWSDDLogInfo(@"Initiated multipart upload on server: %@", output.uploadId); AWSDDLogInfo(@"Concurrency Limit is %@", self.transferUtilityConfiguration.multiPartConcurrencyLimit); //Loop through the file and upload the parts one by one for (int32_t i = 1; i <= partCount ; i++) { NSUInteger dataLength = AWSS3TransferUtilityMultiPartSize; if (i == partCount) { - dataLength = fileSize - ( (i-1) * AWSS3TransferUtilityMultiPartSize); + dataLength = fileSize - ((i-1) * AWSS3TransferUtilityMultiPartSize); } - + AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; subTask.transferID = transferUtilityMultiPartUploadTask.transferID; subTask.partNumber = @(i); @@ -1241,26 +1240,27 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa subTask.responseData = @""; subTask.file = @""; subTask.eTag = @""; - + NSError *subTaskCreationError; - + //Move to inProgress or Waiting based on concurrency limit BOOL isUnderConcurrencyLimit = i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO internalDictionaryToAddSubTaskTo: isUnderConcurrencyLimit ? transferUtilityMultiPartUploadTask.inProgressPartsDictionary : - transferUtilityMultiPartUploadTask.waitingPartsDictionary]; + transferUtilityMultiPartUploadTask.waitingPartsDictionary]; if (!subTaskCreationError) { subTask.status = isUnderConcurrencyLimit ? AWSS3TransferUtilityTransferStatusInProgress : AWSS3TransferUtilityTransferStatusWaiting; AWSDDLogDebug(@"Added task for part [%@] to %@ list", subTask.partNumber, isUnderConcurrencyLimit ? @"inProgress" : @"Waiting"); } - + if (!subTaskCreationError) { //Save in Database after the file has been created, so that file can be referenced incase upload is paused and needs to be restarted. - [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask subTask:subTask - databaseQueue:self.databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask + subTask:subTask + databaseQueue:self.databaseQueue]; } else { //Abort the request, so the server can clean up any partials. [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; @@ -1271,16 +1271,14 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return [AWSTask taskWithError:subTaskCreationError]; } - } - + //Start the subTasks for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); [subTask.sessionTask resume]; } - return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; }]; return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; @@ -1669,7 +1667,7 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer transferUtilityDownloadTask.status = AWSS3TransferUtilityTransferStatusInProgress; //Create task in database - [AWSS3TransferUtilityDatabaseHelper insertDownloadTransferRequestInDB:transferUtilityDownloadTask databaseQueue:self->_databaseQueue]; + [AWSS3TransferUtilityDatabaseHelper insertDownloadTransferRequestInDB:transferUtilityDownloadTask databaseQueue:self.databaseQueue]; return [self createDownloadTask:transferUtilityDownloadTask]; } @@ -1985,6 +1983,7 @@ - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferU [multipartUploadTask suspend]; if (multipartUploadTask.error != nil) { error = multipartUploadTask.error; + break; } } } @@ -2054,6 +2053,7 @@ - (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUt [multipartUploadTask resume]; if (multipartUploadTask.error != nil) { error = multipartUploadTask.error; + break; } } } @@ -2313,99 +2313,16 @@ - (void)URLSession:(NSURLSession *)session //Check if there was an error. if (error) { - - //Retrying if a 500, 503 or 400 RequestTimeout error occured. - if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:subTask.responseData]) { - AWSDDLogDebug(@"Received a 500, 503 or 400 error. Response Data is [%@]", subTask.responseData); - if (transferUtilityMultiPartUploadTask.retryCount < self.transferUtilityConfiguration.retryLimit) { - AWSDDLogDebug(@"Retry count is below limit and error is retriable. "); - [self retryUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES]; - return; - } - } - - if(subTask.responseData != nil && [subTask.responseData isEqualToString:@""]) { - // Transfer's multi-part subtask does not have raw data access, so only check string based response data. - [self extractErrorInformation: [subTask responseData] - userInfo: userInfo]; - } - NSError *updatedError = [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:userInfo]; - - //Error is not retriable. - transferUtilityMultiPartUploadTask.error = updatedError; - transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusError; - - //Execute call back if provided. - [self completeTask:transferUtilityMultiPartUploadTask]; - - //Make sure all other parts that are in progress are canceled. - for (NSNumber *key in [transferUtilityMultiPartUploadTask.inProgressPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:key]; - [subTask.sessionTask cancel]; - } - - for (NSNumber *key in [transferUtilityMultiPartUploadTask.waitingPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.waitingPartsDictionary objectForKey:key]; - [subTask.sessionTask cancel]; - } - - //Abort the request, so the server can clean up any partials. - [self callAbortMultiPartForUploadTask:transferUtilityMultiPartUploadTask]; - - //clean up. - [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; - return; + [self processMultipartUploadTaskError:error + usingHTTPResponse:HTTPResponse + andUserInfo:userInfo + forSubTask:subTask + withMultipartUploadTask:transferUtilityMultiPartUploadTask]; + } else { + [transferUtilityMultiPartUploadTask completeUploadSubTask:subTask usingHTTPResponse:HTTPResponse]; + [transferUtilityMultiPartUploadTask moveWaitingTasksToInProgress]; + [transferUtilityMultiPartUploadTask completeIfDone]; } - - NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *) task.response; - subTask.eTag = (NSString *) HTTPResponse.allHeaderFields[@"ETAG"]; - - //Add it to completed parts and remove it from remaining parts. - [transferUtilityMultiPartUploadTask.completedPartsSet addObject:subTask]; - [transferUtilityMultiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; - //Update progress - transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.completedUnitCount - subTask.totalBytesSent + subTask.totalBytesExpectedToSend; - - //Delete the temporary upload file for this subTask - [self removeFile:subTask.file]; - subTask.status = AWSS3TransferUtilityTransferStatusCompleted; - - //Update Database - [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID - partNumber:subTask.partNumber - taskIdentifier:subTask.taskIdentifier - eTag:subTask.eTag - status:subTask.status - retry_count:transferUtilityMultiPartUploadTask.retryCount databaseQueue:self.databaseQueue]; - - [transferUtilityMultiPartUploadTask moveTasksToInProgress]; - [transferUtilityMultiPartUploadTask completeIfDone]; - - //If there are parts waiting to be uploaded, pick from the waiting parts list and move it to inProgress -// if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { -// long numberOfPartsInProgress = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary count]; -// while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { -// if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary count] > 0) { -// //Get a part from the waitingList -// AWSS3TransferUtilityUploadSubTask *nextSubTask = [[transferUtilityMultiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; -// -// //Add to inProgress list -// [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; -// -// //Remove it from the waitingList -// [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; -// AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), transferUtilityMultiPartUploadTask.uploadID); -// [nextSubTask.sessionTask resume]; -// numberOfPartsInProgress++; -// continue; -// } -// break; -// } -// } -// else -// if (transferUtilityMultiPartUploadTask.inProgressPartsDictionary.count == 0) { -// -// } } } else if ([task isKindOfClass:[NSURLSessionDownloadTask class]]) { @@ -2475,6 +2392,53 @@ - (void)URLSession:(NSURLSession *)session } } +- (void)processMultipartUploadTaskError:(NSError *)error + usingHTTPResponse:(NSHTTPURLResponse *)HTTPResponse + andUserInfo:(NSMutableDictionary *)userInfo + forSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask + withMultipartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { + //Retrying if a 500, 503 or 400 RequestTimeout error occured. + if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:subTask.responseData]) { + AWSDDLogDebug(@"Received a 500, 503 or 400 error. Response Data is [%@]", subTask.responseData); + if (multipartUploadTask.retryCount < self.transferUtilityConfiguration.retryLimit) { + AWSDDLogDebug(@"Retry count is below limit and error is retriable. "); + [self retryUploadSubTask:multipartUploadTask subTask:subTask startTransfer:YES]; + return; + } + } + + if (subTask.responseData != nil && [subTask.responseData isEqualToString:@""]) { + // Transfer's multi-part subtask does not have raw data access, so only check string based response data. + [self extractErrorInformation: [subTask responseData] + userInfo: userInfo]; + } + NSError *updatedError = [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:userInfo]; + + //Error is not retriable. + multipartUploadTask.error = updatedError; + multipartUploadTask.status = AWSS3TransferUtilityTransferStatusError; + + //Execute call back if provided. + [self completeTask:multipartUploadTask]; + + //Make sure all other parts that are in progress are canceled. + for (NSNumber *key in [multipartUploadTask.inProgressPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [multipartUploadTask.inProgressPartsDictionary objectForKey:key]; + [subTask.sessionTask cancel]; + } + + for (NSNumber *key in [multipartUploadTask.waitingPartsDictionary allKeys]) { + AWSS3TransferUtilityUploadSubTask *subTask = [multipartUploadTask.waitingPartsDictionary objectForKey:key]; + [subTask.sessionTask cancel]; + } + + //Abort the request, so the server can clean up any partials. + [self callAbortMultiPartForUploadTask:multipartUploadTask]; + + //clean up. + [self cleanupForMultiPartUploadTask:multipartUploadTask]; +} + - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent @@ -2632,8 +2596,7 @@ - (void)extractErrorInformation:(NSString *)responseString } } -- (void) removeFile: (NSString *) absolutePath -{ +- (void)removeFile:(NSString *)absolutePath { if (!absolutePath || ![[NSFileManager defaultManager ] fileExistsAtPath:absolutePath]) { return; } diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 89941ce3b3d..4eb0c491086 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -198,75 +198,15 @@ - (void)resume { // retry_count:self.retryCount // databaseQueue:self.databaseQueue]; - [self moveTasksToInProgress]; - // Change status from paused to waiting for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingPartsDictionary.allValues) { nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; } + [self moveWaitingTasksToInProgress]; [self completeIfDone]; } -- (void)moveTasksToInProgress { - // move parts from waiting to in progress if under the concurrency limit - while (self.isUnderConcurrencyLimit && self.waitingPartsDictionary.count > 0) { - //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; - - //Add to inProgress list - [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; - - //Remove it from the waitingList - [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; - AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); - [nextSubTask.sessionTask resume]; - nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; - } -} - -- (void)completeIfDone { - // Complete multipart upload if in progress and waiting tasks are done - if (!self.isDone) { - return; - } - - //If there are no more inProgress parts, then we are done. - - //Validate that all the content has been uploaded. - int64_t totalBytesSent = 0; - for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.completedPartsSet) { - totalBytesSent += aSubTask.totalBytesExpectedToSend; - } - - if (totalBytesSent != self.contentLength.longLongValue ) { - NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", - self.contentLength, @(totalBytesSent)]; - AWSDDLogDebug(@"%@", errorMessage); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage - forKey:@"Message"]; - - self.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorClientError - userInfo:userInfo]; - - //Execute call back if provided. - [self.transferUtility completeTask:self]; - - //Abort the request, so the server can clean up any partials. - [self.transferUtility callAbortMultiPartForUploadTask:self]; - - //clean up. - [self.transferUtility cleanupForMultiPartUploadTask:self]; - return; - } - - AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); - AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); - AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); - [self.transferUtility completeMultiPartForUploadTask:self]; -} - - (void)suspend { if (self.status != AWSS3TransferUtilityTransferStatusInProgress) { //Pause called on a transfer that is not in progresss. No op. @@ -298,20 +238,14 @@ - (void)suspend { // databaseQueue:self.databaseQueue]; // } // -// self.status = AWSS3TransferUtilityTransferStatusPaused; -// //Update the Master Record -// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID -// partNumber:@0 -// taskIdentifier:0 -// eTag:@"" -// status:self.status -// retry_count:self.retryCount -// databaseQueue:self.databaseQueue]; // Cancel session task for all subtasks which are in progress and set status to paused for (NSNumber *taskIdentifier in self.inProgressPartsDictionary.allKeys) { AWSS3TransferUtilityUploadSubTask *inProgressSubTask = self.inProgressPartsDictionary[taskIdentifier]; - NSCAssert(inProgressSubTask != nil, @"Task is required"); + // Note: This can happen due to lack of thread-safety + if (!inProgressSubTask) { + continue; + } // cancel the URLSessionTask [inProgressSubTask.sessionTask cancel]; @@ -343,6 +277,98 @@ - (void)suspend { [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:self subTask:subTask databaseQueue:self.databaseQueue]; } } + + self.status = AWSS3TransferUtilityTransferStatusPaused; + //Update the Master Record + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID + partNumber:@0 + taskIdentifier:0 + eTag:@"" + status:self.status + retry_count:self.retryCount + databaseQueue:self.databaseQueue]; +} + +- (void)moveWaitingTasksToInProgress { + // move parts from waiting to in progress if under the concurrency limit + while (self.isUnderConcurrencyLimit && self.waitingPartsDictionary.count > 0) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; + + //Add to inProgress list + [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + + //Remove it from the waitingList + [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); + [nextSubTask.sessionTask resume]; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; + } +} + +- (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask + usingHTTPResponse:(NSHTTPURLResponse *)HTTPResponse { + subTask.eTag = (NSString *)HTTPResponse.allHeaderFields[@"ETAG"]; + + //Add it to completed parts and remove it from remaining parts. + [self.completedPartsSet addObject:subTask]; + [self.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + //Update progress + self.progress.completedUnitCount = self.progress.completedUnitCount - subTask.totalBytesSent + subTask.totalBytesExpectedToSend; + + //Delete the temporary upload file for this subTask + [self.transferUtility removeFile:subTask.file]; + subTask.status = AWSS3TransferUtilityTransferStatusCompleted; + + //Update Database + [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID + partNumber:subTask.partNumber + taskIdentifier:subTask.taskIdentifier + eTag:subTask.eTag + status:subTask.status + retry_count:self.retryCount databaseQueue:self.databaseQueue]; +} + +- (void)completeIfDone { + // Complete multipart upload if in progress and waiting tasks are done + if (!self.isDone) { + return; + } + + //If there are no more inProgress parts, then we are done. + + //Validate that all the content has been uploaded. + int64_t totalBytesSent = 0; + for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.completedPartsSet) { + totalBytesSent += aSubTask.totalBytesExpectedToSend; + } + + if (totalBytesSent != self.contentLength.longLongValue ) { + NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", + self.contentLength, @(totalBytesSent)]; + AWSDDLogDebug(@"%@", errorMessage); + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:@"Message"]; + + self.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorClientError + userInfo:userInfo]; + + //Execute call back if provided. + [self.transferUtility completeTask:self]; + + //Abort the request, so the server can clean up any partials. + [self.transferUtility callAbortMultiPartForUploadTask:self]; + + //clean up. + [self.transferUtility cleanupForMultiPartUploadTask:self]; + return; + } + + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); + [self.transferUtility completeMultiPartForUploadTask:self]; } - (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 965b7d8a294..8952159c005 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -35,6 +35,7 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS - (AWSTask *)callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask; - (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)task; - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask; +- (void)removeFile:(NSString *)absolutePath; @end @@ -85,7 +86,9 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property (weak, nonatomic) AWSS3TransferUtility *transferUtility; - (void)integrateWithTransferUtility:(AWSS3TransferUtility *)transferUtility; -- (void)moveTasksToInProgress; +- (void)moveWaitingTasksToInProgress; +- (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask + usingHTTPResponse:(NSHTTPURLResponse *)HTTPResponse; - (void)completeIfDone; @end From c87594b7bbe3e60b670ddb3fdaf9f41b4a5af651 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 22:32:10 -0700 Subject: [PATCH 06/11] WIP --- AWSS3/AWSS3TransferUtility.m | 290 ++++++++------------------- AWSS3/AWSS3TransferUtilityTasks.m | 93 ++++++++- AWSS3/AWSS3TransferUtility_private.h | 14 ++ 3 files changed, 189 insertions(+), 208 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 632a691d1b2..bac22c230a6 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -583,8 +583,8 @@ - (void)completeTransferUtilityTask:(AWSS3TransferUtilityTask *)transferUtilityT }]; } -- (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary - tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary { +- (void)handleUnlinkedTransfers:(NSMutableDictionary *)tempMultiPartMasterTaskDictionary + tempTransferDictionary:(NSMutableDictionary *)tempTransferDictionary { //At this point, we have finished iterating through the tasks present in the NSURLSession and removed all the matching ones from the transferRequests dictionary. //If there are any left in the transferRequests list, it means that we think they are running, but NSURLSession doesn't know about them. for (id taskIdentifier in [tempTransferDictionary allKeys]) { @@ -596,18 +596,17 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; - + //Delete the transfer record from the DB [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue ]; AWSDDLogDebug(@"Deleted transfer request from the DB"); } //Check if the transfer is in a paused state and the input file for the transfer exists. - else if ( [[NSFileManager defaultManager] fileExistsAtPath:transferUtilityUploadTask.file] && + else if ([[NSFileManager defaultManager] fileExistsAtPath:transferUtilityUploadTask.file] && transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusPaused) { //If the transfer was paused and the local file is still present, create another NSURLSession task and leave it in a paused state - [ self createUploadTask:transferUtilityUploadTask startTransfer:NO]; - } - else { + [self createUploadTask:transferUtilityUploadTask startTransfer:NO]; + } else { //Transfer is in progress according to us, but not present in the NSURLSession. It may have been sucessfully completed. Do not retry. //Mark the status as unknown. The app developer should check to see if the S3 file was uploaded in the app logic and reinitate the transfer if required. transferUtilityUploadTask.status = AWSS3TransferUtilityTransferStatusUnknown; @@ -617,18 +616,16 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask AWSDDLogDebug(@"Deleted transfer request from the DB"); } } - else if([obj isKindOfClass:[AWSS3TransferUtilityUploadSubTask class]]) - { + else if([obj isKindOfClass:[AWSS3TransferUtilityUploadSubTask class]]) { AWSS3TransferUtilityUploadSubTask *subTask = obj; AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:subTask.uploadID]; [self retryUploadSubTask: multiPartUploadTask subTask:subTask startTransfer:NO]; subTask.status = AWSS3TransferUtilityTransferStatusWaiting; - [multiPartUploadTask.waitingPartsDictionary setObject:subTask forKey:@(subTask.taskIdentifier)]; + [multiPartUploadTask addUploadSubTask:subTask]; } else if ([obj isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { - AWSS3TransferUtilityDownloadTask *downloadTask = obj; - + if (downloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue]; @@ -636,7 +633,7 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask } else if (downloadTask.status == AWSS3TransferUtilityTransferStatusPaused) { //If the transfer was paused, create another NSURLSession task and leave it in an paused state - [ self createDownloadTask:downloadTask startTransfer:NO]; + [self createDownloadTask:downloadTask startTransfer:NO]; } else { //Transfer is in progress according to us, but not present in the NSURLSession. It may have been sucessfully completed. Do not retry. @@ -664,48 +661,20 @@ - (void) handleUnlinkedTransfers:(NSMutableDictionary *) tempMultiPartMasterTask if (multiPartUploadTask.status == AWSS3TransferUtilityTransferStatusPaused) { continue; } - - long numberOfPartsInProgress = 0; - while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { - if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { - //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; - - //Add to inProgress list - [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; - - //Remove it from the waitingList - [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; - AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); - [nextSubTask.sessionTask resume]; - - numberOfPartsInProgress++; - continue; - } - break; - } - // move suspended tasks from in progress to waiting to allow multipart upload process to run properly - NSMutableArray *inProgressAndSuspendedTasks = @[].mutableCopy; - - for (AWSS3TransferUtilityUploadSubTask *aSubTask in multiPartUploadTask.inProgressPartsDictionary.allValues) { - if (aSubTask.sessionTask.state == NSURLSessionTaskStateSuspended) { - AWSDDLogDebug(@"Subtask for multipart upload is suspended: %ld", aSubTask.taskIdentifier); - [inProgressAndSuspendedTasks addObject:aSubTask]; - } + while (multiPartUploadTask.isUnderConcurrencyLimit && multiPartUploadTask.hasWaitingTasks) { + //Get a part from the waitingList + AWSS3TransferUtilityUploadSubTask *nextSubTask = [multiPartUploadTask.waitingTasks objectAtIndex:0]; + [multiPartUploadTask moveWaitingTaskToInProgress:nextSubTask startTransfer:YES]; } - for (AWSS3TransferUtilityUploadSubTask *aSubTask in inProgressAndSuspendedTasks) { - [multiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(aSubTask.taskIdentifier)]; - [multiPartUploadTask.waitingPartsDictionary setObject:aSubTask forKey:@(aSubTask.taskIdentifier)]; - } + [multiPartUploadTask moveInProgressAndSuspendedTasks]; } } --(AWSS3TransferUtilityUploadTask *) hydrateUploadTask: (NSMutableDictionary *) task - sessionIdentifier: (NSString *) sessionIdentifier - databaseQueue: (AWSFMDatabaseQueue *) databaseQueue -{ +- (AWSS3TransferUtilityUploadTask *)hydrateUploadTask:(NSMutableDictionary *)task + sessionIdentifier:(NSString *)sessionIdentifier + databaseQueue:(AWSFMDatabaseQueue *)databaseQueue { AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [AWSS3TransferUtilityUploadTask new]; transferUtilityUploadTask.nsURLSessionID = sessionIdentifier; transferUtilityUploadTask.databaseQueue = databaseQueue; @@ -725,11 +694,9 @@ -(AWSS3TransferUtilityUploadTask *) hydrateUploadTask: (NSMutableDictionary *) t return transferUtilityUploadTask; } - -- (AWSS3TransferUtilityDownloadTask *) hydrateDownloadTask: (NSMutableDictionary *) task - sessionIdentifier: (NSString *) sessionIdentifier - databaseQueue: (AWSFMDatabaseQueue *) databaseQueue -{ +- (AWSS3TransferUtilityDownloadTask *)hydrateDownloadTask:(NSMutableDictionary *) task + sessionIdentifier:(NSString *) sessionIdentifier + databaseQueue:(AWSFMDatabaseQueue *) databaseQueue { AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [AWSS3TransferUtilityDownloadTask new]; transferUtilityDownloadTask.nsURLSessionID = sessionIdentifier; transferUtilityDownloadTask.databaseQueue = databaseQueue; @@ -1018,7 +985,7 @@ - (NSURLSessionUploadTask *)getURLSessionUploadTaskWithRequest:(NSURLRequest *) - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTask { //Remove from taskDictionary - [self.taskDictionary removeObjectForKey:@(transferUtilityUploadTask.taskIdentifier)]; + [self unregisterTaskIdentifier:transferUtilityUploadTask.taskIdentifier]; //Remove from Database [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID taskIdentifier:transferUtilityUploadTask.taskIdentifier databaseQueue:_databaseQueue ]; @@ -1240,23 +1207,14 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa subTask.responseData = @""; subTask.file = @""; subTask.eTag = @""; + subTask.status = AWSS3TransferUtilityTransferStatusWaiting; NSError *subTaskCreationError; //Move to inProgress or Waiting based on concurrency limit - BOOL isUnderConcurrencyLimit = i <= [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO - internalDictionaryToAddSubTaskTo: isUnderConcurrencyLimit ? - transferUtilityMultiPartUploadTask.inProgressPartsDictionary : - transferUtilityMultiPartUploadTask.waitingPartsDictionary]; - if (!subTaskCreationError) { - subTask.status = isUnderConcurrencyLimit ? AWSS3TransferUtilityTransferStatusInProgress : - AWSS3TransferUtilityTransferStatusWaiting; - AWSDDLogDebug(@"Added task for part [%@] to %@ list", subTask.partNumber, isUnderConcurrencyLimit ? - @"inProgress" : @"Waiting"); - } - + subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:NO]; if (!subTaskCreationError) { + AWSDDLogDebug(@"Added task for part [%@] to waiting list", subTask.partNumber); //Save in Database after the file has been created, so that file can be referenced incase upload is paused and needs to be restarted. [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:transferUtilityMultiPartUploadTask subTask:subTask @@ -1274,11 +1232,20 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa } //Start the subTasks - for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { - AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; - AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); - [subTask.sessionTask resume]; - } + [transferUtilityMultiPartUploadTask moveWaitingTasksToInProgress:YES]; +// for (AWSS3TransferUtilityUploadSubTask *subTask in transferUtilityMultiPartUploadTask.inProgressTasks) { +// AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); +// if (subTask.sessionTask.state == NSURLSessionTaskStateSuspended) { +// [subTask.sessionTask resume]; +// } +// } + +// for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { +// AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; +// AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); +// [subTask.sessionTask resume]; +// } + return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; }]; return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; @@ -1418,16 +1385,14 @@ - (nullable NSURL *)createPartialFile:(NSURL *)fileURL return partialFileURL; } -- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask - subTask:(AWSS3TransferUtilityUploadSubTask *)subTask -internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo { - return [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES internalDictionaryToAddSubTaskTo:internalDictionaryToAddSubTaskTo]; +- (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask { + return [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:YES]; } - (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask - subTask:(AWSS3TransferUtilityUploadSubTask *)subTask - startTransfer:(BOOL)startTransfer - internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo { + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask + startTransfer:(BOOL)startTransfer { __block NSError *error = nil; //Create a temporary part file if required. if (!(subTask.file || [subTask.file isEqualToString:@""]) || ![[NSFileManager defaultManager] fileExistsAtPath:subTask.file]) { @@ -1493,7 +1458,7 @@ - (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)tran [self registerMultipartUploadTask:transferUtilityMultiPartUploadTask taskIdentifier:subTask.taskIdentifier]; //Add to required internal dictionary - [internalDictionaryToAddSubTaskTo setObject:subTask forKey:@(subTask.taskIdentifier)]; + [transferUtilityMultiPartUploadTask addUploadSubTask:subTask]; //Update Database [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID @@ -1545,38 +1510,40 @@ - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask }]; } --(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transferUtilityMultiPartUploadTask - subTask: (AWSS3TransferUtilityUploadSubTask *) subTask - startTransfer: (BOOL) startTransfer { - +- (void)retryUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask + startTransfer:(BOOL)startTransfer { //Track if the task to be retried is in the waiting or inprogress list - BOOL inWaitingPartsDictionary = NO; - - [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; - if ([transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(subTask.taskIdentifier)] ) { - [transferUtilityMultiPartUploadTask.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + BOOL inWaitingList = NO; + + [self unregisterTaskIdentifier:subTask.taskIdentifier]; + + if ([transferUtilityMultiPartUploadTask inProgressTaskForTaskIdentifier:subTask.taskIdentifier] != nil) { + // remove + [transferUtilityMultiPartUploadTask removeWaitingUploadSubTask:subTask.taskIdentifier]; transferUtilityMultiPartUploadTask.retryCount = transferUtilityMultiPartUploadTask.retryCount + 1; + } else if ([transferUtilityMultiPartUploadTask waitingTaskForTaskIdentifier:subTask.taskIdentifier] != nil) { + // remove + [transferUtilityMultiPartUploadTask removeInProgressUploadSubTask:subTask.taskIdentifier]; + inWaitingList = YES; } - else if ([transferUtilityMultiPartUploadTask.waitingPartsDictionary objectForKey:@(subTask.taskIdentifier)] ) { - [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; - inWaitingPartsDictionary = YES; - } - + //Check if the part file exists if (![[NSFileManager defaultManager] fileExistsAtPath:subTask.file]) { //Set it to nil. This will force the creatUploadSubTask to create the part from the main file subTask.file = nil; } - + NSError *subTaskCreationError; - - if (inWaitingPartsDictionary) { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.waitingPartsDictionary]; - } - else { - subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer internalDictionaryToAddSubTaskTo:transferUtilityMultiPartUploadTask.inProgressPartsDictionary]; + + if (inWaitingList) { + subTask.status = AWSS3TransferUtilityTransferStatusWaiting; + subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer]; + } else { + subTask.status = AWSS3TransferUtilityTransferStatusInProgress; + subTaskCreationError = [self createUploadSubTask:transferUtilityMultiPartUploadTask subTask:subTask startTransfer:startTransfer]; } - + if (subTaskCreationError) { //cancel the multipart transfer [transferUtilityMultiPartUploadTask cancel]; @@ -1734,9 +1701,8 @@ -(void) retryUploadSubTask: (AWSS3TransferUtilityMultiPartUploadTask *) transfer } - (void) retryDownload: (AWSS3TransferUtilityDownloadTask *) transferUtilityDownloadTask { - //Remove from taskDictionary - [self.taskDictionary removeObjectForKey:@(transferUtilityDownloadTask.sessionTask.taskIdentifier)]; + [self unregisterTaskIdentifier:transferUtilityDownloadTask.sessionTask.taskIdentifier]; AWSDDLogDebug(@"Removed object from key %@", @(transferUtilityDownloadTask.sessionTask.taskIdentifier) ); transferUtilityDownloadTask.retryCount = transferUtilityDownloadTask.retryCount + 1; @@ -1832,6 +1798,10 @@ - (void)registerMultipartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)m [self.taskDictionary setObject:multiPartUploadTask forKey:@(taskIdentifier)]; } +- (void)unregisterTaskIdentifier:(NSUInteger)taskIdentifier { + [self.taskDictionary removeObjectForKey:@(taskIdentifier)]; +} + - (AWSS3TransferUtilityTask *)findTransferUtilityTask:(NSURLSessionTask *)task { id obj = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; if (!obj) { @@ -1922,50 +1892,6 @@ - (NSMutableArray *) getTasksHelper:(AWSSynchronizedMutableDictionary *)dictiona #pragma mark - Suspend and Resume -//- (nullable NSError *)suspendMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multipartUploadTask { -// // uses NSNumber * for taskIdentifier to set values in inProgressPartsDictionary -// // these tasks need to be canceled and cleared out and replaced with tasks which have -// // not been started yet so that completed uploads of parts do not cause pending part -// // uploads to be started. -// -// for (NSNumber *taskIdentifier in multipartUploadTask.inProgressPartsDictionary.allKeys) { -// AWSS3TransferUtilityUploadSubTask *inProgressSubTask = multipartUploadTask.inProgressPartsDictionary[taskIdentifier]; -// NSCAssert(inProgressSubTask != nil, @"Task is required"); -// -// // cancel the URLSessionTask -// [inProgressSubTask.sessionTask cancel]; -// -// AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; -// subTask.transferID = multipartUploadTask.transferID; -// subTask.partNumber = inProgressSubTask.partNumber; -// subTask.transferType = @"MULTI_PART_UPLOAD_SUB_TASK"; -// subTask.totalBytesExpectedToSend = inProgressSubTask.totalBytesExpectedToSend; -// subTask.totalBytesSent = (long long) 0; -// subTask.responseData = @""; -// subTask.file = @""; -// subTask.eTag = @""; -// subTask.status = AWSS3TransferUtilityTransferStatusPaused; -// -// NSError *error = [self createUploadSubTask:multipartUploadTask -// subTask:subTask -// startTransfer:NO -// internalDictionaryToAddSubTaskTo:multipartUploadTask.waitingPartsDictionary]; -// -// if (error) { -// AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); -// return error; -// } else { -// multipartUploadTask.inProgressPartsDictionary[taskIdentifier] = nil; -// -// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; -// -// [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:multipartUploadTask subTask:subTask databaseQueue:self.databaseQueue]; -// } -// } -// -// return nil; -//} - /// Suspends all active tasks. Downloads will be - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadSuspendBlock)completionHandler { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -1997,46 +1923,6 @@ - (void)suspendAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferU }); } -//- (nullable NSError *)resumeMultipartUpload:(AWSS3TransferUtilityMultiPartUploadTask *)multiPartUploadTask { -// // move parts from waiting to in progress if under the concurrency limit -// if (multiPartUploadTask.inProgressPartsDictionary.count < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { -// long numberOfPartsInProgress = [multiPartUploadTask.inProgressPartsDictionary count]; -// while (numberOfPartsInProgress < [self.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]) { -// if ([multiPartUploadTask.waitingPartsDictionary count] > 0) { -// //Get a part from the waitingList -// AWSS3TransferUtilityUploadSubTask *nextSubTask = [[multiPartUploadTask.waitingPartsDictionary allValues] objectAtIndex:0]; -// -// //Add to inProgress list -// [multiPartUploadTask.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; -// -// //Remove it from the waitingList -// [multiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; -// AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), multiPartUploadTask.uploadID); -// [nextSubTask.sessionTask resume]; -// nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; -// numberOfPartsInProgress++; -// continue; -// } -// break; -// } -// } -// -// // Change status from paused to waiting -// for (AWSS3TransferUtilityUploadSubTask * nextSubTask in multiPartUploadTask.waitingPartsDictionary.allValues) { -// nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; -// } -// -// // Complete multipart upload if in progress and waiting tasks are done -// if (multiPartUploadTask.isDone) { -// AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)multiPartUploadTask.waitingPartsDictionary.count); -// AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)multiPartUploadTask.inProgressPartsDictionary.count); -// AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)multiPartUploadTask.completedPartsSet.count); -// [self completeMultiPartForUploadTask:multiPartUploadTask]; -// } -// -// return nil; -//} - - (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUtilityMultiPartUploadResumeBlock)completionHandler { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[self getMultiPartUploadTasks] continueWithBlock:^id (AWSTask *> * _Nonnull task) { @@ -2278,16 +2164,12 @@ - (void)URLSession:(NSURLSession *)session return; } - AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.waitingPartsDictionary objectForKey:@(task.taskIdentifier)]; + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask waitingTaskForTaskIdentifier:task.taskIdentifier]; if (subTask) { - //Add it to inProgress list - [transferUtilityMultiPartUploadTask.inProgressPartsDictionary setObject:subTask forKey:@(subTask.taskIdentifier)]; - - //Remove it from the waitingList - [transferUtilityMultiPartUploadTask.waitingPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [transferUtilityMultiPartUploadTask moveWaitingTaskToInProgress:subTask]; } - subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(task.taskIdentifier)]; + subTask = [transferUtilityMultiPartUploadTask inProgressTaskForTaskIdentifier:task.taskIdentifier]; if (!subTask) { AWSDDLogDebug(@"Unable to find information for task %lu in inProgress Dictionary", (unsigned long)task.taskIdentifier); return; @@ -2320,7 +2202,7 @@ - (void)URLSession:(NSURLSession *)session withMultipartUploadTask:transferUtilityMultiPartUploadTask]; } else { [transferUtilityMultiPartUploadTask completeUploadSubTask:subTask usingHTTPResponse:HTTPResponse]; - [transferUtilityMultiPartUploadTask moveWaitingTasksToInProgress]; + [transferUtilityMultiPartUploadTask moveWaitingTasksToInProgress:YES]; [transferUtilityMultiPartUploadTask completeIfDone]; } } @@ -2342,7 +2224,7 @@ - (void)URLSession:(NSURLSession *)session //Check if the task was cancelled. if (downloadTask.cancelled) { [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; - [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; + [self unregisterTaskIdentifier:downloadTask.sessionTask.taskIdentifier]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; return; } @@ -2386,7 +2268,7 @@ - (void)URLSession:(NSURLSession *)session } } [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; - [self.taskDictionary removeObjectForKey:@(downloadTask.sessionTask.taskIdentifier)]; + [self unregisterTaskIdentifier:downloadTask.sessionTask.taskIdentifier]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; [self completeTask:downloadTask]; } @@ -2422,13 +2304,11 @@ - (void)processMultipartUploadTaskError:(NSError *)error [self completeTask:multipartUploadTask]; //Make sure all other parts that are in progress are canceled. - for (NSNumber *key in [multipartUploadTask.inProgressPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [multipartUploadTask.inProgressPartsDictionary objectForKey:key]; + for (AWSS3TransferUtilityUploadSubTask *subTask in multipartUploadTask.inProgressTasks) { [subTask.sessionTask cancel]; } - for (NSNumber *key in [multipartUploadTask.waitingPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [multipartUploadTask.waitingPartsDictionary objectForKey:key]; + for (AWSS3TransferUtilityUploadSubTask *subTask in multipartUploadTask.waitingTasks) { [subTask.sessionTask cancel]; } @@ -2513,7 +2393,7 @@ - (void)completeTask:(AWSS3TransferUtilityTask *)task removeCompletedTask:(BOOL) if (removeCompletedTask) { // complete task before removing from dictionaries [self.completedTaskDictionary removeObjectForKey:task.transferID]; - [self.taskDictionary removeObjectForKey:@(task.taskIdentifier)]; + [self unregisterTaskIdentifier:task.taskIdentifier]; } } @@ -2525,7 +2405,7 @@ - (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask * //Remove all entries from taskDictionary. for ( AWSS3TransferUtilityUploadSubTask *subTask in [task.inProgressPartsDictionary allValues] ) { - [self.taskDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + [self unregisterTaskIdentifier:subTask.taskIdentifier]; [self removeFile:subTask.file]; } @@ -2543,7 +2423,7 @@ - (void) cleanupForUploadTask: (AWSS3TransferUtilityUploadTask *) uploadTask { [self.completedTaskDictionary setObject:uploadTask forKey:uploadTask.transferID]; //Remove entry from taskDictionary - [self.taskDictionary removeObjectForKey:@(uploadTask.taskIdentifier)]; + [self unregisterTaskIdentifier:uploadTask.taskIdentifier]; //Remove temporary file if required. if (uploadTask.temporaryFileCreated) { diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 4eb0c491086..92a74d10840 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -137,10 +137,26 @@ - (BOOL)isUnderConcurrencyLimit { return self.inProgressPartsDictionary.count < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; } +- (BOOL)hasWaitingTasks { + return self.waitingTasks.count > 0; +} + - (BOOL)isDone { return _waitingPartsDictionary.count == 0 && _inProgressPartsDictionary.count == 0; } +- (NSArray *)waitingTasks { + return self.waitingPartsDictionary.allValues; +} + +- (NSArray *)inProgressTasks { + return self.inProgressPartsDictionary.allValues; +} + +- (NSArray *)completedTask { + return self.completedPartsSet.allObjects; +} + - (AWSS3TransferUtilityMultiPartUploadExpression *)expression { if (!_expression) { _expression = [AWSS3TransferUtilityMultiPartUploadExpression new]; @@ -199,7 +215,7 @@ - (void)resume { // databaseQueue:self.databaseQueue]; // Change status from paused to waiting - for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingPartsDictionary.allValues) { + for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingTasks) { nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; } @@ -289,9 +305,77 @@ - (void)suspend { databaseQueue:self.databaseQueue]; } +- (void)addUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask { + if (subTask.status == AWSS3TransferUtilityTransferStatusUnknown || + subTask.status == AWSS3TransferUtilityTransferStatusPaused || + subTask.status == AWSS3TransferUtilityTransferStatusWaiting) { + self.waitingPartsDictionary[@(subTask.taskIdentifier)] = subTask; + } else if (subTask.status == AWSS3TransferUtilityTransferStatusInProgress) { + self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = subTask; + } else { + AWSDDLogDebug(@"Sub Task status not supported: %lu", subTask.status); + NSCAssert(NO, @"Status not supported"); + } +} + +- (void)removeWaitingUploadSubTask:(NSUInteger)taskIdentifier { + self.waitingPartsDictionary[@(taskIdentifier)] = nil; +} + +- (void)removeInProgressUploadSubTask:(NSUInteger)taskIdentifier { + self.inProgressPartsDictionary[@(taskIdentifier)] = nil; +} + +- (AWSS3TransferUtilityUploadSubTask *)waitingTaskForTaskIdentifier:(NSUInteger)taskIdentifier { + return self.waitingPartsDictionary[@(taskIdentifier)]; +} + +- (AWSS3TransferUtilityUploadSubTask *)inProgressTaskForTaskIdentifier:(NSUInteger)taskIdentifier { + return self.inProgressPartsDictionary[@(taskIdentifier)]; +} + +- (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask { + [self moveWaitingTaskToInProgress:subTask startTransfer:NO]; +} + +- (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask startTransfer:(BOOL)startTransfer { + if ([self.waitingTasks containsObject:subTask]) { + //Add to inProgress list + self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = subTask; + //Remove it from the waitingList + self.waitingPartsDictionary[@(subTask.taskIdentifier)] = nil; + AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(subTask.taskIdentifier), self.uploadID); + + if (startTransfer) { + [subTask.sessionTask resume]; + } + } +} + +- (void)moveInProgressAndSuspendedTasks { + // move suspended tasks from in progress to waiting to allow multipart upload process to run properly + NSMutableArray *inProgressAndSuspendedTasks = @[].mutableCopy; + + for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.inProgressTasks) { + if (aSubTask.sessionTask.state == NSURLSessionTaskStateSuspended) { + AWSDDLogDebug(@"Subtask for multipart upload is suspended: %ld", aSubTask.taskIdentifier); + [inProgressAndSuspendedTasks addObject:aSubTask]; + } + } + + for (AWSS3TransferUtilityUploadSubTask *aSubTask in inProgressAndSuspendedTasks) { + [self.inProgressPartsDictionary removeObjectForKey:@(aSubTask.taskIdentifier)]; + [self.waitingPartsDictionary setObject:aSubTask forKey:@(aSubTask.taskIdentifier)]; + } +} + - (void)moveWaitingTasksToInProgress { + [self moveWaitingTasksToInProgress:NO]; +} + +- (void)moveWaitingTasksToInProgress:(BOOL)startTransfer { // move parts from waiting to in progress if under the concurrency limit - while (self.isUnderConcurrencyLimit && self.waitingPartsDictionary.count > 0) { + while (self.isUnderConcurrencyLimit && self.hasWaitingTasks) { //Get a part from the waitingList AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; @@ -301,7 +385,10 @@ - (void)moveWaitingTasksToInProgress { //Remove it from the waitingList [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); - [nextSubTask.sessionTask resume]; + if (startTransfer) { + AWSDDLogDebug(@"Starting subTask %@", @(nextSubTask.taskIdentifier)); + [nextSubTask.sessionTask resume]; + } nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; } } diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 8952159c005..b56a16ec04c 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -76,6 +76,7 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property BOOL cancelled; @property BOOL temporaryFileCreated; @property (readonly) BOOL isUnderConcurrencyLimit; +@property (readonly) BOOL hasWaitingTasks; @property (readonly) BOOL isDone; @property (strong, nonatomic) NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; @@ -83,10 +84,23 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property int partNumber; @property NSNumber *contentLength; +@property (readonly) NSArray * waitingTasks; +@property (readonly) NSArray * inProgressTasks; +@property (readonly) NSArray * completedTask; + @property (weak, nonatomic) AWSS3TransferUtility *transferUtility; - (void)integrateWithTransferUtility:(AWSS3TransferUtility *)transferUtility; +- (void)addUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask; +- (void)removeWaitingUploadSubTask:(NSUInteger)taskIdentifier; +- (void)removeInProgressUploadSubTask:(NSUInteger)taskIdentifier; +- (AWSS3TransferUtilityUploadSubTask *)waitingTaskForTaskIdentifier:(NSUInteger)taskIdentifier; +- (AWSS3TransferUtilityUploadSubTask *)inProgressTaskForTaskIdentifier:(NSUInteger)taskIdentifier; +- (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask; +- (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask startTransfer:(BOOL)startTransfer; +- (void)moveInProgressAndSuspendedTasks; - (void)moveWaitingTasksToInProgress; +- (void)moveWaitingTasksToInProgress:(BOOL)startTransfer; - (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask usingHTTPResponse:(NSHTTPURLResponse *)HTTPResponse; - (void)completeIfDone; From efa5ef346045bcc9dc832c79f92dcbaf748e911d Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 22:42:46 -0700 Subject: [PATCH 07/11] WIP --- AWSS3/AWSS3TransferUtility.m | 47 ++++++++++------------------ AWSS3/AWSS3TransferUtilityTasks.m | 5 ++- AWSS3/AWSS3TransferUtility_private.h | 2 +- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index bac22c230a6..bfd4b1e7c7d 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -422,8 +422,8 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary continue; } //Check if the subTask is is already completed. If it is, add it to the completed parts list, update the progress object and go to the next iteration of the loop - if (subTask.status== AWSS3TransferUtilityTransferStatusCompleted ) { - [multiPartUploadTask.completedPartsSet addObject:subTask]; + if (subTask.status == AWSS3TransferUtilityTransferStatusCompleted) { + [multiPartUploadTask addUploadSubTask:subTask]; continue; } @@ -1233,18 +1233,6 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa //Start the subTasks [transferUtilityMultiPartUploadTask moveWaitingTasksToInProgress:YES]; -// for (AWSS3TransferUtilityUploadSubTask *subTask in transferUtilityMultiPartUploadTask.inProgressTasks) { -// AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); -// if (subTask.sessionTask.state == NSURLSessionTaskStateSuspended) { -// [subTask.sessionTask resume]; -// } -// } - -// for (id taskIdentifier in transferUtilityMultiPartUploadTask.inProgressPartsDictionary) { -// AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:taskIdentifier]; -// AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); -// [subTask.sessionTask resume]; -// } return [AWSTask taskWithResult:transferUtilityMultiPartUploadTask]; }]; @@ -1956,11 +1944,11 @@ - (void)resumeAllMultipartUploadsWithCompletionHandler:(nullable AWSS3TransferUt #pragma mark - Internal helper methods - (AWSTask *)callFinishMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask { - NSMutableArray *completedParts = [NSMutableArray arrayWithCapacity:[uploadTask.completedPartsSet count]]; + NSMutableArray *completedParts = [NSMutableArray arrayWithCapacity:uploadTask.completedTasks.count]; NSMutableDictionary *tempDictionary = [NSMutableDictionary new]; //Create a new Dictionary with the partNumber as the Key - for(AWSS3TransferUtilityUploadSubTask *subTask in uploadTask.completedPartsSet) { + for(AWSS3TransferUtilityUploadSubTask *subTask in uploadTask.completedTasks) { [tempDictionary setObject:subTask forKey:subTask.partNumber]; } @@ -2345,33 +2333,31 @@ - (void)URLSession:(NSURLSession *)session transferUtilityUploadTask.expression.progressBlock(transferUtilityUploadTask, transferUtilityUploadTask.progress); } } - } - else if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { + } else if ([transferUtilityTask isKindOfClass:[AWSS3TransferUtilityMultiPartUploadTask class]]) { //Get the multipart upload task AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [self.taskDictionary objectForKey:@(task.taskIdentifier)]; //Get multipart upload sub task - AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask.inProgressPartsDictionary objectForKey:@(task.taskIdentifier)]; + AWSS3TransferUtilityUploadSubTask *subTask = [transferUtilityMultiPartUploadTask inProgressTaskForTaskIdentifier:task.taskIdentifier]; subTask.totalBytesSent = totalBytesSent; - - + //Calculate the total sent so far int64_t totalSentSoFar = 0; //Create a new Dictionary with the partNumber as the Key NSMutableDictionary *completedPartsByPartNumber = [NSMutableDictionary new]; - for(AWSS3TransferUtilityUploadSubTask *subTask in transferUtilityMultiPartUploadTask.completedPartsSet) { + for (AWSS3TransferUtilityUploadSubTask *subTask in transferUtilityMultiPartUploadTask.completedTasks) { [completedPartsByPartNumber setObject:subTask forKey:subTask.partNumber]; } - for (AWSS3TransferUtilityUploadSubTask *aSubTask in [completedPartsByPartNumber allValues]) { + for (AWSS3TransferUtilityUploadSubTask *aSubTask in completedPartsByPartNumber.allValues) { totalSentSoFar += aSubTask.totalBytesExpectedToSend; } - for (AWSS3TransferUtilityUploadSubTask *aSubTask in [transferUtilityMultiPartUploadTask.inProgressPartsDictionary allValues]) { + for (AWSS3TransferUtilityUploadSubTask *aSubTask in transferUtilityMultiPartUploadTask.inProgressTasks) { totalSentSoFar += aSubTask.totalBytesSent; } - + if (transferUtilityMultiPartUploadTask.progress.completedUnitCount != totalSentSoFar ) { transferUtilityMultiPartUploadTask.progress.totalUnitCount = [transferUtilityMultiPartUploadTask.contentLength longLongValue]; transferUtilityMultiPartUploadTask.progress.completedUnitCount = totalSentSoFar; - + //execute the callback to the progressblock if present. if (transferUtilityMultiPartUploadTask.expression.progressBlock) { AWSDDLogDebug(@"Total %lld, ProgressSoFar %lld", transferUtilityMultiPartUploadTask.progress.totalUnitCount, transferUtilityMultiPartUploadTask.progress.completedUnitCount); @@ -2399,21 +2385,20 @@ - (void)completeTask:(AWSS3TransferUtilityTask *)task removeCompletedTask:(BOOL) } - (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)task { - //Add it to list of completed Tasks [self.completedTaskDictionary setObject:task forKey:task.transferID]; - + //Remove all entries from taskDictionary. - for ( AWSS3TransferUtilityUploadSubTask *subTask in [task.inProgressPartsDictionary allValues] ) { + for (AWSS3TransferUtilityUploadSubTask *subTask in task.inProgressTasks) { [self unregisterTaskIdentifier:subTask.taskIdentifier]; [self removeFile:subTask.file]; } - + //Remove temporary file if required. if (task.temporaryFileCreated) { [self removeFile:task.file]; } - + //Remove data from the Database. [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:task.transferID databaseQueue:_databaseQueue]; } diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 92a74d10840..685fd405f30 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -153,7 +153,7 @@ - (BOOL)isDone { return self.inProgressPartsDictionary.allValues; } -- (NSArray *)completedTask { +- (NSArray *)completedTasks { return self.completedPartsSet.allObjects; } @@ -312,6 +312,9 @@ - (void)addUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask { self.waitingPartsDictionary[@(subTask.taskIdentifier)] = subTask; } else if (subTask.status == AWSS3TransferUtilityTransferStatusInProgress) { self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = subTask; + + } else if (subTask.status == AWSS3TransferUtilityTransferStatusCompleted) { + [self.completedPartsSet addObject:subTask]; } else { AWSDDLogDebug(@"Sub Task status not supported: %lu", subTask.status); NSCAssert(NO, @"Status not supported"); diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index b56a16ec04c..2f73280cb79 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -86,7 +86,7 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property (readonly) NSArray * waitingTasks; @property (readonly) NSArray * inProgressTasks; -@property (readonly) NSArray * completedTask; +@property (readonly) NSArray * completedTasks; @property (weak, nonatomic) AWSS3TransferUtility *transferUtility; From fbb1de784929bf4ded671635daf1c9180fa0453b Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 22:44:04 -0700 Subject: [PATCH 08/11] WIP --- AWSS3/AWSS3TransferUtility.m | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index bfd4b1e7c7d..b5946a4e6b0 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -290,14 +290,14 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur delegateQueue:nil]; //If not able to create the session, call completion handler with error and return nil. - if (!_session ) { + if (!_session) { NSString* message = [NSString stringWithFormat:@"Failed to create a NSURLSession for [%@]", _sessionIdentifier]; NSDictionary *userInfo = [NSDictionary dictionaryWithObject:message forKey:@"Message"]; NSError *error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain code:AWSS3TransferUtilityErrorClientError userInfo:userInfo]; - if (completionHandler ) { + if (completionHandler) { completionHandler(error); } return nil; @@ -307,7 +307,7 @@ - (instancetype)initWithConfiguration:(AWSServiceConfiguration *)serviceConfigur _configuration = [serviceConfiguration copy]; [_configuration addUserAgentProductToken:AWSS3TransferUtilityUserAgent]; - if (transferUtilityConfiguration ) { + if (transferUtilityConfiguration) { _transferUtilityConfiguration = [transferUtilityConfiguration copy]; } else { @@ -363,7 +363,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary NSMutableArray *tasks = [AWSS3TransferUtilityDatabaseHelper getTransferTaskDataFromDB:_sessionIdentifier databaseQueue:_databaseQueue]; //Iterate through the tasks and populate transferRequests and Multipart dictionary. - for( NSMutableDictionary *task in tasks ) { + for( NSMutableDictionary *task in tasks) { NSString *transferType = [task objectForKey:@"transfer_type"]; int sessionTaskID = [[task objectForKey:@"session_task_id"] intValue]; @@ -371,7 +371,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = [self hydrateUploadTask:task sessionIdentifier:self.sessionIdentifier databaseQueue:self.databaseQueue]; //If task is completed, no more processing is required. - if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted) { [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityUploadTask.transferID databaseQueue:self.databaseQueue]; continue; @@ -384,7 +384,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary AWSS3TransferUtilityDownloadTask *transferUtilityDownloadTask = [self hydrateDownloadTask:task sessionIdentifier:self.sessionIdentifier databaseQueue:self.databaseQueue]; //If task is completed, no more processing is required. - if (transferUtilityDownloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + if (transferUtilityDownloadTask.status == AWSS3TransferUtilityTransferStatusCompleted) { [self.completedTaskDictionary setObject:transferUtilityDownloadTask forKey:transferUtilityDownloadTask.transferID]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:transferUtilityDownloadTask.transferID databaseQueue:self.databaseQueue]; continue; @@ -416,7 +416,7 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary //Get the Master MultiPart record from the Dictionary. AWSS3TransferUtilityMultiPartUploadTask *multiPartUploadTask = [tempMultiPartMasterTaskDictionary objectForKey:subTask.uploadID]; - if ( !multiPartUploadTask ) { + if ( !multiPartUploadTask) { //Couldn't find the multipart upload master record. Must be an orphan part record. Clean up the DB and continue. [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:subTask.transferID databaseQueue:self.databaseQueue]; continue; @@ -441,7 +441,7 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { //Loop through all the upload Tasks. - for( NSURLSessionUploadTask *task in uploadTasks ) { + for( NSURLSessionUploadTask *task in uploadTasks) { AWSDDLogDebug(@"Iterating through task Identifier [%lu]", (unsigned long)task.taskIdentifier); NSError *taskError = [task error]; @@ -495,7 +495,7 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster } //Loop through all the Download tasks - for( NSURLSessionDownloadTask *task in downloadTasks ) { + for( NSURLSessionDownloadTask *task in downloadTasks) { id obj = [tempTransferDictionary objectForKey:@(task.taskIdentifier)]; NSError *taskError = [task error]; @@ -594,7 +594,7 @@ - (void)handleUnlinkedTransfers:(NSMutableDictionary *)tempMultiPartMasterTaskDi { AWSS3TransferUtilityUploadTask *transferUtilityUploadTask = obj; - if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + if (transferUtilityUploadTask.status == AWSS3TransferUtilityTransferStatusCompleted) { [self.completedTaskDictionary setObject:transferUtilityUploadTask forKey:transferUtilityUploadTask.transferID]; //Delete the transfer record from the DB @@ -626,7 +626,7 @@ - (void)handleUnlinkedTransfers:(NSMutableDictionary *)tempMultiPartMasterTaskDi else if ([obj isKindOfClass:[AWSS3TransferUtilityDownloadTask class]]) { AWSS3TransferUtilityDownloadTask *downloadTask = obj; - if (downloadTask.status == AWSS3TransferUtilityTransferStatusCompleted ) { + if (downloadTask.status == AWSS3TransferUtilityTransferStatusCompleted) { [self.completedTaskDictionary setObject:downloadTask forKey:downloadTask.transferID]; [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID taskIdentifier:[taskIdentifier integerValue] databaseQueue:self.databaseQueue]; AWSDDLogDebug(@"Deleted transfer request from DB"); @@ -929,7 +929,7 @@ - (NSURLSessionUploadTask *)getURLSessionUploadTaskWithRequest:(NSURLRequest *) return [[self.preSignedURLBuilder getPreSignedURL:getPreSignedURLRequest] continueWithBlock:^id(AWSTask *task) { NSURL *presignedURL = task.result; NSError *error = task.error; - if ( error ) { + if (error) { AWSDDLogError(@"Error: %@", error); return [AWSTask taskWithError:error]; } @@ -1173,7 +1173,7 @@ - (void) retryUpload: (AWSS3TransferUtilityUploadTask *) transferUtilityUploadTa AWSS3CreateMultipartUploadOutput *output = task.result; //Check if the uploadId is null to safeguard from crash reported in https://github.com/aws/aws-sdk-ios/issues/1060 - if (output.uploadId == (id) [NSNull null] || output.uploadId.length == 0 ) { + if (output.uploadId == (id) [NSNull null] || output.uploadId.length == 0) { AWSDDLogError(@"MultiPartUploadID is null - Failing Transfer"); NSDictionary *userInfo = [NSDictionary dictionaryWithObject:@"MultiPartUploadID is null - Failing Transfer" forKey:@"Message"]; @@ -1485,7 +1485,7 @@ - (void)completeMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask transferUtilityMultiPartUploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; transferUtilityMultiPartUploadTask.progress.completedUnitCount = transferUtilityMultiPartUploadTask.progress.totalUnitCount; - if (transferUtilityMultiPartUploadTask.expression.progressBlock ) { + if (transferUtilityMultiPartUploadTask.expression.progressBlock) { transferUtilityMultiPartUploadTask.expression.progressBlock(transferUtilityMultiPartUploadTask, transferUtilityMultiPartUploadTask.progress); } } @@ -2125,7 +2125,7 @@ - (void)URLSession:(NSURLSession *)session } //Mark status as completed if there is no error. - if (! uploadTask.error ) { + if (!uploadTask.error) { uploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; //Set progress to 100% and call the progress block uploadTask.progress.completedUnitCount = uploadTask.progress.totalUnitCount; @@ -2222,7 +2222,7 @@ - (void)URLSession:(NSURLSession *)session downloadTask.error = error; } - if (!downloadTask.error ) { + if (!downloadTask.error) { downloadTask.status = AWSS3TransferUtilityTransferStatusCompleted; } else { @@ -2354,7 +2354,7 @@ - (void)URLSession:(NSURLSession *)session totalSentSoFar += aSubTask.totalBytesSent; } - if (transferUtilityMultiPartUploadTask.progress.completedUnitCount != totalSentSoFar ) { + if (transferUtilityMultiPartUploadTask.progress.completedUnitCount != totalSentSoFar) { transferUtilityMultiPartUploadTask.progress.totalUnitCount = [transferUtilityMultiPartUploadTask.contentLength longLongValue]; transferUtilityMultiPartUploadTask.progress.completedUnitCount = totalSentSoFar; From 374a93afb5e4aab6416ee9bd040ed04c05aba695 Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 22:56:53 -0700 Subject: [PATCH 09/11] WIP --- AWSS3/AWSS3TransferUtility.m | 34 +++++++++++++++---------------- AWSS3/AWSS3TransferUtilityTasks.m | 15 +++++++------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index b5946a4e6b0..0253f042004 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -433,23 +433,22 @@ - (void) hydrateFromDB:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary } } -- (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary - tempTransferDictionary: (NSMutableDictionary *) tempTransferDictionary - completionHandler: (void (^)(NSError *_Nullable error)) completionHandler{ +- (void)linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMasterTaskDictionary + tempTransferDictionary:(NSMutableDictionary *)tempTransferDictionary + completionHandler:(void (^)(NSError *_Nullable error)) completionHandler { //Get tasks from the NSURLSession and reattach to them. //getTasksWithCompletionHandler is an ansynchronous task, so the thread that is calling this method will not be blocked. [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { - + //Loop through all the upload Tasks. - for( NSURLSessionUploadTask *task in uploadTasks) { + for (NSURLSessionUploadTask *task in uploadTasks) { AWSDDLogDebug(@"Iterating through task Identifier [%lu]", (unsigned long)task.taskIdentifier); NSError *taskError = [task error]; - + //Get the Task id obj = [tempTransferDictionary objectForKey:@(task.taskIdentifier)]; - - if ([obj isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) - { + + if ([obj isKindOfClass:[AWSS3TransferUtilityUploadTask class]]) { //Found a upload task. AWSS3TransferUtilityUploadTask *uploadTask = obj; uploadTask.sessionTask = task; @@ -469,11 +468,11 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster filePath:uploadTask.file]; continue; } - + //Check if it is InProgress if (uploadTask.status == AWSS3TransferUtilityTransferStatusInProgress) { //Check if the the underlying NSURLSession task is completed. If so, delete the record from the DB, clean up any temp files and call the completion handler. - if ([task state] == NSURLSessionTaskStateCompleted) { + if (task.state == NSURLSessionTaskStateCompleted) { //Set progress to 100% uploadTask.progress.completedUnitCount = uploadTask.progress.totalUnitCount; uploadTask.status = AWSS3TransferUtilityTransferStatusCompleted; @@ -484,7 +483,7 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster continue; } //If it is in any other status than running, then we need to recover by retrying. - if ([task state] != NSURLSessionTaskStateRunning) { + if (task.state != NSURLSessionTaskStateRunning) { //We think the task in IN_PROGRESS. The underlying task is not running. //Recover the situation by retrying. [self retryUpload:uploadTask]; @@ -522,7 +521,7 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster //Check if this is in progress if (downloadTask.status == AWSS3TransferUtilityTransferStatusInProgress) { - if ([task state] == NSURLSessionTaskStateCompleted) { + if (task.state == NSURLSessionTaskStateCompleted) { //Set progress to 100% downloadTask.progress.completedUnitCount = downloadTask.progress.totalUnitCount; downloadTask.status = AWSS3TransferUtilityTransferStatusCompleted; @@ -530,7 +529,7 @@ - (void) linkTransfersToNSURLSession:(NSMutableDictionary *) tempMultiPartMaster continue; } //Check if the underlying task's status is not in Progress. - else if ([task state] != NSURLSessionTaskStateRunning) { + else if (task.state != NSURLSessionTaskStateRunning) { //We think the task in Progress. The underlying task is not in progress. //Recover the situation by retrying [self retryDownload:downloadTask]; @@ -716,10 +715,9 @@ - (AWSS3TransferUtilityDownloadTask *)hydrateDownloadTask:(NSMutableDictionary * } --( AWSS3TransferUtilityMultiPartUploadTask *) hydrateMultiPartUploadTask: (NSMutableDictionary *) task - sessionIdentifier: (NSString *) sessionIdentifier - databaseQueue: (AWSFMDatabaseQueue *) databaseQueue -{ +- (AWSS3TransferUtilityMultiPartUploadTask *)hydrateMultiPartUploadTask: (NSMutableDictionary *) task + sessionIdentifier:(NSString *) sessionIdentifier + databaseQueue: (AWSFMDatabaseQueue *) databaseQueue { AWSS3TransferUtilityMultiPartUploadTask *transferUtilityMultiPartUploadTask = [AWSS3TransferUtilityMultiPartUploadTask new]; [transferUtilityMultiPartUploadTask integrateWithTransferUtility:self]; transferUtilityMultiPartUploadTask.nsURLSessionID = sessionIdentifier; diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 685fd405f30..2d095bf83ab 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -256,14 +256,15 @@ - (void)suspend { // // Cancel session task for all subtasks which are in progress and set status to paused - for (NSNumber *taskIdentifier in self.inProgressPartsDictionary.allKeys) { - AWSS3TransferUtilityUploadSubTask *inProgressSubTask = self.inProgressPartsDictionary[taskIdentifier]; + for (AWSS3TransferUtilityUploadSubTask *inProgressSubTask in self.inProgressTasks) { // Note: This can happen due to lack of thread-safety if (!inProgressSubTask) { + NSCAssert(NO, @"Sub Task should not be nil!"); continue; } // cancel the URLSessionTask + inProgressSubTask.status = AWSS3TransferUtilityTransferStatusPaused; [inProgressSubTask.sessionTask cancel]; AWSS3TransferUtilityUploadSubTask *subTask = [AWSS3TransferUtilityUploadSubTask new]; @@ -286,7 +287,7 @@ - (void)suspend { AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); self.error = error; } else { - self.inProgressPartsDictionary[taskIdentifier] = nil; + self.inProgressPartsDictionary[@(inProgressSubTask.taskIdentifier)] = nil; [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:inProgressSubTask.transferID partNumber:inProgressSubTask.partNumber taskIdentifier:inProgressSubTask.taskIdentifier eTag:nil status:AWSS3TransferUtilityTransferStatusCancelled retry_count:0 databaseQueue:self.databaseQueue]; @@ -367,8 +368,8 @@ - (void)moveInProgressAndSuspendedTasks { } for (AWSS3TransferUtilityUploadSubTask *aSubTask in inProgressAndSuspendedTasks) { - [self.inProgressPartsDictionary removeObjectForKey:@(aSubTask.taskIdentifier)]; - [self.waitingPartsDictionary setObject:aSubTask forKey:@(aSubTask.taskIdentifier)]; + self.inProgressPartsDictionary[@(aSubTask.taskIdentifier)] = nil; + self.waitingPartsDictionary[@(aSubTask.taskIdentifier)] = aSubTask; } } @@ -383,7 +384,7 @@ - (void)moveWaitingTasksToInProgress:(BOOL)startTransfer { AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; //Add to inProgress list - [self.inProgressPartsDictionary setObject:nextSubTask forKey:@(nextSubTask.taskIdentifier)]; + self.inProgressPartsDictionary[@(nextSubTask.taskIdentifier)] = nextSubTask; //Remove it from the waitingList [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; @@ -402,7 +403,7 @@ - (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask //Add it to completed parts and remove it from remaining parts. [self.completedPartsSet addObject:subTask]; - [self.inProgressPartsDictionary removeObjectForKey:@(subTask.taskIdentifier)]; + self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = nil; //Update progress self.progress.completedUnitCount = self.progress.completedUnitCount - subTask.totalBytesSent + subTask.totalBytesExpectedToSend; From 9205058c203a12531d95b9daee12ba5202bbbd8c Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Fri, 3 Jun 2022 23:00:58 -0700 Subject: [PATCH 10/11] WIP --- AWSS3/AWSS3TransferUtility.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 0253f042004..9981ecdd53a 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -2178,7 +2178,7 @@ - (void)URLSession:(NSURLSession *)session [self cleanupForMultiPartUploadTask:transferUtilityMultiPartUploadTask]; return; } - + //Check if there was an error. if (error) { [self processMultipartUploadTaskError:error @@ -2214,19 +2214,19 @@ - (void)URLSession:(NSURLSession *)session [AWSS3TransferUtilityDatabaseHelper deleteTransferRequestFromDB:downloadTask.transferID databaseQueue:_databaseQueue]; return; } - + //Make sure to not overwrite if an error has already been set on the downloadTask if (!downloadTask.error) { downloadTask.error = error; } - + if (!downloadTask.error) { downloadTask.status = AWSS3TransferUtilityTransferStatusCompleted; } else { downloadTask.status = AWSS3TransferUtilityTransferStatusError; } - + if (downloadTask.error && HTTPResponse) { if ([self isErrorRetriable:HTTPResponse.statusCode responseFromServer:downloadTask.responseData]) { if (downloadTask.retryCount < self.transferUtilityConfiguration.retryLimit) { @@ -2246,7 +2246,7 @@ - (void)URLSession:(NSURLSession *)session NSError *updatedError = [[NSError alloc] initWithDomain:downloadTask.error.domain code:downloadTask.error.code userInfo:userInfo]; downloadTask.error = updatedError; } - + if (!downloadTask.error) { downloadTask.progress.completedUnitCount = downloadTask.progress.totalUnitCount; if (downloadTask.expression.progressBlock) { From dd8f5efa24c09690a885a060268d6c9061869e7e Mon Sep 17 00:00:00 2001 From: Brennan Stehling Date: Sat, 4 Jun 2022 00:49:41 -0700 Subject: [PATCH 11/11] WIP --- AWSS3/AWSS3TransferUtility.m | 1 - AWSS3/AWSS3TransferUtilityTasks.m | 152 ++++++++++----------------- AWSS3/AWSS3TransferUtility_private.h | 7 +- 3 files changed, 60 insertions(+), 100 deletions(-) diff --git a/AWSS3/AWSS3TransferUtility.m b/AWSS3/AWSS3TransferUtility.m index 9981ecdd53a..5c69bfd4604 100644 --- a/AWSS3/AWSS3TransferUtility.m +++ b/AWSS3/AWSS3TransferUtility.m @@ -2379,7 +2379,6 @@ - (void)completeTask:(AWSS3TransferUtilityTask *)task removeCompletedTask:(BOOL) [self.completedTaskDictionary removeObjectForKey:task.transferID]; [self unregisterTaskIdentifier:task.taskIdentifier]; } - } - (void)cleanupForMultiPartUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)task { diff --git a/AWSS3/AWSS3TransferUtilityTasks.m b/AWSS3/AWSS3TransferUtilityTasks.m index 2d095bf83ab..77486c117d1 100644 --- a/AWSS3/AWSS3TransferUtilityTasks.m +++ b/AWSS3/AWSS3TransferUtilityTasks.m @@ -129,12 +129,15 @@ - (instancetype)init { _waitingPartsDictionary = [NSMutableDictionary new]; _inProgressPartsDictionary = [NSMutableDictionary new]; _completedPartsSet = [NSMutableSet new]; + _serialQueue = dispatch_queue_create("com.amazonaws.AWSS3.MultipartUploadTask", DISPATCH_QUEUE_SERIAL); } return self; } - (BOOL)isUnderConcurrencyLimit { - return self.inProgressPartsDictionary.count < [self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit integerValue]; + NSUInteger dynamicLimit = NSProcessInfo.processInfo.activeProcessorCount * 2; + NSUInteger configuredLimit = self.transferUtility.transferUtilityConfiguration.multiPartConcurrencyLimit.integerValue; + return self.inProgressPartsDictionary.count < MAX(dynamicLimit, configuredLimit); } - (BOOL)hasWaitingTasks { @@ -167,13 +170,11 @@ - (AWSS3TransferUtilityMultiPartUploadExpression *)expression { - (void)cancel { self.cancelled = YES; self.status = AWSS3TransferUtilityTransferStatusCancelled; - for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; + for (AWSS3TransferUtilityUploadSubTask *subTask in self.inProgressTasks) { [subTask.sessionTask cancel]; } - for (NSNumber *key in [self.waitingPartsDictionary allKeys]) { - AWSS3TransferUtilityUploadSubTask *subTask = [self.waitingPartsDictionary objectForKey:key]; + for (AWSS3TransferUtilityUploadSubTask *subTask in self.waitingTasks) { [subTask.sessionTask cancel]; } @@ -190,37 +191,13 @@ - (void)resume { } NSCAssert(self.transferUtility != nil, @"Transfer Utility must be provided."); - -// for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { -// AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; -// subTask.status = AWSS3TransferUtilityTransferStatusInProgress; -// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID -// partNumber:subTask.partNumber -// taskIdentifier:subTask.taskIdentifier -// eTag:subTask.eTag -// status:subTask.status -// retry_count:self.retryCount -// databaseQueue:self.databaseQueue]; -// [subTask.sessionTask resume]; -// } -// -// self.status = AWSS3TransferUtilityTransferStatusInProgress; -// //Update the Master Record -// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID -// partNumber:@0 -// taskIdentifier:0 -// eTag:@"" -// status:self.status -// retry_count:self.retryCount -// databaseQueue:self.databaseQueue]; // Change status from paused to waiting for (AWSS3TransferUtilityUploadSubTask * nextSubTask in self.waitingTasks) { nextSubTask.status = AWSS3TransferUtilityTransferStatusWaiting; } - [self moveWaitingTasksToInProgress]; - [self completeIfDone]; + [self moveWaitingTasksToInProgress:YES]; } - (void)suspend { @@ -231,30 +208,6 @@ - (void)suspend { NSCAssert(self.transferUtility != nil, @"Transfer Utility must be provided."); -// for (NSNumber *key in [self.inProgressPartsDictionary allKeys]) { -// // all in progress tasks should be cancelled and a new subtask should replace it which is -// // put in the waiting dictionary and set with that status with a URLSessionTask which -// // has not been started. -// -// // then resuming should start uploading a number of parts up to the concurrency limit. -// -// AWSS3TransferUtilityUploadSubTask *subTask = [self.inProgressPartsDictionary objectForKey:key]; -// if (!subTask) { -// continue; -// } -// [subTask.sessionTask suspend]; -// subTask.status = AWSS3TransferUtilityTransferStatusPaused; -// -// [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:subTask.transferID -// partNumber:subTask.partNumber -// taskIdentifier:subTask.taskIdentifier -// eTag:subTask.eTag -// status:subTask.status -// retry_count:self.retryCount -// databaseQueue:self.databaseQueue]; -// } -// - // Cancel session task for all subtasks which are in progress and set status to paused for (AWSS3TransferUtilityUploadSubTask *inProgressSubTask in self.inProgressTasks) { // Note: This can happen due to lack of thread-safety @@ -280,8 +233,7 @@ - (void)suspend { NSError *error = [self.transferUtility createUploadSubTask:self subTask:subTask - startTransfer:NO - internalDictionaryToAddSubTaskTo:self.waitingPartsDictionary]; + startTransfer:NO]; if (error) { AWSDDLogError(@"Error creating AWSS3TransferUtilityUploadSubTask [%@]", error); @@ -294,7 +246,7 @@ - (void)suspend { [AWSS3TransferUtilityDatabaseHelper insertMultiPartUploadRequestSubTaskInDB:self subTask:subTask databaseQueue:self.databaseQueue]; } } - + self.status = AWSS3TransferUtilityTransferStatusPaused; //Update the Master Record [AWSS3TransferUtilityDatabaseHelper updateTransferRequestInDB:self.transferID @@ -313,13 +265,14 @@ - (void)addUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask { self.waitingPartsDictionary[@(subTask.taskIdentifier)] = subTask; } else if (subTask.status == AWSS3TransferUtilityTransferStatusInProgress) { self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = subTask; - } else if (subTask.status == AWSS3TransferUtilityTransferStatusCompleted) { [self.completedPartsSet addObject:subTask]; } else { AWSDDLogDebug(@"Sub Task status not supported: %lu", subTask.status); NSCAssert(NO, @"Status not supported"); } + + [self completeIfDone]; } - (void)removeWaitingUploadSubTask:(NSUInteger)taskIdentifier { @@ -344,13 +297,15 @@ - (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask - (void)moveWaitingTaskToInProgress:(AWSS3TransferUtilityUploadSubTask *)subTask startTransfer:(BOOL)startTransfer { if ([self.waitingTasks containsObject:subTask]) { - //Add to inProgress list + // Add to inProgress list self.inProgressPartsDictionary[@(subTask.taskIdentifier)] = subTask; - //Remove it from the waitingList + // Remove it from the waitingList self.waitingPartsDictionary[@(subTask.taskIdentifier)] = nil; AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(subTask.taskIdentifier), self.uploadID); if (startTransfer) { + AWSDDLogDebug(@"Starting subTask %@", @(subTask.taskIdentifier)); + NSCAssert(subTask.sessionTask.state == NSURLSessionTaskStateSuspended, @"State should be suspended before resuming."); [subTask.sessionTask resume]; } } @@ -381,20 +336,24 @@ - (void)moveWaitingTasksToInProgress:(BOOL)startTransfer { // move parts from waiting to in progress if under the concurrency limit while (self.isUnderConcurrencyLimit && self.hasWaitingTasks) { //Get a part from the waitingList - AWSS3TransferUtilityUploadSubTask *nextSubTask = [[self.waitingPartsDictionary allValues] objectAtIndex:0]; + AWSS3TransferUtilityUploadSubTask *nextSubTask = [self.waitingTasks objectAtIndex:0]; //Add to inProgress list self.inProgressPartsDictionary[@(nextSubTask.taskIdentifier)] = nextSubTask; + nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; //Remove it from the waitingList - [self.waitingPartsDictionary removeObjectForKey:@(nextSubTask.taskIdentifier)]; + self.waitingPartsDictionary[@(nextSubTask.taskIdentifier)] = nil; AWSDDLogDebug(@"Moving Task[%@] to progress for Multipart[%@]", @(nextSubTask.taskIdentifier), self.uploadID); + if (startTransfer) { AWSDDLogDebug(@"Starting subTask %@", @(nextSubTask.taskIdentifier)); + NSCAssert(nextSubTask.sessionTask.state == NSURLSessionTaskStateSuspended, @"State should be suspended before resuming."); [nextSubTask.sessionTask resume]; } - nextSubTask.status = AWSS3TransferUtilityTransferStatusInProgress; } + + [self completeIfDone]; } - (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask @@ -421,45 +380,49 @@ - (void)completeUploadSubTask:(AWSS3TransferUtilityUploadSubTask *)subTask } - (void)completeIfDone { - // Complete multipart upload if in progress and waiting tasks are done - if (!self.isDone) { - return; - } + dispatch_async(self.serialQueue, ^{ + // Complete multipart upload if in progress and waiting tasks are done + if (!self.isDone && self.status != AWSS3TransferUtilityTransferStatusCompleted) { + return; + } - //If there are no more inProgress parts, then we are done. + //If there are no more inProgress parts, then we are done. - //Validate that all the content has been uploaded. - int64_t totalBytesSent = 0; - for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.completedPartsSet) { - totalBytesSent += aSubTask.totalBytesExpectedToSend; - } + //Validate that all the content has been uploaded. + int64_t totalBytesSent = 0; + for (AWSS3TransferUtilityUploadSubTask *aSubTask in self.completedTasks) { + totalBytesSent += aSubTask.totalBytesExpectedToSend; + } - if (totalBytesSent != self.contentLength.longLongValue ) { - NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", - self.contentLength, @(totalBytesSent)]; - AWSDDLogDebug(@"%@", errorMessage); - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage - forKey:@"Message"]; + if (totalBytesSent != self.contentLength.longLongValue ) { + NSString *errorMessage = [NSString stringWithFormat:@"Expected to send [%@], but sent [%@] and there are no remaining parts. Failing transfer ", + self.contentLength, @(totalBytesSent)]; + AWSDDLogDebug(@"%@", errorMessage); + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMessage + forKey:@"Message"]; - self.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain - code:AWSS3TransferUtilityErrorClientError - userInfo:userInfo]; + self.error = [NSError errorWithDomain:AWSS3TransferUtilityErrorDomain + code:AWSS3TransferUtilityErrorClientError + userInfo:userInfo]; - //Execute call back if provided. - [self.transferUtility completeTask:self]; + //Execute call back if provided. + [self.transferUtility completeTask:self]; - //Abort the request, so the server can clean up any partials. - [self.transferUtility callAbortMultiPartForUploadTask:self]; + //Abort the request, so the server can clean up any partials. + [self.transferUtility callAbortMultiPartForUploadTask:self]; - //clean up. - [self.transferUtility cleanupForMultiPartUploadTask:self]; - return; - } + //clean up. + [self.transferUtility cleanupForMultiPartUploadTask:self]; + return; + } + + AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingTasks.count); + AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressTasks.count); + AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedTasks.count); + [self.transferUtility completeMultiPartForUploadTask:self]; + self.status = AWSS3TransferUtilityTransferStatusCompleted; + }); - AWSDDLogDebug(@"There are %lu waiting upload parts.", (unsigned long)self.waitingPartsDictionary.count); - AWSDDLogDebug(@"There are %lu in progress upload parts.", (unsigned long)self.inProgressPartsDictionary.count); - AWSDDLogDebug(@"There are %lu completed upload parts.", (unsigned long)self.completedPartsSet.count); - [self.transferUtility completeMultiPartForUploadTask:self]; } - (void)setCompletionHandler:(AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock)completionHandler { @@ -498,7 +461,6 @@ - (void)cancel { } - (void)setCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler { - self.expression.completionHandler = completionHandler; //If the task has already completed successfully //Or the task has completed with error, complete the task diff --git a/AWSS3/AWSS3TransferUtility_private.h b/AWSS3/AWSS3TransferUtility_private.h index 2f73280cb79..a65e83b47b6 100644 --- a/AWSS3/AWSS3TransferUtility_private.h +++ b/AWSS3/AWSS3TransferUtility_private.h @@ -23,13 +23,11 @@ @interface AWSS3TransferUtility () - (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask - subTask:(AWSS3TransferUtilityUploadSubTask *)subTask -internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo; + subTask:(AWSS3TransferUtilityUploadSubTask *)subTask; - (NSError *)createUploadSubTask:(AWSS3TransferUtilityMultiPartUploadTask *)transferUtilityMultiPartUploadTask subTask:(AWSS3TransferUtilityUploadSubTask *)subTask - startTransfer:(BOOL)startTransfer -internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddSubTaskTo; + startTransfer:(BOOL)startTransfer; - (void)completeTask:(AWSS3TransferUtilityTask *)task; - (AWSTask *)callAbortMultiPartForUploadTask:(AWSS3TransferUtilityMultiPartUploadTask *)uploadTask; @@ -81,6 +79,7 @@ internalDictionaryToAddSubTaskTo:(NSMutableDictionary *)internalDictionaryToAddS @property (strong, nonatomic) NSMutableDictionary *waitingPartsDictionary; @property (strong, nonatomic) NSMutableDictionary *inProgressPartsDictionary; @property (strong, nonatomic) NSMutableSet *completedPartsSet; +@property (strong, nonatomic) dispatch_queue_t serialQueue; @property int partNumber; @property NSNumber *contentLength;