Title: [246053] trunk/Source/WebKit
Revision
246053
Author
[email protected]
Date
2019-06-03 18:28:36 -0700 (Mon, 03 Jun 2019)

Log Message

[iOS] Do not prevent app suspension for more than 20 seconds after getting backgrounded
https://bugs.webkit.org/show_bug.cgi?id=198488
<rdar://problem/50837208>

Reviewed by Geoff Garen.

Do not prevent app suspension for more than 20 seconds after getting backgrounded on iOS. We
do this by implementing our own expiration handler which notifies our child processes of
their imminent suspension before ending the background task that was preventing suspension.

* UIProcess/ios/ProcessAssertionIOS.mm:
(isBackgroundState):
(-[WKProcessAssertionBackgroundTaskManager init]):
(-[WKProcessAssertionBackgroundTaskManager _scheduleTimeoutTask]):
(-[WKProcessAssertionBackgroundTaskManager _cancelTimeoutTask]):
(-[WKProcessAssertionBackgroundTaskManager _backgroundTaskExpired]):
(-[WKProcessAssertionBackgroundTaskManager _updateBackgroundTask]):
(-[WKProcessAssertionBackgroundTaskManager _releaseBackgroundTask]):

Modified Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (246052 => 246053)


--- trunk/Source/WebKit/ChangeLog	2019-06-04 00:25:40 UTC (rev 246052)
+++ trunk/Source/WebKit/ChangeLog	2019-06-04 01:28:36 UTC (rev 246053)
@@ -1,3 +1,24 @@
+2019-06-03  Chris Dumez  <[email protected]>
+
+        [iOS] Do not prevent app suspension for more than 20 seconds after getting backgrounded
+        https://bugs.webkit.org/show_bug.cgi?id=198488
+        <rdar://problem/50837208>
+
+        Reviewed by Geoff Garen.
+
+        Do not prevent app suspension for more than 20 seconds after getting backgrounded on iOS. We
+        do this by implementing our own expiration handler which notifies our child processes of
+        their imminent suspension before ending the background task that was preventing suspension.
+
+        * UIProcess/ios/ProcessAssertionIOS.mm:
+        (isBackgroundState):
+        (-[WKProcessAssertionBackgroundTaskManager init]):
+        (-[WKProcessAssertionBackgroundTaskManager _scheduleTimeoutTask]):
+        (-[WKProcessAssertionBackgroundTaskManager _cancelTimeoutTask]):
+        (-[WKProcessAssertionBackgroundTaskManager _backgroundTaskExpired]):
+        (-[WKProcessAssertionBackgroundTaskManager _updateBackgroundTask]):
+        (-[WKProcessAssertionBackgroundTaskManager _releaseBackgroundTask]):
+
 2019-06-03  Wenson Hsieh  <[email protected]>
 
         Implement an internal switch to turn idempotent text autosizing and viewport rules off

Modified: trunk/Source/WebKit/UIProcess/ApplicationStateTracker.mm (246052 => 246053)


--- trunk/Source/WebKit/UIProcess/ApplicationStateTracker.mm	2019-06-04 00:25:40 UTC (rev 246052)
+++ trunk/Source/WebKit/UIProcess/ApplicationStateTracker.mm	2019-06-04 01:28:36 UTC (rev 246053)
@@ -142,9 +142,11 @@
         pid_t applicationPID = serviceViewController._hostProcessIdentifier;
         ASSERT(applicationPID);
 
-        auto applicationStateMonitor = adoptNS([[BKSApplicationStateMonitor alloc] init]);
-        m_isInBackground = isBackgroundState([applicationStateMonitor mostElevatedApplicationStateForPID:applicationPID]);
-        [applicationStateMonitor invalidate];
+        {
+            auto applicationStateMonitor = adoptNS([[BKSApplicationStateMonitor alloc] init]);
+            m_isInBackground = isBackgroundState([applicationStateMonitor mostElevatedApplicationStateForPID:applicationPID]);
+            [applicationStateMonitor invalidate];
+        }
 
         // Workaround for <rdar://problem/34028921>. If the host application is StoreKitUIService then it is also a ViewService
         // and is always in the background. We need to treat StoreKitUIService as foreground for the purpose of process suspension

