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