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