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 | |
35 | namespace WTF { |
36 | |
37 | #if RELEASE_LOG_DISABLED |
38 | WTFLogChannel LogMemoryPressure = { WTFLogChannelState::On, "MemoryPressure" , WTFLogLevel::Error }; |
39 | #else |
40 | WTFLogChannel LogMemoryPressure = { WTFLogChannelState::On, "MemoryPressure" , WTFLogLevel::Error, LOG_CHANNEL_WEBKIT_SUBSYSTEM, OS_LOG_DEFAULT }; |
41 | #endif |
42 | |
43 | WTF_EXPORT_PRIVATE bool MemoryPressureHandler::ReliefLogger::s_loggingEnabled = false; |
44 | |
45 | MemoryPressureHandler& MemoryPressureHandler::singleton() |
46 | { |
47 | static NeverDestroyed<MemoryPressureHandler> memoryPressureHandler; |
48 | return memoryPressureHandler; |
49 | } |
50 | |
51 | MemoryPressureHandler::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 | |
63 | void 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 = makeUnique<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 |
79 | static 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 | |
89 | static 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 | |
104 | void MemoryPressureHandler::setPageCount(unsigned pageCount) |
105 | { |
106 | if (singleton().m_pageCount == pageCount) |
107 | return; |
108 | singleton().m_pageCount = pageCount; |
109 | } |
110 | |
111 | size_t MemoryPressureHandler::thresholdForMemoryKill() |
112 | { |
113 | return thresholdForMemoryKillWithProcessState(m_processState, m_pageCount); |
114 | } |
115 | |
116 | static 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 | |
141 | static MemoryUsagePolicy (size_t ) |
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 | |
150 | MemoryUsagePolicy MemoryPressureHandler::currentMemoryUsagePolicy() |
151 | { |
152 | return policyForFootprint(memoryFootprint()); |
153 | } |
154 | |
155 | void 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 = 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 | |
174 | void MemoryPressureHandler::setMemoryUsagePolicyBasedOnFootprint(size_t ) |
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 | |
185 | void MemoryPressureHandler::measurementTimerFired() |
186 | { |
187 | size_t = 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 | |
213 | void MemoryPressureHandler::doesExceedInactiveLimitWhileActive() |
214 | { |
215 | if (m_hasInvokedDidExceedInactiveLimitWhileActiveCallback) |
216 | return; |
217 | if (m_didExceedInactiveLimitWhileActiveCallback) |
218 | m_didExceedInactiveLimitWhileActiveCallback(); |
219 | m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = true; |
220 | } |
221 | |
222 | void MemoryPressureHandler::doesNotExceedInactiveLimitWhileActive() |
223 | { |
224 | m_hasInvokedDidExceedInactiveLimitWhileActiveCallback = false; |
225 | } |
226 | |
227 | void MemoryPressureHandler::setProcessState(WebsamProcessState state) |
228 | { |
229 | if (m_processState == state) |
230 | return; |
231 | m_processState = state; |
232 | } |
233 | |
234 | void MemoryPressureHandler::beginSimulatedMemoryPressure() |
235 | { |
236 | if (m_isSimulatingMemoryPressure) |
237 | return; |
238 | m_isSimulatingMemoryPressure = true; |
239 | memoryPressureStatusChanged(); |
240 | respondToMemoryPressure(Critical::Yes, Synchronous::Yes); |
241 | } |
242 | |
243 | void MemoryPressureHandler::endSimulatedMemoryPressure() |
244 | { |
245 | if (!m_isSimulatingMemoryPressure) |
246 | return; |
247 | m_isSimulatingMemoryPressure = false; |
248 | memoryPressureStatusChanged(); |
249 | } |
250 | |
251 | void 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 | |
261 | void MemoryPressureHandler::setUnderMemoryPressure(bool underMemoryPressure) |
262 | { |
263 | if (m_underMemoryPressure == underMemoryPressure) |
264 | return; |
265 | m_underMemoryPressure = underMemoryPressure; |
266 | memoryPressureStatusChanged(); |
267 | } |
268 | |
269 | void MemoryPressureHandler::memoryPressureStatusChanged() |
270 | { |
271 | if (m_memoryPressureStatusChangedCallback) |
272 | m_memoryPressureStatusChangedCallback(isUnderMemoryPressure()); |
273 | } |
274 | |
275 | void 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) |
301 | void MemoryPressureHandler::platformInitialize() { } |
302 | #endif |
303 | |
304 | #if PLATFORM(COCOA) |
305 | void 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 | |