1ee3b8f042c7142bbff50b68d6523ac259e05299
[WebKit-https.git] / Source / WebCore / platform / cocoa / MemoryPressureHandlerCocoa.mm
1 /*
2  * Copyright (C) 2011-2015 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 #import "config.h"
27 #import "MemoryPressureHandler.h"
28
29 #import "DispatchSPI.h"
30 #import "IOSurfacePool.h"
31 #import "GCController.h"
32 #import "JSDOMWindow.h"
33 #import "JSDOMWindowBase.h"
34 #import "LayerPool.h"
35 #import "Logging.h"
36 #import "WebCoreSystemInterface.h"
37 #import <JavaScriptCore/IncrementalSweeper.h>
38 #import <mach/mach.h>
39 #import <mach/task_info.h>
40 #import <malloc/malloc.h>
41 #import <notify.h>
42 #import <wtf/CurrentTime.h>
43
44 #if PLATFORM(IOS)
45 #import "SystemMemory.h"
46 #import "WebCoreThread.h"
47 #endif
48
49 extern "C" void cache_simulate_memory_warning_event(uint64_t);
50 extern "C" void _sqlite3_purgeEligiblePagerCacheMemory(void);
51
52 namespace WebCore {
53
54 void MemoryPressureHandler::platformReleaseMemory(bool critical)
55 {
56     {
57         ReliefLogger log("Purging SQLite caches");
58         _sqlite3_purgeEligiblePagerCacheMemory();
59     }
60
61     {
62         ReliefLogger log("Drain LayerPools");
63         for (auto& pool : LayerPool::allLayerPools())
64             pool->drain();
65     }
66 #if USE(IOSURFACE)
67     {
68         ReliefLogger log("Drain IOSurfacePool");
69         IOSurfacePool::sharedPool().discardAllSurfaces();
70     }
71 #endif
72
73 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
74     if (critical && !isUnderMemoryPressure()) {
75         // libcache listens to OS memory notifications, but for process suspension
76         // or memory pressure simulation, we need to prod it manually:
77         ReliefLogger log("Purging libcache caches");
78         cache_simulate_memory_warning_event(DISPATCH_MEMORYPRESSURE_CRITICAL);
79     }
80 #else
81     UNUSED_PARAM(critical);
82 #endif
83
84 #if PLATFORM(IOS)
85     if (isUnderMemoryPressure()) {
86         gcController().garbageCollectSoon();
87     } else {
88         // If we're not under memory pressure, that means we're here due to impending process suspension.
89         // Do a full GC since this is our last chance to run any code.
90         ReliefLogger log("Collecting JavaScript garbage");
91         gcController().garbageCollectNow();
92     }
93
94     // Do a full sweep of collected objects.
95     {
96         ReliefLogger log("Full JavaScript garbage sweep");
97         JSC::JSLockHolder lock(JSDOMWindow::commonVM());
98         JSDOMWindow::commonVM().heap.sweeper()->fullSweep();
99     }
100 #endif
101 }
102
103 static dispatch_source_t _cache_event_source = 0;
104 static dispatch_source_t _timer_event_source = 0;
105 static int _notifyToken;
106
107 // Disable memory event reception for a minimum of s_minimumHoldOffTime
108 // seconds after receiving an event.  Don't let events fire any sooner than
109 // s_holdOffMultiplier times the last cleanup processing time.  Effectively 
110 // this is 1 / s_holdOffMultiplier percent of the time.
111 // These value seems reasonable and testing verifies that it throttles frequent
112 // low memory events, greatly reducing CPU usage.
113 static const unsigned s_minimumHoldOffTime = 5;
114 #if !PLATFORM(IOS)
115 static const unsigned s_holdOffMultiplier = 20;
116 #endif
117
118 void MemoryPressureHandler::install()
119 {
120     if (m_installed || _timer_event_source)
121         return;
122
123     dispatch_async(dispatch_get_main_queue(), ^{
124 #if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
125         _cache_event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYSTATUS, 0, DISPATCH_MEMORYSTATUS_PRESSURE_NORMAL | DISPATCH_MEMORYSTATUS_PRESSURE_WARN | DISPATCH_MEMORYSTATUS_PRESSURE_CRITICAL, dispatch_get_main_queue());
126 #elif PLATFORM(MAC)
127         _cache_event_source = wkCreateMemoryStatusPressureCriticalDispatchOnMainQueue();
128 #else
129         _cache_event_source = wkCreateVMPressureDispatchOnMainQueue();
130 #endif
131         if (_cache_event_source) {
132             dispatch_set_context(_cache_event_source, this);
133             dispatch_source_set_event_handler(_cache_event_source, ^{
134                 bool critical = true;
135 #if PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000
136                 unsigned long status = dispatch_source_get_data(_cache_event_source);
137                 critical = status == DISPATCH_MEMORYPRESSURE_CRITICAL;
138                 auto& memoryPressureHandler = MemoryPressureHandler::singleton();
139                 bool wasCritical = memoryPressureHandler.isUnderMemoryPressure();
140                 memoryPressureHandler.setUnderMemoryPressure(critical);
141                 if (status == DISPATCH_MEMORYSTATUS_PRESSURE_NORMAL) {
142                     if (ReliefLogger::loggingEnabled())
143                         NSLog(@"System is no longer under (%s) memory pressure.", wasCritical ? "critical" : "non-critical");
144                     return;
145                 }
146
147                 if (ReliefLogger::loggingEnabled())
148                     NSLog(@"Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
149 #endif
150                 MemoryPressureHandler::singleton().respondToMemoryPressure(critical);
151             });
152             dispatch_resume(_cache_event_source);
153         }
154     });
155
156     // Allow simulation of memory pressure with "notifyutil -p org.WebKit.lowMemory"
157     notify_register_dispatch("org.WebKit.lowMemory", &_notifyToken, dispatch_get_main_queue(), ^(int) {
158         MemoryPressureHandler::singleton().respondToMemoryPressure(true);
159
160         // We only do a synchronous GC when *simulating* memory pressure.
161         // This gives us a more consistent picture of live objects at the end of testing.
162         gcController().garbageCollectNow();
163
164         WTF::releaseFastMallocFreeMemory();
165
166         malloc_zone_pressure_relief(nullptr, 0);
167     });
168
169     m_installed = true;
170 }
171
172 void MemoryPressureHandler::uninstall()
173 {
174     if (!m_installed)
175         return;
176
177     dispatch_async(dispatch_get_main_queue(), ^{
178         if (_cache_event_source) {
179             dispatch_source_cancel(_cache_event_source);
180             dispatch_release(_cache_event_source);
181             _cache_event_source = 0;
182         }
183
184         if (_timer_event_source) {
185             dispatch_source_cancel(_timer_event_source);
186             dispatch_release(_timer_event_source);
187             _timer_event_source = 0;
188         }
189     });
190
191     m_installed = false;
192     
193     notify_cancel(_notifyToken);
194 }
195
196 void MemoryPressureHandler::holdOff(unsigned seconds)
197 {
198     dispatch_async(dispatch_get_main_queue(), ^{
199         _timer_event_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
200         if (_timer_event_source) {
201             dispatch_set_context(_timer_event_source, this);
202             dispatch_source_set_timer(_timer_event_source, dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1 * s_minimumHoldOffTime);
203             dispatch_source_set_event_handler(_timer_event_source, ^{
204                 if (_timer_event_source) {
205                     dispatch_source_cancel(_timer_event_source);
206                     dispatch_release(_timer_event_source);
207                     _timer_event_source = 0;
208                 }
209                 MemoryPressureHandler::singleton().install();
210             });
211             dispatch_resume(_timer_event_source);
212         }
213     });
214 }
215
216 void MemoryPressureHandler::respondToMemoryPressure(bool critical)
217 {
218 #if !PLATFORM(IOS)
219     uninstall();
220     double startTime = monotonicallyIncreasingTime();
221 #endif
222
223     m_lowMemoryHandler(critical);
224
225 #if !PLATFORM(IOS)
226     unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
227     holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
228 #endif
229 }
230
231 size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
232 {
233     // Flush free memory back to the OS before every measurement.
234     // Note that this code only runs when detailed pressure relief logging is enabled.
235     WTF::releaseFastMallocFreeMemory();
236     malloc_zone_pressure_relief(nullptr, 0);
237
238     task_vm_info_data_t vmInfo;
239     mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
240     kern_return_t err = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
241     if (err != KERN_SUCCESS)
242         return static_cast<size_t>(-1);
243
244     return static_cast<size_t>(vmInfo.internal);
245 }
246
247 void MemoryPressureHandler::ReliefLogger::platformLog()
248 {
249     size_t currentMemory = platformMemoryUsage();
250     if (currentMemory == static_cast<size_t>(-1) || m_initialMemory == static_cast<size_t>(-1)) {
251         NSLog(@"%s (Unable to get dirty memory information for process)\n", m_logString);
252         return;
253     }
254
255     ssize_t memoryDiff = currentMemory - m_initialMemory;
256     if (memoryDiff < 0)
257         NSLog(@"Pressure relief: %s: -dirty %ld bytes (from %ld to %ld)\n", m_logString, (memoryDiff * -1), m_initialMemory, currentMemory);
258     else if (memoryDiff > 0)
259         NSLog(@"Pressure relief: %s: +dirty %ld bytes (from %ld to %ld)\n", m_logString, memoryDiff, m_initialMemory, currentMemory);
260     else
261         NSLog(@"Pressure relief: %s: =dirty (at %ld bytes)\n", m_logString, currentMemory);
262 }
263
264 #if PLATFORM(IOS)
265 static void respondToMemoryPressureCallback(CFRunLoopObserverRef observer, CFRunLoopActivity /*activity*/, void* /*info*/)
266 {
267     MemoryPressureHandler::singleton().respondToMemoryPressureIfNeeded();
268     CFRunLoopObserverInvalidate(observer);
269     CFRelease(observer);
270 }
271
272 void MemoryPressureHandler::installMemoryReleaseBlock(void (^releaseMemoryBlock)(), bool clearPressureOnMemoryRelease)
273 {
274     if (m_installed)
275         return;
276     m_releaseMemoryBlock = Block_copy(releaseMemoryBlock);
277     m_clearPressureOnMemoryRelease = clearPressureOnMemoryRelease;
278     m_installed = true;
279 }
280
281 void MemoryPressureHandler::setReceivedMemoryPressure(MemoryPressureReason reason)
282 {
283     m_underMemoryPressure = true;
284
285     {
286         MutexLocker locker(m_observerMutex);
287         if (!m_observer) {
288             m_observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting | kCFRunLoopExit, NO /* don't repeat */,
289                 0, WebCore::respondToMemoryPressureCallback, NULL);
290             CFRunLoopAddObserver(WebThreadRunLoop(), m_observer, kCFRunLoopCommonModes);
291             CFRunLoopWakeUp(WebThreadRunLoop());
292         }
293         m_memoryPressureReason |= reason;
294     }
295 }
296
297 void MemoryPressureHandler::clearMemoryPressure()
298 {
299     m_underMemoryPressure = false;
300
301     {
302         MutexLocker locker(m_observerMutex);
303         m_memoryPressureReason = MemoryPressureReasonNone;
304     }
305 }
306
307 bool MemoryPressureHandler::shouldWaitForMemoryClearMessage()
308 {
309     MutexLocker locker(m_observerMutex);
310     return m_memoryPressureReason & MemoryPressureReasonVMStatus;
311 }
312
313 void MemoryPressureHandler::respondToMemoryPressureIfNeeded()
314 {
315     ASSERT(WebThreadIsLockedOrDisabled());
316
317     {
318         MutexLocker locker(m_observerMutex);
319         m_observer = 0;
320     }
321
322     if (isUnderMemoryPressure()) {
323         ASSERT(m_releaseMemoryBlock);
324         LOG(MemoryPressure, "Handle memory pressure at %s", __PRETTY_FUNCTION__);
325         m_releaseMemoryBlock();
326         if (m_clearPressureOnMemoryRelease)
327             clearMemoryPressure();
328     }
329 }
330
331 #endif
332
333 } // namespace WebCore