[PSON] Add mechanism to clear suspended pages while bypassing the WebProcess cache
[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 WebProcessCache::WebProcessCache(WebProcessPool& processPool)
41     : m_evictionTimer(RunLoop::main(), this, &WebProcessCache::clear)
42 {
43     updateCapacity(processPool);
44     platformInitialize();
45 }
46
47 bool WebProcessCache::addProcessIfPossible(const String& registrableDomain, Ref<WebProcessProxy>&& process)
48 {
49     ASSERT(!registrableDomain.isEmpty());
50     ASSERT(!process->pageCount());
51     ASSERT(!process->provisionalPageCount());
52     ASSERT(!process->suspendedPageCount());
53
54     if (!capacity())
55         return false;
56
57     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
58         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Not caching process %i because we are under memory pressure", this, process->processIdentifier());
59         return false;
60     }
61
62     if (m_processesPerRegistrableDomain.contains(registrableDomain)) {
63         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Not caching process %i because we already have a cached process for this domain", this, process->processIdentifier());
64         return false;
65     }
66
67     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Checking if process %i is responsive before caching it...", this, process->processIdentifier());
68     process->setIsInProcessCache(true);
69     process->isResponsive([process = process.copyRef(), processPool = makeRef(process->processPool()), registrableDomain](bool isResponsive) {
70         process->setIsInProcessCache(false);
71         if (!isResponsive) {
72             RELEASE_LOG_ERROR(ProcessSwapping, "%p - WebProcessCache::addProcessIfPossible(): Not caching process %i because it is not responsive", &process->processPool().webProcessCache(), process->processIdentifier());
73             process->shutDown();
74             return;
75         }
76         if (!processPool->webProcessCache().addProcess(registrableDomain, process.copyRef()))
77             process->shutDown();
78     });
79     return true;
80 }
81
82 bool WebProcessCache::addProcess(const String& registrableDomain, Ref<WebProcessProxy>&& process)
83 {
84     ASSERT(!process->pageCount());
85     ASSERT(!process->provisionalPageCount());
86     ASSERT(!process->suspendedPageCount());
87
88     if (!capacity())
89         return false;
90
91     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
92         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Not caching process %i because we are under memory pressure", this, process->processIdentifier());
93         return false;
94     }
95
96     while (m_processesPerRegistrableDomain.size() >= capacity()) {
97         auto it = m_processesPerRegistrableDomain.random();
98         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Evicting process %i from WebProcess cache", this, it->value->process().processIdentifier());
99         m_processesPerRegistrableDomain.remove(it);
100     }
101
102     auto addResult = m_processesPerRegistrableDomain.ensure(registrableDomain, [process = process.copyRef()]() mutable {
103         return std::make_unique<CachedProcess>(WTFMove(process));
104     });
105     if (!addResult.isNewEntry) {
106         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Not caching process %i because we already have a cached process for this domain", this, process->processIdentifier());
107         return false;
108     }
109
110     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess: Adding process %i to WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity());
111     return true;
112 }
113
114 RefPtr<WebProcessProxy> WebProcessCache::takeProcess(const String& registrableDomain, WebsiteDataStore& dataStore)
115 {
116     auto it = m_processesPerRegistrableDomain.find(registrableDomain);
117     if (it == m_processesPerRegistrableDomain.end())
118         return nullptr;
119
120     if (&it->value->process().websiteDataStore() != &dataStore)
121         return nullptr;
122
123     auto process = it->value->takeProcess();
124     m_processesPerRegistrableDomain.remove(it);
125     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::takeProcess: Taking process %i from WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity());
126
127     ASSERT(!process->pageCount());
128     ASSERT(!process->provisionalPageCount());
129     ASSERT(!process->suspendedPageCount());
130
131     return WTFMove(process);
132 }
133
134 void WebProcessCache::updateCapacity(WebProcessPool& processPool)
135 {
136     if (!processPool.configuration().processSwapsOnNavigation() || !processPool.configuration().usesWebProcessCache()) {
137         if (!processPool.configuration().processSwapsOnNavigation())
138             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because process swap on navigation is disabled", this);
139         else
140             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled by client", this);
141         m_capacity = 0;
142     } else {
143         size_t memorySize = ramSize() / GB;
144         if (memorySize < 3) {
145             m_capacity = 0;
146             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because device does not have enough RAM", this);
147         } else {
148             // Allow 4 processes in the cache per GB of RAM, up to 30 processes.
149             m_capacity = std::min<unsigned>(memorySize * 4, 30);
150             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache has a capacity of %u processes", this, capacity());
151         }
152     }
153
154     if (!m_capacity)
155         clear();
156 }
157
158 void WebProcessCache::clear()
159 {
160     if (m_processesPerRegistrableDomain.isEmpty())
161         return;
162
163     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clear() evicting %u processes", this, m_processesPerRegistrableDomain.size());
164     m_processesPerRegistrableDomain.clear();
165 }
166
167 void WebProcessCache::setApplicationIsActive(bool isActive)
168 {
169     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::setApplicationIsActive(%d)", this, isActive);
170     if (isActive)
171         m_evictionTimer.stop();
172     else if (!m_processesPerRegistrableDomain.isEmpty())
173         m_evictionTimer.startOneShot(clearingDelayAfterApplicationResignsActive);
174 }
175
176 void WebProcessCache::evictProcess(WebProcessProxy& process)
177 {
178     auto it = m_processesPerRegistrableDomain.find(process.registrableDomain());
179     ASSERT(it != m_processesPerRegistrableDomain.end());
180     ASSERT(&it->value->process() == &process);
181
182     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::evictProcess(): Evicting process %i from WebProcess cache because it expired", this, process.processIdentifier());
183
184     m_processesPerRegistrableDomain.remove(it);
185 }
186
187 WebProcessCache::CachedProcess::CachedProcess(Ref<WebProcessProxy>&& process)
188     : m_process(WTFMove(process))
189     , m_evictionTimer(RunLoop::main(), this, &CachedProcess::evictionTimerFired)
190 {
191     m_process->setIsInProcessCache(true);
192     m_evictionTimer.startOneShot(cachedProcessLifetime);
193 }
194
195 WebProcessCache::CachedProcess::~CachedProcess()
196 {
197     if (!m_process)
198         return;
199
200     ASSERT(!m_process->pageCount());
201     ASSERT(!m_process->provisionalPageCount());
202     ASSERT(!m_process->suspendedPageCount());
203
204     m_process->setIsInProcessCache(false);
205     m_process->shutDown();
206 }
207
208 Ref<WebProcessProxy> WebProcessCache::CachedProcess::takeProcess()
209 {
210     ASSERT(m_process);
211     m_evictionTimer.stop();
212     m_process->setIsInProcessCache(false);
213     return m_process.releaseNonNull();
214 }
215
216 void WebProcessCache::CachedProcess::evictionTimerFired()
217 {
218     ASSERT(m_process);
219     m_process->processPool().webProcessCache().evictProcess(*m_process);
220 }
221
222 #if !PLATFORM(COCOA)
223 void WebProcessCache::platformInitialize()
224 {
225 }
226 #endif
227
228 } // namespace WebKit