| /* |
| * Copyright (C) 2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "WebProcessCache.h" |
| |
| #include "LegacyGlobalSettings.h" |
| #include "Logging.h" |
| #include "WebProcessPool.h" |
| #include "WebProcessProxy.h" |
| #include <wtf/RAMSize.h> |
| #include <wtf/StdLibExtras.h> |
| |
| namespace WebKit { |
| |
| Seconds WebProcessCache::cachedProcessLifetime { 30_min }; |
| Seconds WebProcessCache::clearingDelayAfterApplicationResignsActive { 5_min }; |
| |
| static uint64_t generateAddRequestIdentifier() |
| { |
| static uint64_t identifier = 0; |
| return ++identifier; |
| } |
| |
| WebProcessCache::WebProcessCache(WebProcessPool& processPool) |
| : m_evictionTimer(RunLoop::main(), this, &WebProcessCache::clear) |
| { |
| updateCapacity(processPool); |
| platformInitialize(); |
| } |
| |
| bool WebProcessCache::canCacheProcess(WebProcessProxy& process) const |
| { |
| if (!capacity()) |
| return false; |
| |
| if (process.registrableDomain().isEmpty()) { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because it does not have an associated registrable domain", this, process.processIdentifier()); |
| return false; |
| } |
| |
| if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because we are under memory pressure", this, process.processIdentifier()); |
| return false; |
| } |
| |
| auto sessionID = process.websiteDataStore().sessionID(); |
| if (sessionID.isEphemeral() && !process.processPool().hasPagesUsingWebsiteDataStore(process.websiteDataStore())) { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because this session has been destroyed", this, process.processIdentifier()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool WebProcessCache::addProcessIfPossible(Ref<WebProcessProxy>&& process) |
| { |
| ASSERT(!process->pageCount()); |
| ASSERT(!process->provisionalPageCount()); |
| ASSERT(!process->suspendedPageCount()); |
| |
| if (!canCacheProcess(process)) |
| return false; |
| |
| uint64_t requestIdentifier = generateAddRequestIdentifier(); |
| m_pendingAddRequests.add(requestIdentifier, makeUnique<CachedProcess>(process.copyRef())); |
| |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Checking if process %i is responsive before caching it...", this, process->processIdentifier()); |
| process->isResponsive([this, processPool = makeRef(process->processPool()), requestIdentifier](bool isResponsive) { |
| auto cachedProcess = m_pendingAddRequests.take(requestIdentifier); |
| if (!cachedProcess) |
| return; |
| |
| if (!isResponsive) { |
| RELEASE_LOG_ERROR(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Not caching process %i because it is not responsive", &processPool->webProcessCache(), cachedProcess->process().processIdentifier()); |
| return; |
| } |
| processPool->webProcessCache().addProcess(WTFMove(cachedProcess)); |
| }); |
| return true; |
| } |
| |
| bool WebProcessCache::addProcess(std::unique_ptr<CachedProcess>&& cachedProcess) |
| { |
| ASSERT(!cachedProcess->process().pageCount()); |
| ASSERT(!cachedProcess->process().provisionalPageCount()); |
| ASSERT(!cachedProcess->process().suspendedPageCount()); |
| |
| if (!canCacheProcess(cachedProcess->process())) |
| return false; |
| |
| auto registrableDomain = cachedProcess->process().registrableDomain(); |
| RELEASE_ASSERT(!registrableDomain.isEmpty()); |
| |
| if (auto previousProcess = m_processesPerRegistrableDomain.take(registrableDomain)) |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Evicting process %i from WebProcess cache because a new process was added for the same domain", this, previousProcess->process().processIdentifier()); |
| |
| while (m_processesPerRegistrableDomain.size() >= capacity()) { |
| auto it = m_processesPerRegistrableDomain.random(); |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Evicting process %i from WebProcess cache because capacity was reached", this, it->value->process().processIdentifier()); |
| m_processesPerRegistrableDomain.remove(it); |
| } |
| |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess: Added process %i to WebProcess cache, cache size: [%u / %u]", this, cachedProcess->process().processIdentifier(), size() + 1, capacity()); |
| m_processesPerRegistrableDomain.add(registrableDomain, WTFMove(cachedProcess)); |
| |
| return true; |
| } |
| |
| RefPtr<WebProcessProxy> WebProcessCache::takeProcess(const WebCore::RegistrableDomain& registrableDomain, WebsiteDataStore& dataStore) |
| { |
| auto it = m_processesPerRegistrableDomain.find(registrableDomain); |
| if (it == m_processesPerRegistrableDomain.end()) |
| return nullptr; |
| |
| if (&it->value->process().websiteDataStore() != &dataStore) |
| return nullptr; |
| |
| auto process = it->value->takeProcess(); |
| m_processesPerRegistrableDomain.remove(it); |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::takeProcess: Taking process %i from WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity()); |
| |
| ASSERT(!process->pageCount()); |
| ASSERT(!process->provisionalPageCount()); |
| ASSERT(!process->suspendedPageCount()); |
| |
| return process; |
| } |
| |
| void WebProcessCache::updateCapacity(WebProcessPool& processPool) |
| { |
| if (!processPool.configuration().processSwapsOnNavigation() || !processPool.configuration().usesWebProcessCache() || LegacyGlobalSettings::singleton().cacheModel() != CacheModel::PrimaryWebBrowser) { |
| if (!processPool.configuration().processSwapsOnNavigation()) |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because process swap on navigation is disabled", this); |
| else if (!processPool.configuration().usesWebProcessCache()) |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled by client", this); |
| else |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because cache model is not PrimaryWebBrowser", this); |
| m_capacity = 0; |
| } else { |
| size_t memorySize = ramSize() / GB; |
| if (memorySize < 3) { |
| m_capacity = 0; |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because device does not have enough RAM", this); |
| } else { |
| // Allow 4 processes in the cache per GB of RAM, up to 30 processes. |
| m_capacity = std::min<unsigned>(memorySize * 4, 30); |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache has a capacity of %u processes", this, capacity()); |
| } |
| } |
| |
| if (!m_capacity) |
| clear(); |
| } |
| |
| void WebProcessCache::clear() |
| { |
| if (m_pendingAddRequests.isEmpty() && m_processesPerRegistrableDomain.isEmpty()) |
| return; |
| |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clear() evicting %u processes", this, m_pendingAddRequests.size() + m_processesPerRegistrableDomain.size()); |
| m_pendingAddRequests.clear(); |
| m_processesPerRegistrableDomain.clear(); |
| } |
| |
| void WebProcessCache::clearAllProcessesForSession(PAL::SessionID sessionID) |
| { |
| Vector<WebCore::RegistrableDomain> keysToRemove; |
| for (auto& pair : m_processesPerRegistrableDomain) { |
| if (pair.value->process().websiteDataStore().sessionID() == sessionID) { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clearAllProcessesForSession() evicting process %i because its session was destroyed", this, pair.value->process().processIdentifier()); |
| keysToRemove.append(pair.key); |
| } |
| } |
| for (auto& key : keysToRemove) |
| m_processesPerRegistrableDomain.remove(key); |
| |
| Vector<uint64_t> pendingRequestsToRemove; |
| for (auto& pair : m_pendingAddRequests) { |
| if (pair.value->process().websiteDataStore().sessionID() == sessionID) { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clearAllProcessesForSession() evicting process %i because its session was destroyed", this, pair.value->process().processIdentifier()); |
| pendingRequestsToRemove.append(pair.key); |
| } |
| } |
| for (auto& key : pendingRequestsToRemove) |
| m_pendingAddRequests.remove(key); |
| } |
| |
| void WebProcessCache::setApplicationIsActive(bool isActive) |
| { |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::setApplicationIsActive(%d)", this, isActive); |
| if (isActive) |
| m_evictionTimer.stop(); |
| else if (!m_processesPerRegistrableDomain.isEmpty()) |
| m_evictionTimer.startOneShot(clearingDelayAfterApplicationResignsActive); |
| } |
| |
| void WebProcessCache::removeProcess(WebProcessProxy& process, ShouldShutDownProcess shouldShutDownProcess) |
| { |
| RELEASE_ASSERT(!process.registrableDomain().isEmpty()); |
| RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::evictProcess(): Evicting process %i from WebProcess cache because it expired", this, process.processIdentifier()); |
| |
| std::unique_ptr<CachedProcess> cachedProcess; |
| auto it = m_processesPerRegistrableDomain.find(process.registrableDomain()); |
| if (it != m_processesPerRegistrableDomain.end() && &it->value->process() == &process) { |
| cachedProcess = WTFMove(it->value); |
| m_processesPerRegistrableDomain.remove(it); |
| } else { |
| for (auto& pair : m_pendingAddRequests) { |
| if (&pair.value->process() == &process) { |
| cachedProcess = WTFMove(pair.value); |
| m_pendingAddRequests.remove(pair.key); |
| break; |
| } |
| } |
| } |
| ASSERT(cachedProcess); |
| if (!cachedProcess) |
| return; |
| |
| ASSERT(&cachedProcess->process() == &process); |
| if (shouldShutDownProcess == ShouldShutDownProcess::No) |
| cachedProcess->takeProcess(); |
| } |
| |
| WebProcessCache::CachedProcess::CachedProcess(Ref<WebProcessProxy>&& process) |
| : m_process(WTFMove(process)) |
| , m_evictionTimer(RunLoop::main(), this, &CachedProcess::evictionTimerFired) |
| { |
| RELEASE_ASSERT(!m_process->pageCount()); |
| RELEASE_ASSERT_WITH_MESSAGE(!m_process->websiteDataStore().processes().contains(*m_process), "Only processes with pages should be registered with the data store"); |
| m_process->setIsInProcessCache(true); |
| m_evictionTimer.startOneShot(cachedProcessLifetime); |
| } |
| |
| WebProcessCache::CachedProcess::~CachedProcess() |
| { |
| if (!m_process) |
| return; |
| |
| ASSERT(!m_process->pageCount()); |
| ASSERT(!m_process->provisionalPageCount()); |
| ASSERT(!m_process->suspendedPageCount()); |
| |
| m_process->setIsInProcessCache(false); |
| m_process->shutDown(); |
| } |
| |
| Ref<WebProcessProxy> WebProcessCache::CachedProcess::takeProcess() |
| { |
| ASSERT(m_process); |
| m_evictionTimer.stop(); |
| m_process->setIsInProcessCache(false); |
| return m_process.releaseNonNull(); |
| } |
| |
| void WebProcessCache::CachedProcess::evictionTimerFired() |
| { |
| ASSERT(m_process); |
| m_process->processPool().webProcessCache().removeProcess(*m_process, ShouldShutDownProcess::Yes); |
| } |
| |
| #if !PLATFORM(COCOA) |
| void WebProcessCache::platformInitialize() |
| { |
| } |
| #endif |
| |
| } // namespace WebKit |