Modified: trunk/Source/WebKit/UIProcess/ios/ProcessAssertionIOS.mm (246052 => 246053)


--- trunk/Source/WebKit/UIProcess/ios/ProcessAssertionIOS.mm	2019-06-04 00:25:40 UTC (rev 246052)
+++ trunk/Source/WebKit/UIProcess/ios/ProcessAssertionIOS.mm	2019-06-04 01:28:36 UTC (rev 246053)
@@ -41,6 +41,7 @@
 // the background task before the UIKit timeout (We get killed if we do not release the background task within 5 seconds
 // on the expiration handler getting called).
 static const Seconds releaseBackgroundTaskAfterExpirationDelay { 2_s };
+static const Seconds maximumBackgroundTaskDuration { 20_s };
 
 @interface WKProcessAssertionBackgroundTaskManager : NSObject
 
@@ -56,7 +57,8 @@
     UIBackgroundTaskIdentifier _backgroundTask;
     HashSet<ProcessAndUIAssertion*> _assertionsNeedingBackgroundTask;
     BOOL _applicationIsBackgrounded;
-    dispatch_block_t _pendingTaskReleaseTask;
+    dispatch_block_t _releaseTask;
+    dispatch_block_t _timeoutTask;
 }
 
 + (WKProcessAssertionBackgroundTaskManager *)shared
@@ -65,6 +67,11 @@
     return shared;
 }
 
+static bool isBackgroundState(BKSApplicationState state)
+{
+    return state == BKSApplicationStateBackgroundRunning || state == BKSApplicationStateBackgroundTaskSuspended;
+}
+
 - (instancetype)init
 {
     self = [super init];
@@ -73,14 +80,30 @@
 
     _backgroundTask = UIBackgroundTaskInvalid;
 
-    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
+    {
+        auto applicationStateMonitor = adoptNS([[BKSApplicationStateMonitor alloc] init]);
+        _applicationIsBackgrounded = isBackgroundState([applicationStateMonitor mostElevatedApplicationStateForPID:getpid()]);
+        [applicationStateMonitor invalidate];
+    }
+
+    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+    UIApplication *application = [UIApplication sharedApplication];
+
+    [notificationCenter addObserverForName:UIApplicationWillEnterForegroundNotification object:application queue:nil usingBlock:^(NSNotification *) {
+        RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Application will enter foreground", self);
         _applicationIsBackgrounded = NO;
-        [self _cancelPendingReleaseTask];
+        [self _cancelReleaseTask];
+        [self _cancelTimeoutTask];
         [self _updateBackgroundTask];
     }];
 
-    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
+    [notificationCenter addObserverForName:UIApplicationDidEnterBackgroundNotification object:application queue:nil usingBlock:^(NSNotification *) {
+        RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Application did enter background", self);
         _applicationIsBackgrounded = YES;
+
+        // We do not want to keep the app awake for more than 20 seconds after it gets backgrounded, so start the timeout timer now.
+        if (_backgroundTask != UIBackgroundTaskInvalid)
+            [self _scheduleTimeoutTask];
     }];
 
     return self;
@@ -112,35 +135,83 @@
         assertion->uiAssertionWillExpireImminently();
 }
 
