[PSON] Introduce a WebContent Process 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 static Seconds cachedProcessLifetime { 30_min };
38 static Seconds clearingDelayAfterApplicationResignsActive { 5_min };
39
40 WebProcessCache::WebProcessCache(WebProcessPool& processPool)
41     : m_evictionTimer(RunLoop::main(), this, &WebProcessCache::clear)
42 {
43     updateCapacity(processPool);
44 }
45
46 bool WebProcessCache::addProcess(const String& registrableDomain, Ref<WebProcessProxy>&& process)
47 {
48     ASSERT(!registrableDomain.isEmpty());
49     ASSERT(!process->pageCount());
50     ASSERT(!process->provisionalPageCount());
51     ASSERT(!process->processPool().hasSuspendedPageFor(process));
52
53     if (!capacity())
54         return false;
55
56     if (MemoryPressureHandler::singleton().isUnderMemoryPressure()) {
57         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Not caching process %i because we are under memory pressure", this, process->processIdentifier());
58         return false;
59     }
60
61     while (m_processesPerRegistrableDomain.size() >= capacity()) {
62         auto it = m_processesPerRegistrableDomain.random();
63         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess(): Evicting process %i from WebProcess cache", this, it->value->process().processIdentifier());
64         m_processesPerRegistrableDomain.remove(it);
65     }
66
67     auto addResult = m_processesPerRegistrableDomain.ensure(registrableDomain, [process = process.copyRef()]() mutable {
68         return std::make_unique<CachedProcess>(WTFMove(process));
69     });
70     if (!addResult.isNewEntry)
71         return false;
72
73     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::addProcess: Adding process %i to WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity());
74     return true;
75 }
76
77 RefPtr<WebProcessProxy> WebProcessCache::takeProcess(const String& registrableDomain, WebsiteDataStore& dataStore)
78 {
79     auto it = m_processesPerRegistrableDomain.find(registrableDomain);
80     if (it == m_processesPerRegistrableDomain.end())
81         return nullptr;
82
83     if (&it->value->process().websiteDataStore() != &dataStore)
84         return nullptr;
85
86     auto process = it->value->takeProcess();
87     m_processesPerRegistrableDomain.remove(it);
88     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::takeProcess: Taking process %i from WebProcess cache, cache size: [%u / %u]", this, process->processIdentifier(), size(), capacity());
89
90     ASSERT(!process->pageCount());
91     ASSERT(!process->provisionalPageCount());
92     ASSERT(!process->processPool().hasSuspendedPageFor(process));
93
94     return WTFMove(process);
95 }
96
97 void WebProcessCache::updateCapacity(WebProcessPool& processPool)
98 {
99     if (!processPool.configuration().processSwapsOnNavigation() || !processPool.configuration().usesWebProcessCache()) {
100         if (!processPool.configuration().processSwapsOnNavigation())
101             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled because process swap on navigation is disabled", this);
102         else
103             RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache is disabled by client", this);
104         m_capacity = 0;
105     } else {
106         size_t memorySize = ramSize() / GB;
107
108         // Allow 4 processes in the cache per GB of RAM, up to 30 processes.
109         m_capacity = std::min<unsigned>(memorySize * 4, 30);
110         RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::updateCapacity: Cache has a capacity of %u processes", this, capacity());
111     }
112
113     if (!m_capacity)
114         clear();
115 }
116
117 void WebProcessCache::clear()
118 {
119     if (m_processesPerRegistrableDomain.isEmpty())
120         return;
121
122     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::clear() evicting %u processes", this, m_processesPerRegistrableDomain.size());
123     m_processesPerRegistrableDomain.clear();
124 }
125
126 void WebProcessCache::setApplicationIsActive(bool isActive)
127 {
128     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::setApplicationIsActive(%d)", this, isActive);
129     if (isActive)
130         m_evictionTimer.stop();
131     else if (!m_processesPerRegistrableDomain.isEmpty())
132         m_evictionTimer.startOneShot(clearingDelayAfterApplicationResignsActive);
133 }
134
135 void WebProcessCache::evictProcess(WebProcessProxy& process)
136 {
137     auto it = m_processesPerRegistrableDomain.find(process.registrableDomain());
138     ASSERT(it != m_processesPerRegistrableDomain.end());
139     ASSERT(&it->value->process() == &process);
140
141     RELEASE_LOG(ProcessSwapping, "%p - WebProcessCache::evictProcess(): Evicting process %i from WebProcess cache because it expired", this, process.processIdentifier());
142
143     m_processesPerRegistrableDomain.remove(it);
144 }
145
146 WebProcessCache::CachedProcess::CachedProcess(Ref<WebProcessProxy>&& process)
147     : m_process(WTFMove(process))
148     , m_evictionTimer(RunLoop::main(), this, &CachedProcess::evictionTimerFired)
149 {
150     m_process->setIsInProcessCache(true);
151     m_evictionTimer.startOneShot(cachedProcessLifetime);
152 }
153
154 WebProcessCache::CachedProcess::~CachedProcess()
155 {
156     if (!m_process)
157         return;
158
159     ASSERT(!m_process->pageCount());
160     ASSERT(!m_process->provisionalPageCount());
161     ASSERT(!m_process->processPool().hasSuspendedPageFor(*m_process));
162
163     m_process->setIsInProcessCache(false);
164     m_process->shutDown();
165 }
166
167 Ref<WebProcessProxy> WebProcessCache::CachedProcess::takeProcess()
168 {
169     ASSERT(m_process);
170     m_evictionTimer.stop();
171     m_process->setIsInProcessCache(false);
172     return m_process.releaseNonNull();
173 }
174
175 void WebProcessCache::CachedProcess::evictionTimerFired()
176 {
177     ASSERT(m_process);
178     m_process->processPool().webProcessCache().evictProcess(*m_process);
179 }
180
181 } // namespace WebKit