1/*
2 * Copyright (C) 2011-2017 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include <wtf/MemoryPressureHandler.h>
28
29#include <wtf/MemoryFootprint.h>
30#include <wtf/NeverDestroyed.h>
31#include <wtf/RAMSize.h>
32
33#define LOG_CHANNEL_PREFIX Log
34
35namespace WTF {
36
37#if RELEASE_LOG_DISABLED
38WTFLogChannel LogMemoryPressure = { WTFLogChannelState::On, "MemoryPressure", WTFLogLevel::Error };
39#else
40WTFLogChannel LogMemoryPressure = { WTFLogChannelState::On, "MemoryPressure", WTFLogLevel::Error, LOG_CHANNEL_WEBKIT_SUBSYSTEM, OS_LOG_DEFAULT };
41#endif
42
43WTF_EXPORT_PRIVATE bool MemoryPressureHandler::ReliefLogger::s_loggingEnabled = false;
44
45MemoryPressureHandler& MemoryPressureHandler::singleton()
46{
47 static NeverDestroyed<MemoryPressureHandler> memoryPressureHandler;
48 return memoryPressureHandler;
49}
50
51MemoryPressureHandler::MemoryPressureHandler()
52#if OS(LINUX)
53 : m_holdOffTimer(RunLoop::main(), this, &MemoryPressureHandler::holdOffTimerFired)
54#elif OS(WINDOWS)
55 : m_windowsMeasurementTimer(RunLoop::main(), this, &MemoryPressureHandler::windowsMeasurementTimerFired)
56#endif
57{
58#if PLATFORM(COCOA)
59 setDispatchQueue(dispatch_get_main_queue());
60#endif
61}
62
63void MemoryPressureHandler::setShouldUsePeriodicMemoryMonitor(bool use)
64{
65 if (!isFastMallocEnabled()) {
66 // If we're running with FastMalloc disabled, some kind of testing or debugging is probably happening.
67 // Let's be nice and not enable the memory kill mechanism.
68 return;
69 }
70
71 if (use) {
72 m_measurementTimer = std::make_unique<RunLoop::Timer<MemoryPressureHandler>>(RunLoop::main(), this, &MemoryPressureHandler::measurementTimerFired);
73 m_measurementTimer->startRepeating(30_s);
74 } else
75 m_measurementTimer = nullptr;
76}
77
78#if !RELEASE_LOG_DISABLED
79static const char* toString(MemoryUsagePolicy policy)
80{
81 switch (policy) {
82 case MemoryUsagePolicy::Unrestricted: return "Unrestricted";
83 case MemoryUsagePolicy::Conservative: return "Conservative";
84 case MemoryUsagePolicy::Strict: return "Strict";
85 }
86}
87#endif
88
89static size_t thresholdForMemoryKillWithProcessState(WebsamProcessState processState, unsigned tabCount)
90{
91 size_t baseThreshold = 2 * GB;
92#if CPU(X86_64) || CPU(ARM64)
93 if (processState == WebsamProcessState::Active)
94 baseThreshold = 4 * GB;
95 if (tabCount > 1)
96 baseThreshold += std::min(tabCount - 1, 4u) * 1 * GB;
97#else
98 if ((tabCount > 1) || (processState == WebsamProcessState::Active))
99 baseThreshold = 3 * GB;
100#endif
101 return std::min(baseThreshold, static_cast<size_t>(ramSize() * 0.9));
102}
103
104void MemoryPressureHandler::setPageCount(unsigned pageCount)
105{
106 if (singleton().m_pageCount == pageCount)
107 return;
108 singleton().m_pageCount = pageCount;
109}
110
111size_t MemoryPressureHandler::thresholdForMemoryKill()
112{
113 return thresholdForMemoryKillWithProcessState(m_processState, m_pageCount);
114}
115
116static size_t thresholdForPolicy(MemoryUsagePolicy policy)
117{
118 const size_t baseThresholdForPolicy = std::min(3 * GB, ramSize());
119
120#if PLATFORM(IOS_FAMILY)
121 const double conservativeThresholdFraction = 0.5;
122 const double strictThresholdFraction = 0.65;
123#else
124 const double conservativeThresholdFraction = 0.33;
125 const double strictThresholdFraction = 0.5;
126#endif
127
128 switch (policy) {
129 case MemoryUsagePolicy::Unrestricted:
130 return 0;
131 case MemoryUsagePolicy::Conservative:
132 return baseThresholdForPolicy * conservativeThresholdFraction;
133 case MemoryUsagePolicy::Strict:
134 return baseThresholdForPolicy * strictThresholdFraction;
135 default:
136 ASSERT_NOT_REACHED();
137 return 0;
138 }
139}
140
141static MemoryUsagePolicy policyForFootprint(size_t footprint)
142{
143 if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Strict))
144 return MemoryUsagePolicy::Strict;
145 if (footprint >= thresholdForPolicy(MemoryUsagePolicy::Conservative))
146 return MemoryUsagePolicy::Conservative;
147 return MemoryUsagePolicy::Unrestricted;
148}
149
150MemoryUsagePolicy MemoryPressureHandler::currentMemoryUsagePolicy()
151{
152 return policyForFootprint(memoryFootprint());
153}
154
155void MemoryPressureHandler::shrinkOrDie()
156{
157 RELEASE_LOG(MemoryPressure, "Process is above the memory kill threshold. Trying to shrink down.");
158 releaseMemory(Critical::Yes, Synchronous::Yes);
159
160 size_t footprint = memoryFootprint();
161 RELEASE_LOG(MemoryPressure, "New memory footprint: %zu MB", footprint / MB);
162
163 if (footprint < thresholdForMemoryKill()) {
164 RELEASE_LOG(MemoryPressure, "Shrank below memory kill threshold. Process gets to live.");
165 setMemoryUsagePolicyBasedOnFootprint(footprint);
166 return;
167 }
168
169 WTFLogAlways("Unable to shrink memory footprint of process (%zu MB) below the kill thresold (%zu MB). Killed\n", footprint / MB, thresholdForMemoryKill() / MB);
170 RELEASE_ASSERT(m_memoryKillCallback);
171 m_memoryKillCallback();
172}
173
174void MemoryPressureHandler::setMemoryUsagePolicyBasedOnFootprint(size_t footprint)
175{
176 auto newPolicy = policyForFootprint(footprint);
177 if (newPolicy == m_memoryUsagePolicy)
178 return;
179
180 RELEASE_LOG(MemoryPressure, "Memory usage policy changed: %s -> %s", toString(m_memoryUsagePolicy), toString(newPolicy));
181 m_memoryUsagePolicy = newPolicy;
182 memoryPressureStatusChanged();
183}
184
185void MemoryPressureHandler::measurementTimerFired()
186{
187 size_t footprint = memoryFootprint();
188 RELEASE_LOG(MemoryPressure, "Current memory footprint: %zu MB", footprint / MB);
189 if (footprint >= thresholdForMemoryKill()) {
190 shrinkOrDie();
191 return;
192 }
193
194 setMemoryUsagePolicyBasedOnFootprint(footprint);
195
196 switch (m_memoryUsagePolicy) {
197 case MemoryUsagePolicy::Unrestricted:
198 break;
199 case MemoryUsagePolicy::Conservative:
200 releaseMemory(Critical::No, Synchronous::No);
201 break;
202 case MemoryUsagePolicy::Strict:
203 releaseMemory(Critical::Yes, Synchronous::No);
204 break;
205 }
206
207 if (processState() == WebsamProcessState::Active && footprint > thresholdForMemoryKillWithProcessState(WebsamProcessState::Inactive, m_pageCount))
208 doesExceedInactiveLimitWhileActive();
209 else
210 doesNotExceedInactiveLimitWhileActive();
211}
212
213void MemoryPressureHandler::doesExceedInactiveLimitWhileActive()
214{
215 if (m_hasInvokedDidExceedInactiveLimitWhileActiveCallback)
216 return;
217 if (m_didExceedInactiveLimitWhileActiveCallback)
218 m_didExceedInactiveLimitWhileActiveCallback();
219 m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = true;
220}
221
222void MemoryPressureHandler::doesNotExceedInactiveLimitWhileActive()
223{
224 m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = false;
225}
226
227void MemoryPressureHandler::setProcessState(WebsamProcessState state)
228{
229 if (m_processState == state)
230 return;
231 m_processState = state;
232}
233
234void MemoryPressureHandler::beginSimulatedMemoryPressure()
235{
236 if (m_isSimulatingMemoryPressure)
237 return;
238 m_isSimulatingMemoryPressure = true;
239 memoryPressureStatusChanged();
240 respondToMemoryPressure(Critical::Yes, Synchronous::Yes);
241}
242
243void MemoryPressureHandler::endSimulatedMemoryPressure()
244{
245 if (!m_isSimulatingMemoryPressure)
246 return;
247 m_isSimulatingMemoryPressure = false;
248 memoryPressureStatusChanged();
249}
250
251void MemoryPressureHandler::releaseMemory(Critical critical, Synchronous synchronous)
252{
253 if (!m_lowMemoryHandler)
254 return;
255
256 ReliefLogger log("Total");
257 m_lowMemoryHandler(critical, synchronous);
258 platformReleaseMemory(critical);
259}
260
261void MemoryPressureHandler::setUnderMemoryPressure(bool underMemoryPressure)
262{
263 if (m_underMemoryPressure == underMemoryPressure)
264 return;
265 m_underMemoryPressure = underMemoryPressure;
266 memoryPressureStatusChanged();
267}
268
269void MemoryPressureHandler::memoryPressureStatusChanged()
270{
271 if (m_memoryPressureStatusChangedCallback)
272 m_memoryPressureStatusChangedCallback(isUnderMemoryPressure());
273}
274
275void MemoryPressureHandler::ReliefLogger::logMemoryUsageChange()
276{
277#if !RELEASE_LOG_DISABLED
278#define STRING_SPECIFICATION "%{public}s"
279#define MEMORYPRESSURE_LOG(...) RELEASE_LOG(MemoryPressure, __VA_ARGS__)
280#else
281#define STRING_SPECIFICATION "%s"
282#define MEMORYPRESSURE_LOG(...) WTFLogAlways(__VA_ARGS__)
283#endif
284
285 auto currentMemory = platformMemoryUsage();
286 if (!currentMemory || !m_initialMemory) {
287 MEMORYPRESSURE_LOG("Memory pressure relief: " STRING_SPECIFICATION ": (Unable to get dirty memory information for process)", m_logString);
288 return;
289 }
290
291 long residentDiff = currentMemory->resident - m_initialMemory->resident;
292 long physicalDiff = currentMemory->physical - m_initialMemory->physical;
293
294 MEMORYPRESSURE_LOG("Memory pressure relief: " STRING_SPECIFICATION ": res = %zu/%zu/%ld, res+swap = %zu/%zu/%ld",
295 m_logString,
296 m_initialMemory->resident, currentMemory->resident, residentDiff,
297 m_initialMemory->physical, currentMemory->physical, physicalDiff);
298}
299
300#if !OS(WINDOWS)
301void MemoryPressureHandler::platformInitialize() { }
302#endif
303
304#if PLATFORM(COCOA)
305void MemoryPressureHandler::setDispatchQueue(dispatch_queue_t queue)
306{
307 RELEASE_ASSERT(!m_installed);
308 dispatch_retain(queue);
309 if (m_dispatchQueue)
310 dispatch_release(m_dispatchQueue);
311 m_dispatchQueue = queue;
312}
313#endif
314
315} // namespace WebCore
316