Changeset 245334 in webkit


Ignore:
Timestamp:
May 15, 2019 11:29:40 AM (5 years ago)
Author:
Chris Dumez
Message:

[WK2][iOS] UIProcess may get killed because it is taking too long to release its background task after expiration
https://bugs.webkit.org/show_bug.cgi?id=197893
<rdar://problem/50234105>

Reviewed by Alex Christensen.

The UIProcess may get killed because it is taking too long to release its background task after its expiration handler
was called. The reason is that the background task's expiration handler was sequentially sending a ProcessWillSuspendImminently
synchronous IPC to each of its child processes and only then ends the background task. By the time we receive the response from
all child processes, it may be too late and we get killed.

To address the issue, we now:

  1. Send the ProcessWillSuspendImminently asynchronously so that all processes can do their processing in parallel
  2. After 2 seconds, the UIProcess releases the background task (We get killed after ~5 seconds)

Also, to make sure that the UIProcess supends promptly, we now make sure we never start a new background task *after*
the app has been backgrounded. The intention of our background task is too finish critical work (like releasing locked
files) after the app gets backgrounded, not to start new work and delay process suspension.

  • NetworkProcess/NetworkProcess.cpp:

(WebKit::NetworkProcess::processWillSuspendImminently):

  • NetworkProcess/NetworkProcess.h:
  • NetworkProcess/NetworkProcess.messages.in:
  • UIProcess/Network/NetworkProcessProxy.cpp:

(WebKit::NetworkProcessProxy::sendProcessWillSuspendImminently):

  • UIProcess/WebProcessProxy.cpp:

(WebKit::WebProcessProxy::sendProcessWillSuspendImminently):

  • UIProcess/ios/ProcessAssertionIOS.mm:

(-[WKProcessAssertionBackgroundTaskManager init]):
(-[WKProcessAssertionBackgroundTaskManager _scheduleReleaseTask]):
(-[WKProcessAssertionBackgroundTaskManager _cancelPendingReleaseTask]):
(-[WKProcessAssertionBackgroundTaskManager _updateBackgroundTask]):

  • WebProcess/WebProcess.cpp:

(WebKit::WebProcess::didReceiveSyncMessage):
(WebKit::WebProcess::processWillSuspendImminently):

  • WebProcess/WebProcess.h:
  • WebProcess/WebProcess.messages.in:
