⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 282218 in webkit


Ignore:
Timestamp:
Sep 9, 2021, 9:40:46 AM (5 years ago)
Author:
Chris Dumez
Message:

Implement a WebProcess cap
​https://bugs.webkit.org/show_bug.cgi?id=230067
<rdar://79479244>

Reviewed by Geoffrey Garen.

Source/WebKit:

Implement a WebProcess cap to avoid getting into a state where we run out of resources and crash in various ways.
The current limit is 400 and can be changed via an SPI. The limit is per UIProcess (not per process pool).
When we try and launch a new WebProcess and the limit has been reached, we terminate the least recently used
WebProcess to avoid going over the limit.

  • Shared/ProcessTerminationReason.h:
  • UIProcess/API/C/WKAPICast.h:

(WebKit::toAPI):

  • UIProcess/API/Cocoa/WKProcessPool.mm:

(+[WKProcessPool _setWebProcessCountLimit:]):

  • UIProcess/API/Cocoa/WKProcessPoolPrivate.h:
  • UIProcess/Cocoa/NavigationState.mm:

(WebKit::wkProcessTerminationReason):

  • UIProcess/WebPageProxy.cpp:

(WebKit::WebPageProxy::loadRequestWithNavigationShared):
(WebKit::WebPageProxy::loadFile):
(WebKit::WebPageProxy::loadDataWithNavigationShared):
(WebKit::WebPageProxy::loadSimulatedRequest):
(WebKit::WebPageProxy::loadAlternateHTML):
(WebKit::WebPageProxy::loadWebArchiveData):
(WebKit::WebPageProxy::reload):
(WebKit::WebPageProxy::goToBackForwardItem):
(WebKit::shouldReloadAfterProcessTermination):

  • UIProcess/WebProcessProxy.cpp:

(WebKit::WebProcessProxy::setProcessCountLimit):
(WebKit::WebProcessProxy::create):
(WebKit::WebProcessProxy::~WebProcessProxy):
(WebKit::WebProcessProxy::addProvisionalPageProxy):
(WebKit::WebProcessProxy::addExistingWebPage):
(WebKit::WebProcessProxy::processDidTerminateOrFailedToLaunch):
(WebKit::WebProcessProxy::establishServiceWorkerContext):
(WebKit::WebProcessProxy::markProcessAsRecentlyUsed):

  • UIProcess/WebProcessProxy.h:

Tools:

Add API test coverage.

  • TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm:

(TEST):

