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