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