Location:
trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r282215 r282218  
     12021-09-09  Chris Dumez  <cdumez@apple.com>
     2
     3        Implement a WebProcess cap
     4        https://bugs.webkit.org/show_bug.cgi?id=230067
     5        <rdar://79479244>
     6
     7        Reviewed by Geoffrey Garen.
     8
     9        Implement a WebProcess cap to avoid getting into a state where we run out of resources and crash in various ways.
     10        The current limit is 400 and can be changed via an SPI. The limit is per UIProcess (not per process pool).
     11        When we try and launch a new WebProcess and the limit has been reached, we terminate the least recently used
     12        WebProcess to avoid going over the limit.
     13
     14        * Shared/ProcessTerminationReason.h:
     15        * UIProcess/API/C/WKAPICast.h:
     16        (WebKit::toAPI):
     17        * UIProcess/API/Cocoa/WKProcessPool.mm:
     18        (+[WKProcessPool _setWebProcessCountLimit:]):
     19        * UIProcess/API/Cocoa/WKProcessPoolPrivate.h:
     20        * UIProcess/Cocoa/NavigationState.mm:
     21        (WebKit::wkProcessTerminationReason):
     22        * UIProcess/WebPageProxy.cpp:
     23        (WebKit::WebPageProxy::loadRequestWithNavigationShared):
     24        (WebKit::WebPageProxy::loadFile):
     25        (WebKit::WebPageProxy::loadDataWithNavigationShared):
     26        (WebKit::WebPageProxy::loadSimulatedRequest):
     27        (WebKit::WebPageProxy::loadAlternateHTML):
     28        (WebKit::WebPageProxy::loadWebArchiveData):
     29        (WebKit::WebPageProxy::reload):
     30        (WebKit::WebPageProxy::goToBackForwardItem):
     31        (WebKit::shouldReloadAfterProcessTermination):
     32        * UIProcess/WebProcessProxy.cpp:
     33        (WebKit::WebProcessProxy::setProcessCountLimit):
     34        (WebKit::WebProcessProxy::create):
     35        (WebKit::WebProcessProxy::~WebProcessProxy):
     36        (WebKit::WebProcessProxy::addProvisionalPageProxy):
     37        (WebKit::WebProcessProxy::addExistingWebPage):
     38        (WebKit::WebProcessProxy::processDidTerminateOrFailedToLaunch):
     39        (WebKit::WebProcessProxy::establishServiceWorkerContext):
     40        (WebKit::WebProcessProxy::markProcessAsRecentlyUsed):
     41        * UIProcess/WebProcessProxy.h:
     42
    1432021-09-09  Wenson Hsieh  <wenson_hsieh@apple.com>
    244
  • trunk/Source/WebKit/Shared/ProcessTerminationReason.h

    r278895 r282218  
    3131    ExceededMemoryLimit,
    3232    ExceededCPULimit,
     33    ExceededProcessCountLimit,
    3334    RequestedByClient,
    3435    Crash,
  • trunk/Source/WebKit/UIProcess/API/C/WKAPICast.h

    r274815 r282218  
    244244    case ProcessTerminationReason::RequestedByClient:
    245245        return kWKProcessTerminationReasonRequestedByClient;
     246    case ProcessTerminationReason::ExceededProcessCountLimit:
    246247    case ProcessTerminationReason::RequestedByNetworkProcess:
    247248    case ProcessTerminationReason::RequestedByGPUProcess:
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPool.mm

    r281965 r282218  
    566566}
    567567
     568+ (void)_setWebProcessCountLimit:(unsigned)limit
     569{
     570    WebKit::WebProcessProxy::setProcessCountLimit(limit);
     571}
     572
    568573- (void)_garbageCollectJavaScriptObjectsForTesting
    569574{
  • trunk/Source/WebKit/UIProcess/API/Cocoa/WKProcessPoolPrivate.h

    r281965 r282218  
    7474+ (pid_t)_webAuthnProcessIdentifier WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
    7575
     76+ (void)_setWebProcessCountLimit:(unsigned)limit WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
     77
    7678- (void)_warmInitialProcess WK_API_AVAILABLE(macos(10.12), ios(10.0));
    7779- (void)_automationCapabilitiesDidChange WK_API_AVAILABLE(macos(10.12), ios(10.0));
  • trunk/Source/WebKit/UIProcess/Cocoa/NavigationState.mm

    r278315 r282218  
    10611061    case ProcessTerminationReason::RequestedByClient:
    10621062        return _WKProcessTerminationReasonRequestedByClient;
     1063    case ProcessTerminationReason::ExceededProcessCountLimit:
    10631064    case ProcessTerminationReason::RequestedByNetworkProcess:
    10641065    case ProcessTerminationReason::RequestedByGPUProcess:
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r282189 r282218  
    14141414    navigation.setIsLoadedWithNavigationShared(true);
    14151415
     1416    process->markProcessAsRecentlyUsed();
     1417
    14161418    if (!process->isLaunching() || !url.isLocalFile())
    14171419        process->send(Messages::WebPage::LoadRequest(loadParameters), webPageID);
    … …  
    14751477    addPlatformLoadParameters(m_process, loadParameters);
    14761478
     1479    m_process->markProcessAsRecentlyUsed();
    14771480    if (m_process->isLaunching())
    14781481        send(Messages::WebPage::LoadRequestWaitingForProcessLaunch(loadParameters, resourceDirectoryURL, m_identifier, checkAssumedReadAccessToResourceURL));
    … …  
    15341537    addPlatformLoadParameters(process, loadParameters);
    15351538
     1539    process->markProcessAsRecentlyUsed();
    15361540    process->assumeReadAccessToBaseURL(*this, baseURL);
    15371541    process->send(Messages::WebPage::LoadData(loadParameters), webPageID);
    … …  
    15911595    addPlatformLoadParameters(m_process, loadParameters);
    15921596
     1597    m_process->markProcessAsRecentlyUsed();
    15931598    m_process->assumeReadAccessToBaseURL(*this, baseURL);
    15941599    m_process->send(Messages::WebPage::LoadSimulatedRequestAndResponse(loadParameters, simulatedResponse), m_webPageID);
    … …  
    16341639    addPlatformLoadParameters(process(), loadParameters);
    16351640
     1641    m_process->markProcessAsRecentlyUsed();
    16361642    m_process->assumeReadAccessToBaseURL(*this, baseURL.string());
    16371643    m_process->assumeReadAccessToBaseURL(*this, unreachableURL.string());
    … …  
    16631669    addPlatformLoadParameters(process(), loadParameters);
    16641670
     1671    m_process->markProcessAsRecentlyUsed();
    16651672    send(Messages::WebPage::LoadData(loadParameters));
    16661673    m_process->startResponsivenessTimer();
    … …  
    17381745        navigation->setUserContentExtensionsEnabled(false);
    17391746
     1747    m_process->markProcessAsRecentlyUsed();
    17401748    send(Messages::WebPage::Reload(navigation->navigationID(), options.toRaw(), sandboxExtensionHandle));
    17411749    m_process->startResponsivenessTimer();
    … …  
    18161824    m_pageLoadState.setPendingAPIRequest(transaction, { navigation ? navigation->navigationID() : 0, item.url() });
    18171825
     1826    m_process->markProcessAsRecentlyUsed();
    18181827    send(Messages::WebPage::GoToBackForwardItem(navigation ? navigation->navigationID() : 0, item.itemID(), frameLoadType, ShouldTreatAsContinuingLoad::No, std::nullopt, m_lastNavigationWasAppInitiated));
    18191828    m_process->startResponsivenessTimer();
    … …  
    51775186void WebPageProxy::viewIsBecomingVisible()
    51785187{
     5188    m_process->markProcessAsRecentlyUsed();
    51795189#if ENABLE(MEDIA_STREAM)
    51805190    if (m_userMediaPermissionRequestManager)
    … …  
    77407750    case ProcessTerminationReason::Crash:
    77417751        return true;
     7752    case ProcessTerminationReason::ExceededProcessCountLimit:
    77427753    case ProcessTerminationReason::NavigationSwap:
    77437754    case ProcessTerminationReason::RequestedByClient:
  • trunk/Source/WebKit/UIProcess/WebProcessPool.cpp

    r282034 r282218  
    867867        return;
    868868
     869    if (WebProcessProxy::hasReachedProcessCountLimit())
     870        return;
     871
    869872    WEBPROCESSPOOL_RELEASE_LOG(PerformanceLogging, "prewarmProcess: Prewarming a WebProcess for performance");
    870873    createNewWebProcess(nullptr, WebProcessProxy::IsPrewarmed::Yes);
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.cpp

    r281832 r282218  
    109109using namespace WebCore;
    110110
     111static unsigned s_maxProcessCount { 400 };
     112
     113static ListHashSet<WebProcessProxy*>& liveProcessesLRU()
     114{
     115    ASSERT(RunLoop::isMain());
     116    static NeverDestroyed<ListHashSet<WebProcessProxy*>> processes;
     117    return processes;
     118}
     119
     120void WebProcessProxy::setProcessCountLimit(unsigned limit)
     121{
     122    s_maxProcessCount = limit;
     123}
     124
     125bool WebProcessProxy::hasReachedProcessCountLimit()
     126{
     127    return liveProcessesLRU().size() >= s_maxProcessCount;
     128}
     129
    111130static bool isMainThreadOrCheckDisabled()
    112131{
    … …  
    151170{
    152171    auto proxy = adoptRef(*new WebProcessProxy(processPool, websiteDataStore, isPrewarmed, crossOriginMode));
    153     if (shouldLaunchProcess == ShouldLaunchProcess::Yes)
     172    if (shouldLaunchProcess == ShouldLaunchProcess::Yes) {
     173        if (liveProcessesLRU().size() >= s_maxProcessCount) {
     174            for (auto& processPool : WebProcessPool::allProcessPools())
     175                processPool->webProcessCache().clear();
     176            if (liveProcessesLRU().size() >= s_maxProcessCount)
     177                liveProcessesLRU().first()->requestTermination(ProcessTerminationReason::ExceededProcessCountLimit);
     178        }
     179        ASSERT(liveProcessesLRU().size() < s_maxProcessCount);
     180        liveProcessesLRU().add(proxy.ptr());
    154181        proxy->connect();
     182    }
    155183    return proxy;
    156184}
    … …  
    238266    WEBPROCESSPROXY_RELEASE_LOG(Process, "destructor:");
    239267
     268    liveProcessesLRU().remove(this);
     269
    240270    for (auto identifier : m_speechRecognitionServerMap.keys())
    241271        removeMessageReceiver(Messages::SpeechRecognitionServer::messageReceiverName(), identifier);
    … …  
    338368    ASSERT(!m_isInProcessCache);
    339369    ASSERT(!m_provisionalPages.contains(&provisionalPage));
     370    markProcessAsRecentlyUsed();
    340371    m_provisionalPages.add(&provisionalPage);
    341372    updateRegistrationWithDataStore();
    … …  
    559590    }
    560591
     592    markProcessAsRecentlyUsed();
    561593    m_pageMap.set(webPage.identifier(), &webPage);
    562594    globalPageMap().set(webPage.identifier(), &webPage);
    … …  
    889921    // to be deleted before we can finish our work.
    890922    auto protectedThis = makeRef(*this);
     923
     924    liveProcessesLRU().remove(this);
    891925
    892926#if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)
    … …  
    18571891{
    18581892    WEBPROCESSPROXY_RELEASE_LOG(Loading, "establishServiceWorkerContext: Started");
     1893    markProcessAsRecentlyUsed();
    18591894    sendWithAsyncReply(Messages::WebProcess::EstablishWorkerContextConnectionToNetworkProcess { processPool().defaultPageGroup().pageGroupID(), m_serviceWorkerInformation->serviceWorkerPageProxyID, m_serviceWorkerInformation->serviceWorkerPageID, store, *m_registrableDomain, m_serviceWorkerInformation->initializationData }, [this, weakThis = makeWeakPtr(*this), completionHandler = WTFMove(completionHandler)]() mutable {
    18601895        if (weakThis)
    … …  
    20072042}
    20082043
     2044void WebProcessProxy::markProcessAsRecentlyUsed()
     2045{
     2046    if (liveProcessesLRU().contains(this))
     2047        liveProcessesLRU().appendOrMoveToLast(this);
     2048}
     2049
    20092050void WebProcessProxy::systemBeep()
    20102051{
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.h

    r281832 r282218  
    167167    PAL::SessionID sessionID() const;
    168168
     169    static bool hasReachedProcessCountLimit();
     170    static void setProcessCountLimit(unsigned);
     171
    169172    static WebProcessProxy* processForIdentifier(WebCore::ProcessIdentifier);
    170173    static WebPageProxy* webPage(WebPageProxyIdentifier);
    … …  
    411414#endif
    412415
     416    void markProcessAsRecentlyUsed();
     417
    413418#if PLATFORM(MAC)
    414419    void platformSuspendProcess();
  • trunk/Tools/ChangeLog

    r282216 r282218  
     12021-09-09  Chris Dumez  <cdumez@apple.com>
     2
     3        Implement a WebProcess cap
     4        https://bugs.webkit.org/show_bug.cgi?id=230067
     5        <rdar://79479244>
     6
     7        Reviewed by Geoffrey Garen.
     8
     9        Add API test coverage.
     10
     11        * TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm:
     12        (TEST):
     13
    1142021-09-09  Aakash Jain  <aakash_jain@apple.com>
    215
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebContentProcessDidTerminate.mm

    r273997 r282218  
    377377    TestWebKitAPI::Util::run(&done);
    378378}
     379
     380TEST(WKNavigation, WebProcessLimit)
     381{
     382    constexpr unsigned maxProcessCount = 10;
     383    [WKProcessPool _setWebProcessCountLimit:maxProcessCount];
     384
     385    auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
     386    [navigationDelegate setDidFinishNavigation:^(WKWebView *, WKNavigation *) {
     387        finishedLoad = true;
     388    }];
     389    auto createWebView = [&] {
     390        auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
     391        auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100) configuration:configuration.get()]);
     392        [webView setNavigationDelegate:navigationDelegate.get()];
     393        finishedLoad = false;
     394        [webView loadTestPageNamed:@"simple"];
     395        TestWebKitAPI::Util::run(&finishedLoad);
     396        return webView;
     397    };
     398
     399    [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView *) {
     400        didCrash = true;
     401    }];
     402
     403    Vector<RetainPtr<WKWebView>> views;
     404    for (unsigned i = 0; i < maxProcessCount; ++i)
     405        views.append(createWebView());
     406    EXPECT_FALSE(didCrash);
     407    for (auto& view : views)
     408        EXPECT_NE([view _webProcessIdentifier], 0);
     409
     410    // We have now reached the WebProcess cap, let's try and launch a new one.
     411    __block unsigned crashCount = 0;
     412    [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView * view) {
     413        EXPECT_EQ(views[0], view);
     414        ++crashCount;
     415    }];
     416    views.append(createWebView());
     417
     418    EXPECT_EQ(crashCount, 1U);
     419    for (unsigned i = 0; i < views.size(); ++i) {
     420        if (!i)
     421            EXPECT_EQ([views[i] _webProcessIdentifier], 0);
     422        else
     423            EXPECT_NE([views[i] _webProcessIdentifier], 0);
     424    }
     425
     426    crashCount = 0;
     427    [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView * view) {
     428        EXPECT_EQ(views[1], view);
     429        ++crashCount;
     430    }];
     431    views.append(createWebView());
     432
     433    EXPECT_EQ(crashCount, 1U);
     434    for (unsigned i = 0; i < views.size(); ++i) {
     435        if (i < 2)
     436            EXPECT_EQ([views[i] _webProcessIdentifier], 0);
     437        else
     438            EXPECT_NE([views[i] _webProcessIdentifier], 0);
     439    }
     440
     441    [WKProcessPool _setWebProcessCountLimit:400];
     442}
Note: See TracChangeset for help on using the changeset viewer.