Location:
trunk/Source/WebKit
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r245329 r245334  
     12019-05-15  Chris Dumez  <cdumez@apple.com>
     2
     3        [WK2][iOS] UIProcess may get killed because it is taking too long to release its background task after expiration
     4        https://bugs.webkit.org/show_bug.cgi?id=197893
     5        <rdar://problem/50234105>
     6
     7        Reviewed by Alex Christensen.
     8
     9        The UIProcess may get killed because it is taking too long to release its background task after its expiration handler
     10        was called. The reason is that the background task's expiration handler was sequentially sending a ProcessWillSuspendImminently
     11        synchronous IPC to each of its child processes and only then ends the background task. By the time we receive the response from
     12        all child processes, it may be too late and we get killed.
     13
     14        To address the issue, we now:
     15        1. Send the ProcessWillSuspendImminently asynchronously so that all processes can do their processing in parallel
     16        2. After 2 seconds, the UIProcess releases the background task (We get killed after ~5 seconds)
     17
     18        Also, to make sure that the UIProcess supends promptly, we now make sure we never start a new background task *after*
     19        the app has been backgrounded. The intention of our background task is too finish critical work (like releasing locked
     20        files) after the app gets backgrounded, not to start new work and delay process suspension.
     21
     22        * NetworkProcess/NetworkProcess.cpp:
     23        (WebKit::NetworkProcess::processWillSuspendImminently):
     24        * NetworkProcess/NetworkProcess.h:
     25        * NetworkProcess/NetworkProcess.messages.in:
     26        * UIProcess/Network/NetworkProcessProxy.cpp:
     27        (WebKit::NetworkProcessProxy::sendProcessWillSuspendImminently):
     28        * UIProcess/WebProcessProxy.cpp:
     29        (WebKit::WebProcessProxy::sendProcessWillSuspendImminently):
     30        * UIProcess/ios/ProcessAssertionIOS.mm:
     31        (-[WKProcessAssertionBackgroundTaskManager init]):
     32        (-[WKProcessAssertionBackgroundTaskManager _scheduleReleaseTask]):
     33        (-[WKProcessAssertionBackgroundTaskManager _cancelPendingReleaseTask]):
     34        (-[WKProcessAssertionBackgroundTaskManager _updateBackgroundTask]):
     35        * WebProcess/WebProcess.cpp:
     36        (WebKit::WebProcess::didReceiveSyncMessage):
     37        (WebKit::WebProcess::processWillSuspendImminently):
     38        * WebProcess/WebProcess.h:
     39        * WebProcess/WebProcess.messages.in:
     40
    1412019-05-15  Jiewen Tan  <jiewen_tan@apple.com>
    242
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.cpp

    r245297 r245334  
    19501950}
    19511951
    1952 void NetworkProcess::processWillSuspendImminently(CompletionHandler<void(bool)>&& completionHandler)
    1953 {
    1954     RELEASE_LOG(ProcessSuspension, "%p - NetworkProcess::processWillSuspendImminently()", this);
     1952void NetworkProcess::processWillSuspendImminently()
     1953{
     1954    RELEASE_LOG(ProcessSuspension, "%p - NetworkProcess::processWillSuspendImminently() BEGIN", this);
    19551955#if PLATFORM(IOS_FAMILY) && ENABLE(INDEXED_DATABASE)
    19561956    for (auto& server : m_idbServers.values())
     
    19581958#endif
    19591959    actualPrepareToSuspend(ShouldAcknowledgeWhenReadyToSuspend::No);
    1960     completionHandler(true);
     1960    RELEASE_LOG(ProcessSuspension, "%p - NetworkProcess::processWillSuspendImminently() END", this);
    19611961}
    19621962
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.h

    r245149 r245334  
    182182    bool canHandleHTTPSServerTrustEvaluation() const { return m_canHandleHTTPSServerTrustEvaluation; }
    183183
    184     void processWillSuspendImminently(CompletionHandler<void(bool)>&&);
     184    void processWillSuspendImminently();
    185185    void prepareToSuspend();
    186186    void cancelPrepareToSuspend();
  • trunk/Source/WebKit/NetworkProcess/NetworkProcess.messages.in

    r245149 r245334  
    7878    ProcessDidTransitionToForeground()
    7979
    80     ProcessWillSuspendImminently() -> (bool handled) Synchronous
     80    ProcessWillSuspendImminently()
    8181    PrepareToSuspend()
    8282    CancelPrepareToSuspend()
  • trunk/Source/WebKit/UIProcess/Network/NetworkProcessProxy.cpp

    r245328 r245334  
    958958void NetworkProcessProxy::sendProcessWillSuspendImminently()
    959959{
    960     if (!canSendMessage())
    961         return;
    962 
    963     bool handled = false;
    964     sendSync(Messages::NetworkProcess::ProcessWillSuspendImminently(), Messages::NetworkProcess::ProcessWillSuspendImminently::Reply(handled), 0, 1_s);
     960    if (canSendMessage())
     961        send(Messages::NetworkProcess::ProcessWillSuspendImminently(), 0);
    965962}
    966963   
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.cpp

    r245328 r245334  
    11471147void WebProcessProxy::sendProcessWillSuspendImminently()
    11481148{
    1149     if (!canSendMessage())
    1150         return;
    1151 
    1152     bool handled = false;
    1153     sendSync(Messages::WebProcess::ProcessWillSuspendImminently(), Messages::WebProcess::ProcessWillSuspendImminently::Reply(handled), 0, 1_s);
     1149    if (canSendMessage())
     1150        send(Messages::WebProcess::ProcessWillSuspendImminently(), 0);
    11541151}
    11551152
  • trunk/Source/WebKit/UIProcess/ios/ProcessAssertionIOS.mm

    r245255 r245334  
    3838using WebKit::ProcessAndUIAssertion;
    3939
     40// This gives some time to our child processes to process the ProcessWillSuspendImminently IPC but makes sure we release
     41// the background task before the UIKit timeout (We get killed if we do not release the background task within 5 seconds
     42// on the expiration handler getting called).
     43static const Seconds releaseBackgroundTaskAfterExpirationDelay { 2_s };
     44
    4045@interface WKProcessAssertionBackgroundTaskManager : NSObject
    4146
     
    5156    UIBackgroundTaskIdentifier _backgroundTask;
    5257    HashSet<ProcessAndUIAssertion*> _assertionsNeedingBackgroundTask;
    53     BOOL _assertionHasExpiredInTheBackground;
     58    BOOL _applicationIsBackgrounded;
     59    dispatch_block_t _pendingTaskReleaseTask;
    5460}
    5561
     
    6975
    7076    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
    71         _assertionHasExpiredInTheBackground = NO;
     77        _applicationIsBackgrounded = NO;
     78        [self _cancelPendingReleaseTask];
    7279        [self _updateBackgroundTask];
    7380    }];
    7481
     82    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication] queue:nil usingBlock:^(NSNotification *) {
     83        _applicationIsBackgrounded = YES;
     84    }];
     85
    7586    return self;
    7687}
     
    102113}
    103114
     115
     116- (void)_scheduleReleaseTask
     117{
     118    ASSERT(!_pendingTaskReleaseTask);
     119    if (_pendingTaskReleaseTask)
     120        return;
     121
     122    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _scheduleReleaseTask because the expiration handler has been called", self);
     123    _pendingTaskReleaseTask = dispatch_block_create((dispatch_block_flags_t)0, ^{
     124        _pendingTaskReleaseTask = nil;
     125        [self _releaseBackgroundTask];
     126    });
     127    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, releaseBackgroundTaskAfterExpirationDelay.value() * NSEC_PER_SEC), dispatch_get_main_queue(), _pendingTaskReleaseTask);
     128#if !__has_feature(objc_arc)
     129    // dispatch_async() does a Block_copy() / Block_release() on behalf of the caller.
     130    Block_release(_pendingTaskReleaseTask);
     131#endif
     132}
     133
     134- (void)_cancelPendingReleaseTask
     135{
     136    if (!_pendingTaskReleaseTask)
     137        return;
     138
     139    RELEASE_LOG(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager - _cancelPendingReleaseTask because the application is foreground again", self);
     140    dispatch_block_cancel(_pendingTaskReleaseTask);
     141    _pendingTaskReleaseTask = nil;
     142}
     143
    104144- (void)_updateBackgroundTask
    105145{
    106146    if (!_assertionsNeedingBackgroundTask.isEmpty() && _backgroundTask == UIBackgroundTaskInvalid) {
    107         if (_assertionHasExpiredInTheBackground) {
    108             RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Ignored request to start a background task because we're still in the background and the previous task expired", self);
    109             // Our invalidation handler would not get called if we tried to re-take a new background assertion at this point, and the UIProcess would get killed (rdar://problem/50001505).
     147        if (_applicationIsBackgrounded) {
     148            RELEASE_LOG_ERROR(ProcessSuspension, "%p - WKProcessAssertionBackgroundTaskManager: Ignored request to start a new background task because the application is already in the background", self);
    110149            return;
    111150        }
     
    122161            }
    123162
    124             // Remember that the assertion has expired in the background so we do not try to re-take it until the application becomes foreground again.
    125             _assertionHasExpiredInTheBackground = YES;
    126             [self _releaseBackgroundTask];
     163            [self _scheduleReleaseTask];
    127164        }];
    128165    } else if (_assertionsNeedingBackgroundTask.isEmpty())
  • trunk/Source/WebKit/WebProcess/WebProcess.cpp

    r245299 r245334  
    733733    if (messageReceiverMap().dispatchSyncMessage(connection, decoder, replyEncoder))
    734734        return;
    735 
    736     didReceiveSyncWebProcessMessage(connection, decoder, replyEncoder);
    737735}
    738736
     
    14911489}
    14921490
    1493 void WebProcess::processWillSuspendImminently(CompletionHandler<void(bool)>&& completionHandler)
    1494 {
    1495     if (parentProcessConnection()->inSendSync()) {
    1496         // Avoid reentrency bugs such as rdar://problem/21605505 by just bailing
    1497         // if we get an incoming ProcessWillSuspendImminently message when waiting for a
    1498         // reply to a sync message.
    1499         // FIXME: ProcessWillSuspendImminently should not be a sync message.
    1500         return completionHandler(false);
    1501     }
    1502 
    1503     RELEASE_LOG(ProcessSuspension, "%p - WebProcess::processWillSuspendImminently()", this);
     1491void WebProcess::processWillSuspendImminently()
     1492{
     1493    RELEASE_LOG(ProcessSuspension, "%p - WebProcess::processWillSuspendImminently() BEGIN", this);
    15041494    actualPrepareToSuspend(ShouldAcknowledgeWhenReadyToSuspend::No);
    1505     completionHandler(true);
     1495    RELEASE_LOG(ProcessSuspension, "%p - WebProcess::processWillSuspendImminently() END", this);
    15061496}
    15071497
  • trunk/Source/WebKit/WebProcess/WebProcess.h

    r245299 r245334  
    217217    void setHiddenPageDOMTimerThrottlingIncreaseLimit(int milliseconds);
    218218
    219     void processWillSuspendImminently(CompletionHandler<void(bool)>&&);
     219    void processWillSuspendImminently();
    220220    void prepareToSuspend();
    221221    void cancelPrepareToSuspend();
     
    421421    // Implemented in generated WebProcessMessageReceiver.cpp
    422422    void didReceiveWebProcessMessage(IPC::Connection&, IPC::Decoder&);
    423     void didReceiveSyncWebProcessMessage(IPC::Connection&, IPC::Decoder&, std::unique_ptr<IPC::Encoder>&);
    424423
    425424#if PLATFORM(MAC)
  • trunk/Source/WebKit/WebProcess/WebProcess.messages.in

    r245151 r245334  
    9797    DestroyAutomationSessionProxy()
    9898
    99     ProcessWillSuspendImminently() -> (bool handled) Synchronous
     99    ProcessWillSuspendImminently()
    100100    PrepareToSuspend()
    101101    CancelPrepareToSuspend()
Note: See TracChangeset for help on using the changeset viewer.