Skip to content

Commit

Permalink
Add a ProcessAssertion cache
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=263096
rdar://113579564

Reviewed by Brent Fulgham.

Add a ProcessAssertion cache to avoid hammering RunningBoard when switching
quickly from one assertion type to another (e.g. background <-> foreground).
Taking RunningBoard assertions is expensive and this churn can result in high
CPU usage.

We have evidence this can happen for example when we're holding a background
assertion on the WebProcess but the client app keeps running JS in the view,
causing us to take a foreground assertion for each JS execution request.

We now keep previous assertions around for 3 seconds when switching from one
assertion type to another and we reuse them when possible.

* Source/WebKit/UIProcess/ProcessThrottler.cpp:
(WebKit::ProcessThrottler::ProcessAssertionCache::add):
(WebKit::ProcessThrottler::ProcessAssertionCache::tryTake):
(WebKit::ProcessThrottler::ProcessAssertionCache::remove):
(WebKit::ProcessThrottler::ProcessAssertionCache::CachedAssertion::CachedAssertion):
(WebKit::ProcessThrottler::ProcessAssertionCache::CachedAssertion::isValid const):
(WebKit::ProcessThrottler::ProcessAssertionCache::CachedAssertion::release):
(WebKit::ProcessThrottler::ProcessAssertionCache::CachedAssertion::entryExpired):
(WebKit::ProcessThrottler::ProcessThrottler):
(WebKit::ProcessThrottler::setThrottleState):
* Source/WebKit/UIProcess/ProcessThrottler.h:

Canonical link: https://commits.webkit.org/269319@main
  • Loading branch information
cdumez committed Oct 13, 2023
1 parent 7cdbf62 commit f6fade6
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 14 deletions.
85 changes: 72 additions & 13 deletions Source/WebKit/UIProcess/ProcessThrottler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
#include "Logging.h"
#include "ProcessThrottlerClient.h"
#include <optional>
#include <wtf/CheckedRef.h>
#include <wtf/CompletionHandler.h>
#include <wtf/EnumTraits.h>
#include <wtf/RunLoop.h>
#include <wtf/text/TextStream.h>

#if PLATFORM(COCOA)
Expand All @@ -42,8 +44,59 @@

