From 7ba29f4a36b81f416c79cccb45ffea4c169861d7 Mon Sep 17 00:00:00 2001 From: Darren Mo Date: Sun, 16 May 2021 18:40:57 -0700 Subject: [PATCH] Mitigate upload failure when app is backgrounded. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iOS closes an app’s network connections when the app is backgrounded. This can cause an in-progress upload request to fail. We can mitigate this by requesting additional background execution time using the `UIApplication` background task APIs. BUG=b:130302235 Change-Id: Ifd8e14ca82c736ad7dd60dcdd0d4bbcabb76f5ad Signed-off-by: Darren Mo Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/2251020 Reviewed-by: Mark Mentovai --- .../ios/Breakpad.xcodeproj/project.pbxproj | 8 ++ src/common/mac/HTTPRequest.m | 73 ++++++++++++++++--- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/client/ios/Breakpad.xcodeproj/project.pbxproj b/src/client/ios/Breakpad.xcodeproj/project.pbxproj index 2ed66d53..bf00472a 100644 --- a/src/client/ios/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/ios/Breakpad.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; }; CF6D547D1F9E6FFE00E95174 /* long_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF6D547C1F9E6FFE00E95174 /* long_string_dictionary.cc */; }; CF706DC11F7C6EFB002C54C7 /* long_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = CF706DC01F7C6EFB002C54C7 /* long_string_dictionary.h */; }; + E69213D8265202570071B04F /* HTTPRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E69213D6265202570071B04F /* HTTPRequest.h */; }; + E69213D9265202570071B04F /* HTTPRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = E69213D7265202570071B04F /* HTTPRequest.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -116,6 +118,8 @@ CF6D547C1F9E6FFE00E95174 /* long_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = long_string_dictionary.cc; sourceTree = ""; }; CF706DC01F7C6EFB002C54C7 /* long_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = long_string_dictionary.h; sourceTree = ""; }; D2AAC07E0554694100DB518D /* libBreakpad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBreakpad.a; sourceTree = BUILT_PRODUCTS_DIR; }; + E69213D6265202570071B04F /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = ""; }; + E69213D7265202570071B04F /* HTTPRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPRequest.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -289,6 +293,8 @@ children = ( 16C7CC88147D4A4300776EAD /* GTMLogger.h */, 16C7CC89147D4A4300776EAD /* GTMLogger.m */, + E69213D6265202570071B04F /* HTTPRequest.h */, + E69213D7265202570071B04F /* HTTPRequest.m */, 16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */, 16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */, 16C7CC93147D4A4300776EAD /* file_id.cc */, @@ -335,6 +341,7 @@ 16C7CE1A147D4A4300776EAD /* minidump_file_writer.h in Headers */, 16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */, 16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */, + E69213D8265202570071B04F /* HTTPRequest.h in Headers */, 16C7CE7A147D4A4300776EAD /* HTTPMultipartUpload.h in Headers */, 16C7CE84147D4A4300776EAD /* file_id.h in Headers */, 16C7CE86147D4A4300776EAD /* macho_id.h in Headers */, @@ -416,6 +423,7 @@ buildActionMask = 2147483647; files = ( 16C7CCCD147D4A4300776EAD /* Breakpad.mm in Sources */, + E69213D9265202570071B04F /* HTTPRequest.m in Sources */, 16C7CDE9147D4A4300776EAD /* ConfigFile.mm in Sources */, 16C7CDF5147D4A4300776EAD /* breakpad_nlist_64.cc in Sources */, 16C7CDF7147D4A4300776EAD /* dynamic_images.cc in Sources */, diff --git a/src/common/mac/HTTPRequest.m b/src/common/mac/HTTPRequest.m index ee7de3af..925ab582 100644 --- a/src/common/mac/HTTPRequest.m +++ b/src/common/mac/HTTPRequest.m @@ -32,8 +32,26 @@ #include #include +#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) +#import +#define HAS_BACKGROUND_TASK_API 1 +#else +#define HAS_BACKGROUND_TASK_API 0 +#endif + #import "encoding_util.h" +#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ + __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) || \ + (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ + defined(MAC_OS_X_VERSION_10_11) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) +#define USE_NSURLSESSION 1 +#else +#define USE_NSURLSESSION 0 +#endif + // As -[NSURLConnection sendSynchronousRequest:returningResponse:error:] has // been deprecated with iOS 9.0 / OS X 10.11 SDKs, this function re-implements // it using -[NSURLSession dataTaskWithRequest:completionHandler:] which is @@ -41,20 +59,17 @@ static NSData* SendSynchronousNSURLRequest(NSURLRequest* req, NSURLResponse** outResponse, NSError** outError) { -#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_7_0) && \ - __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) || \ - (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \ - defined(MAC_OS_X_VERSION_10_11) && \ - MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) +#if USE_NSURLSESSION __block NSData* result = nil; __block NSError* error = nil; __block NSURLResponse* response = nil; dispatch_semaphore_t waitSemaphone = dispatch_semaphore_create(0); + NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; [config setTimeoutIntervalForRequest:240.0]; NSURLSession* session = [NSURLSession sessionWithConfiguration:config]; - [[session + NSURLSessionDataTask *task = [session dataTaskWithRequest:req completionHandler:^(NSData* data, NSURLResponse* resp, NSError* err) { if (outError) @@ -64,19 +79,59 @@ static NSData* SendSynchronousNSURLRequest(NSURLRequest* req, if (err == nil) result = [data retain]; dispatch_semaphore_signal(waitSemaphone); - }] resume]; + }]; + [task resume]; + +#if HAS_BACKGROUND_TASK_API + // Used to guard against ending the background task twice, which UIKit + // considers to be an error. + __block BOOL isBackgroundTaskActive = YES; + __block UIBackgroundTaskIdentifier backgroundTaskIdentifier = + UIBackgroundTaskInvalid; + backgroundTaskIdentifier = [UIApplication.sharedApplication + beginBackgroundTaskWithName:@"Breakpad Upload" + expirationHandler:^{ + if (!isBackgroundTaskActive) { + return; + } + isBackgroundTaskActive = NO; + + [task cancel]; + [UIApplication.sharedApplication + endBackgroundTask:backgroundTaskIdentifier]; + }]; +#endif // HAS_BACKGROUND_TASK_API + dispatch_semaphore_wait(waitSemaphone, DISPATCH_TIME_FOREVER); dispatch_release(waitSemaphone); + +#if HAS_BACKGROUND_TASK_API + if (backgroundTaskIdentifier != UIBackgroundTaskInvalid) { + // Dispatch to main queue in order to synchronize access to + // `isBackgroundTaskActive` with the background task expiration handler, + // which is always run on the main thread. + dispatch_async(dispatch_get_main_queue(), ^{ + if (!isBackgroundTaskActive) { + return; + } + isBackgroundTaskActive = NO; + + [UIApplication.sharedApplication + endBackgroundTask:backgroundTaskIdentifier]; + }); + } +#endif // HAS_BACKGROUND_TASK_API + if (outError) *outError = [error autorelease]; if (outResponse) *outResponse = [response autorelease]; return [result autorelease]; -#else +#else // USE_NSURLSESSION return [NSURLConnection sendSynchronousRequest:req returningResponse:outResponse error:outError]; -#endif +#endif // USE_NSURLSESSION } @implementation HTTPRequest