1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WebProcessCache.h"
28
29#include "Logging.h"
30#include "WebProcessPool.h"
31#include "WebProcessProxy.h"
32#include <wtf/RAMSize.h>
33#include <wtf/StdLibExtras.h>
34
35namespace WebKit {
36
37Seconds WebProcessCache::cachedProcessLifetime { 30_min };
38Seconds WebProcessCache::clearingDelayAfterApplicationResignsActive { 5_min };
39
40static uint64_t generateAddRequestIdentifier()
41{
42 static uint64_t identifier = 0;
43 return ++identifier;
44}
45
46WebProcessCache::WebProcessCache(WebProcessPool& processPool)
47 : m_evictionTimer(RunLoop::main(), this, &WebProcessCache::clear)
48{
49 updateCapacity(processPool);
50 platformInitialize();
51}
52
53bool WebProcessCache::canCacheProcess(WebProcessProxy& process) const
54{
55 if (!capacity())
56 return false;
57
58 if (process.registrableDomain().isEmpty()) {
59 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because it does not have an associated registrable domain", this, process.processIdentifier());
60 return false;
61 }
62
63 if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
64 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because we are under memory pressure", this, process.processIdentifier());
65 return false;
66 }
67
68 auto sessionID = process.websiteDataStore().sessionID();
69 if (sessionID != PAL::SessionID::defaultSessionID() && !process.processPool().hasPagesUsingWebsiteDataStore(process.websiteDataStore())) {
70 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::canCacheProcess(): Not caching process %i because this session has been destroyed", this, process.processIdentifier());
71 return false;
72 }
73
74 return true;
75}
76
77bool WebProcessCache::addProcessIfPossible(Ref<WebProcessProxy>&& process)
78{
79 ASSERT(!process->pageCount());
80 ASSERT(!process->provisionalPageCount());
81 ASSERT(!process->suspendedPageCount());
82
83 if (!canCacheProcess(process))
84 return false;
85
86 uint64_t requestIdentifier = generateAddRequestIdentifier();
87 m_pendingAddRequests.add(requestIdentifier, std::make_unique<CachedProcess>(process.copyRef()));
88
89 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Checking if process %i is responsive before caching it...", this, process->processIdentifier());
90 process->isResponsive([this, processPool = makeRef(process->processPool()), requestIdentifier](bool isResponsive) {
91 auto cachedProcess = m_pendingAddRequests.take(requestIdentifier);
92 if (!cachedProcess)
93 return;
94
95 if (!isResponsive) {
96 RELEASE_LOG_ERROR(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Not caching process %i because it is not responsive", &processPool->webProcessCache(), cachedProcess->process().processIdentifier());
97 return;
98 }
99 processPool->webProcessCache().addProcess(WTFMove(cachedProcess));
100 });
101 return true;
102}
103
104bool WebProcessCache::addProcess(std::unique_ptr<CachedProcess>&& cachedProcess)
105{
106 ASSERT(!cachedProcess->process().pageCount());
107 ASSERT(!cachedProcess->process().provisionalPageCount());
108 ASSERT(!cachedProcess->process().suspendedPageCount());
109
110 if (!canCacheProcess(cachedProcess->process()))
111 return false;
112
113 auto registrableDomain = cachedProcess->process().registrableDomain();
114 RELEASE_ASSERT(!registrableDomain.isEmpty());
115
116 if (auto previousProcess = m_processesPerRegistrableDomain.take(registrableDomain))
117 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());
118
119 while (m_processesPerRegistrableDomain.size() >= capacity()) {
120 auto it = m_processesPerRegistrableDomain.random();
121 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Evicting process %i from WebProcess cache because capacity was reached", this, it->value->process().processIdentifier());
122 m_processesPerRegistrableDomain.remove(it);
123 }
124
125 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess: Added process %i to WebProcess cache, cache size: [%u / %u]", this, cachedProcess->process().processIdentifier(), size() + 1, capacity());
126 m_processesPerRegistrableDomain.add(registrableDomain, WTFMove(cachedProcess));
127
128 return true;
129}
130
131RefPtr<WebProcessProxy> WebProcessCache::takeProcess(const WebCore::RegistrableDomain& registrableDomain, WebsiteDataStore& dataStore)
132{
133 auto it = m_processesPerRegistrableDomain.find(registrableDomain);
134 if (it == m_processesPerRegistrableDomain.end())
135 return nullptr;
136
137 if (&it->value->process().websiteDataStore() != &dataStore)
138 return nullptr;
139
140 auto process = it->value->takeProcess();
141 m_processesPerRegistrableDomain.remove(it);
142 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::takeProcess: Taking process %i from WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity());
143
144 ASSERT(!process->pageCount());
145 ASSERT(!process->provisionalPageCount());
146 ASSERT(!process->suspendedPageCount());
147
148 return process;
149}
150
151void WebProcessCache::updateCapacity(WebProcessPool& processPool)
152{
153 if (!processPool.configuration().processSwapsOnNavigation() || !processPool.configuration().usesWebProcessCache()) {
154 if (!processPool.configuration().processSwapsOnNavigation())
155 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because process swap on navigation is disabled", this);
156 else
157 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled by client", this);
158 m_capacity = 0;
159 } else {
160 size_t memorySize = ramSize() / GB;
161 if (memorySize < 3) {
162 m_capacity = 0;
163 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because device does not have enough RAM", this);
164 } else {
165 // Allow 4 processes in the cache per GB of RAM, up to 30 processes.
166 m_capacity = std::min<unsigned>(memorySize * 4, 30);
167 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache has a capacity of %u processes", this, capacity());
168 }
169 }
170
171 if (!m_capacity)
172 clear();
173}
174
175void WebProcessCache::clear()
176{
177 if (m_pendingAddRequests.isEmpty() && m_processesPerRegistrableDomain.isEmpty())
178 return;
179
180 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clear() evicting %u processes", this, m_pendingAddRequests.size() + m_processesPerRegistrableDomain.size());
181 m_pendingAddRequests.clear();
182 m_processesPerRegistrableDomain.clear();
183}
184
185void WebProcessCache::clearAllProcessesForSession(PAL::SessionID sessionID)
186{
187 Vector<WebCore::RegistrableDomain> keysToRemove;
188 for (auto& pair : m_processesPerRegistrableDomain) {
189 if (pair.value->process().websiteDataStore().sessionID() == sessionID) {
190 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clearAllProcessesForSession() evicting process %i because its session was destroyed", this, pair.value->process().processIdentifier());
191 keysToRemove.append(pair.key);
192 }
193 }
194 for (auto& key : keysToRemove)
195 m_processesPerRegistrableDomain.remove(key);
196
197 Vector<uint64_t> pendingRequestsToRemove;
198 for (auto& pair : m_pendingAddRequests) {
199 if (pair.value->process().websiteDataStore().sessionID() == sessionID) {
200 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clearAllProcessesForSession() evicting process %i because its session was destroyed", this, pair.value->process().processIdentifier());
201 pendingRequestsToRemove.append(pair.key);
202 }
203 }
204 for (auto& key : pendingRequestsToRemove)
205 m_pendingAddRequests.remove(key);
206}
207
208void WebProcessCache::setApplicationIsActive(bool isActive)
209{
210 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::setApplicationIsActive(%d)", this, isActive);
211 if (isActive)
212 m_evictionTimer.stop();
213 else if (!m_processesPerRegistrableDomain.isEmpty())
214 m_evictionTimer.startOneShot(clearingDelayAfterApplicationResignsActive);
215}
216
217void WebProcessCache::removeProcess(WebProcessProxy& process, ShouldShutDownProcess shouldShutDownProcess)
218{
219 RELEASE_ASSERT(!process.registrableDomain().isEmpty());
220 RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::evictProcess(): Evicting process %i from WebProcess cache because it expired", this, process.processIdentifier());
221
222 std::unique_ptr<CachedProcess> cachedProcess;
223 auto it = m_processesPerRegistrableDomain.find(process.registrableDomain());
224 if (it != m_processesPerRegistrableDomain.end() && &it->value->process() == &process) {
225 cachedProcess = WTFMove(it->value);
226 m_processesPerRegistrableDomain.remove(it);
227 } else {
228 for (auto& pair : m_pendingAddRequests) {
229 if (&pair.value->process() == &process) {
230 cachedProcess = WTFMove(pair.value);
231 m_pendingAddRequests.remove(pair.key);
232 break;
233 }
234 }
235 }
236 ASSERT(cachedProcess);
237 if (!cachedProcess)
238 return;
239
240 ASSERT(&cachedProcess->process() == &process);
241 if (shouldShutDownProcess == ShouldShutDownProcess::No)
242 cachedProcess->takeProcess();
243}
244
245WebProcessCache::CachedProcess::CachedProcess(Ref<WebProcessProxy>&& process)
246 : m_process(WTFMove(process))
247 , m_evictionTimer(RunLoop::main(), this, &CachedProcess::evictionTimerFired)
248{
249 RELEASE_ASSERT(!m_process->pageCount());
250 RELEASE_ASSERT_WITH_MESSAGE(!m_process->websiteDataStore().hasProcess(m_process.get()), "Only processes with pages should be registered with the data store");
251 m_process->setIsInProcessCache(true);
252 m_evictionTimer.startOneShot(cachedProcessLifetime);
253}
254
255WebProcessCache::CachedProcess::~CachedProcess()
256{
257 if (!m_process)
258 return;
259
260 ASSERT(!m_process->pageCount());
261 ASSERT(!m_process->provisionalPageCount());
262 ASSERT(!m_process->suspendedPageCount());
263
264 m_process->setIsInProcessCache(false);
265 m_process->shutDown();
266}
267
268Ref<WebProcessProxy> WebProcessCache::CachedProcess::takeProcess()
269{
270 ASSERT(m_process);
271 m_evictionTimer.stop();
272 m_process->setIsInProcessCache(false);
273 return m_process.releaseNonNull();
274}
275
276void WebProcessCache::CachedProcess::evictionTimerFired()
277{
278 ASSERT(m_process);
279 m_process->processPool().webProcessCache().removeProcess(*m_process, ShouldShutDownProcess::Yes);
280}
281
282#if !PLATFORM(COCOA)
283void WebProcessCache::platformInitialize()
284{
285}
286#endif
287
288} // namespace WebKit
289