Unreviewed, fix assertion introduced in r245339.
[WebKit-https.git] / Source / WebKit / UIProcess / WebProcessCache.cpp
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
35 namespace WebKit {
36
37 Seconds WebProcessCache::cachedProcessLifetime { 30_min };
38 Seconds WebProcessCache::clearingDelayAfterApplicationResignsActive { 5_min };
39
40 static uint64_t generateAddRequestIdentifier()
41 {
42     static uint64_t identifier = 0;
43     return ++identifier;
44 }
45
46 WebProcessCache::WebProcessCache(WebProcessPool& processPool)
47     : m_evictionTimer(RunLoop::main(), this, &WebProcessCache::clear)
48 {
49     updateCapacity(processPool);
50     platformInitialize();
51 }
52
53 bool 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
77 bool 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
104 bool 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
131 RefPtr<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
151 void 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
175 void 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
185 void 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
208 void 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
217 void 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
245 WebProcessCache::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
255 WebProcessCache::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
268 Ref<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
276 void WebProcessCache::CachedProcess::evictionTimerFired()
277 {
278     ASSERT(m_process);
279     m_process->processPool().webProcessCache().removeProcess(*m_process, ShouldShutDownProcess::Yes);
280 }
281
282 #if !PLATFORM(COCOA)
283 void WebProcessCache::platformInitialize()
284 {
285 }
286 #endif
287
288 } // namespace WebKit