+- (void)_scheduleTimeoutTask
+{
+    ASSERT(_backgroundTask != UIBackgroundTaskInvalid);
 
+    if (_timeoutTask)
+        return;
+
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _scheduleTimeoutTask", self);
+    _timeoutTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
+        _timeoutTask = nil;
+        RELEASE_LOG_ERROR(ProcessSuspension, "Background task was running for too long so WebKit will end it shortly.");
+        [self _backgroundTaskExpired];
+    });
+
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, maximumBackgroundTaskDuration.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _timeoutTask);
+#if !__has_feature(objc_arc)
+    // dispatch_async() does a Block_copy() / Block_release() on behalf of the caller.
+    Block_release(_timeoutTask);
+#endif
+}
+
+- (void)_cancelTimeoutTask
+{
+    if (!_timeoutTask)
+        return;
+
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelTimeoutTask", self);
+    dispatch_block_cancel(_timeoutTask);
+    _timeoutTask = nil;
+}
+
 - (void)_scheduleReleaseTask
 {
-    ASSERT(!_pendingTaskReleaseTask);
-    if (_pendingTaskReleaseTask)
+    ASSERT(!_releaseTask);
+    if (_releaseTask)
         return;
 
     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _scheduleReleaseTask because the expiration handler has been called", self);
-    _pendingTaskReleaseTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
-        _pendingTaskReleaseTask = nil;
+    _releaseTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
+        _releaseTask = nil;
         [self _releaseBackgroundTask];
     });
-    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, releaseBackgroundTaskAfterExpirationDelay.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _pendingTaskReleaseTask);
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, releaseBackgroundTaskAfterExpirationDelay.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _releaseTask);
 #if !__has_feature(objc_arc)
     // dispatch_async() does a Block_copy() / Block_release() on behalf of the caller.
-    Block_release(_pendingTaskReleaseTask);
+    Block_release(_releaseTask);
 #endif
 }
 
-- (void)_cancelPendingReleaseTask
+- (void)_cancelReleaseTask
 {
-    if (!_pendingTaskReleaseTask)
+    if (!_releaseTask)
         return;
 
-    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelPendingReleaseTask because the application is foreground again", self);
-    dispatch_block_cancel(_pendingTaskReleaseTask);
-    _pendingTaskReleaseTask = nil;
+    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelReleaseTask because the application is foreground again", self);
+    dispatch_block_cancel(_releaseTask);
+    _releaseTask = nil;
 }
 
+- (void)_backgroundTaskExpired
+{
+    RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _backgroundTaskExpired", self);
+    [self _cancelTimeoutTask];
+
+    // Tell our child processes they will suspend imminently.
+    if (RunLoop::isMain())
+        [self _notifyAssertionsOfImminentSuspension];
+    else {
+        // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
+        dispatch_sync(dispatch_get_main_queue(), ^{
+            [self _notifyAssertionsOfImminentSuspension];
+        });
+    }
+
+    [self _scheduleReleaseTask];
+}
+
 - (void)_updateBackgroundTask
 {
     if (!_assertionsNeedingBackgroundTask.isEmpty() && _backgroundTask == UIBackgroundTaskInvalid) {
@@ -150,17 +221,8 @@
         }
         RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - beginBackgroundTaskWithName", self);
         _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"com.apple.WebKit.ProcessAssertion" expirationHandler:^{
-            RELEASE_LOG_ERROR(ProcessSuspension, "Background task expired while holding WebKit ProcessAssertion (isMainThread? %d).", RunLoop::isMain());
-            // The expiration handler gets called on a non-main thread when the underlying assertion could not be taken (rdar://problem/27278419).
-            if (RunLoop::isMain())
-                [self _notifyAssertionsOfImminentSuspension];
-            else {
-                dispatch_sync(dispatch_get_main_queue(), ^{
-                    [self _notifyAssertionsOfImminentSuspension];
-                });
-            }
-
-            [self _scheduleReleaseTask];
+            RELEASE_LOG_ERROR(ProcessSuspension, "Background task expired while holding WebKit ProcessAssertion (isMainThread? %d, _applicationIsBackgrounded? %d).", RunLoop::isMain(), _applicationIsBackgrounded);
+            [self _backgroundTaskExpired];
         }];
     } else if (_assertionsNeedingBackgroundTask.isEmpty())
         [self _releaseBackgroundTask];
@@ -173,6 +235,8 @@
 
     RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - endBackgroundTask", self);
     [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
+    [self _cancelReleaseTask];
+    [self _cancelTimeoutTask];
     _backgroundTask = UIBackgroundTaskInvalid;
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to