namespace WebKit {

static const Seconds processSuspensionTimeout { 20_s };
static const Seconds removeAllAssertionsTimeout { 8_min };
static constexpr Seconds processSuspensionTimeout { 20_s };
static constexpr Seconds removeAllAssertionsTimeout { 8_min };
static constexpr Seconds processAssertionCacheLifetime { 3_s };

class ProcessThrottler::ProcessAssertionCache : public CanMakeCheckedPtr {
WTF_MAKE_FAST_ALLOCATED;
public:
void add(Ref<ProcessAssertion>&& assertion)
{
auto type = assertion->type();
assertion->setInvalidationHandler(nullptr);
ASSERT(!m_entries.contains(type));
m_entries.add(type, makeUniqueRef<CachedAssertion>(*this, WTFMove(assertion)));
}

RefPtr<ProcessAssertion> tryTake(ProcessAssertionType type)
{
if (auto assertion = m_entries.take(type); assertion && assertion->isValid())
return assertion->release();
return nullptr;
}

void remove(ProcessAssertionType type) { m_entries.remove(type); }

private:
class CachedAssertion {
WTF_MAKE_FAST_ALLOCATED;
public:
CachedAssertion(ProcessAssertionCache& cache, Ref<ProcessAssertion>&& assertion)
: m_cache(cache)
, m_assertion(WTFMove(assertion))
, m_expirationTimer(RunLoop::main(), this, &CachedAssertion::entryExpired)
{
m_expirationTimer.startOneShot(processAssertionCacheLifetime);
}

bool isValid() const { return m_assertion->isValid(); }
Ref<ProcessAssertion> release() { return m_assertion.releaseNonNull(); }

private:
void entryExpired()
{
ASSERT(m_assertion);
m_cache->remove(m_assertion->type());
}

CheckedRef<ProcessAssertionCache> m_cache;
RefPtr<ProcessAssertion> m_assertion;
RunLoop::Timer m_expirationTimer;
};

HashMap<ProcessAssertionType, UniqueRef<CachedAssertion>, IntHash<ProcessAssertionType>, WTF::StrongEnumHashTraits<ProcessAssertionType>> m_entries;
};

static uint64_t generatePrepareToSuspendRequestID()
{
Expand All @@ -52,7 +105,8 @@ static uint64_t generatePrepareToSuspendRequestID()
}

ProcessThrottler::ProcessThrottler(ProcessThrottlerClient& process, bool shouldTakeUIBackgroundAssertion)
: m_process(process)
: m_assertionCache(makeUniqueRef<ProcessAssertionCache>())
, m_process(process)
, m_prepareToSuspendTimeoutTimer(RunLoop::main(), this, &ProcessThrottler::prepareToSuspendTimeoutTimerFired)
, m_dropNearSuspendedAssertionTimer(RunLoop::main(), this, &ProcessThrottler::dropNearSuspendedAssertionTimerFired)
, m_prepareToDropLastAssertionTimeoutTimer(RunLoop::main(), this, &ProcessThrottler::prepareToDropLastAssertionTimeoutTimerFired)
Expand Down Expand Up @@ -188,16 +242,21 @@ void ProcessThrottler::setThrottleState(ProcessThrottleState newState)

// Keep the previous assertion active until the new assertion is taken asynchronously.
auto previousAssertion = std::exchange(m_assertion, nullptr);
if (m_shouldTakeUIBackgroundAssertion) {
auto assertion = ProcessAndUIAssertion::create(m_processID, assertionName(newType), newType, ProcessAssertion::Mode::Async, m_process.environmentIdentifier(), [previousAssertion = WTFMove(previousAssertion)] { });
assertion->setUIAssertionExpirationHandler([weakThis = WeakPtr { *this }] {
if (weakThis)
weakThis->uiAssertionWillExpireImminently();
});
m_assertion = WTFMove(assertion);
} else
m_assertion = ProcessAssertion::create(m_processID, assertionName(newType), newType, ProcessAssertion::Mode::Async, m_process.environmentIdentifier(), [previousAssertion = WTFMove(previousAssertion)] { });

if (previousAssertion)
m_assertionCache->add(*previousAssertion);

m_assertion = m_assertionCache->tryTake(newType);
if (!m_assertion) {
if (m_shouldTakeUIBackgroundAssertion) {
auto assertion = ProcessAndUIAssertion::create(m_processID, assertionName(newType), newType, ProcessAssertion::Mode::Async, m_process.environmentIdentifier(), [previousAssertion = WTFMove(previousAssertion)] { });
assertion->setUIAssertionExpirationHandler([weakThis = WeakPtr { *this }] {
if (weakThis)
weakThis->uiAssertionWillExpireImminently();
});
m_assertion = WTFMove(assertion);
} else
m_assertion = ProcessAssertion::create(m_processID, assertionName(newType), newType, ProcessAssertion::Mode::Async, m_process.environmentIdentifier(), [previousAssertion = WTFMove(previousAssertion)] { });
}
m_assertion->setInvalidationHandler([weakThis = WeakPtr { *this }] {
if (weakThis)
weakThis->assertionWasInvalidated();
Expand Down
5 changes: 4 additions & 1 deletion Source/WebKit/UIProcess/ProcessThrottler.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ class ProcessThrottler : public CanMakeWeakPtr<ProcessThrottler> {
void numberOfPagesAllowedToRunInTheBackgroundChanged();
void clearAssertion();

class ProcessAssertionCache;

UniqueRef<ProcessAssertionCache> m_assertionCache;
ProcessThrottlerClient& m_process;
ProcessID m_processID { 0 };
RefPtr<ProcessAssertion> m_assertion;
Expand All @@ -181,7 +184,7 @@ class ProcessThrottler : public CanMakeWeakPtr<ProcessThrottler> {
ProcessThrottleState m_state { ProcessThrottleState::Suspended };
PageAllowedToRunInTheBackgroundCounter m_pageAllowedToRunInTheBackgroundCounter;
bool m_shouldDropNearSuspendedAssertionAfterDelay { false };
bool m_shouldTakeUIBackgroundAssertion { false };
const bool m_shouldTakeUIBackgroundAssertion { false };
bool m_shouldTakeNearSuspendedAssertion { true };
bool m_allowsActivities { true };
};
Expand Down

0 comments on commit f6fade6

Please sign in to comment.