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