2 * Copyright (C) 2011, 2012 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2014 Raspberry Pi Foundation. All Rights Reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "MemoryPressureHandler.h"
32 #include "CurrentProcessMemoryStatus.h"
38 #include <sys/eventfd.h>
40 #include <sys/types.h>
42 #include <wtf/CurrentTime.h>
43 #include <wtf/MainThread.h>
44 #include <wtf/text/WTFString.h>
47 #include <glib-unix.h>
52 // Disable memory event reception for a minimum of s_minimumHoldOffTime
53 // seconds after receiving an event. Don't let events fire any sooner than
54 // s_holdOffMultiplier times the last cleanup processing time. Effectively
55 // this is 1 / s_holdOffMultiplier percent of the time.
56 // These value seems reasonable and testing verifies that it throttles frequent
57 // low memory events, greatly reducing CPU usage.
58 static const unsigned s_minimumHoldOffTime = 5;
59 static const unsigned s_holdOffMultiplier = 20;
61 static const char* s_cgroupMemoryPressureLevel = "/sys/fs/cgroup/memory/memory.pressure_level";
62 static const char* s_cgroupEventControl = "/sys/fs/cgroup/memory/cgroup.event_control";
68 GIOCondition condition;
71 static const unsigned eventFDSourceCondition = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
73 static GSourceFuncs eventFDSourceFunctions = {
77 [](GSource* source, GSourceFunc callback, gpointer userData) -> gboolean
79 EventFDSource* eventFDSource = reinterpret_cast<EventFDSource*>(source);
80 unsigned events = g_source_query_unix_fd(source, eventFDSource->fdTag) & eventFDSourceCondition;
81 if (events & G_IO_HUP || events & G_IO_ERR || events & G_IO_NVAL)
82 return G_SOURCE_REMOVE;
84 gboolean returnValue = G_SOURCE_CONTINUE;
86 returnValue = callback(userData);
87 g_source_set_ready_time(source, -1);
91 nullptr, // closure_callback
92 nullptr, // closure_marshall
96 MemoryPressureHandler::EventFDPoller::EventFDPoller(int fd, std::function<void ()>&& notifyHandler)
98 , m_notifyHandler(WTFMove(notifyHandler))
101 m_source = adoptGRef(g_source_new(&eventFDSourceFunctions, sizeof(EventFDSource)));
102 g_source_set_name(m_source.get(), "WebCore: MemoryPressureHandler");
103 if (!g_unix_set_fd_nonblocking(m_fd.value(), TRUE, nullptr)) {
104 LOG(MemoryPressure, "Failed to set eventfd nonblocking");
108 EventFDSource* eventFDSource = reinterpret_cast<EventFDSource*>(m_source.get());
109 eventFDSource->fdTag = g_source_add_unix_fd(m_source.get(), m_fd.value(), static_cast<GIOCondition>(eventFDSourceCondition));
110 g_source_set_callback(m_source.get(), [](gpointer userData) -> gboolean {
111 static_cast<EventFDPoller*>(userData)->readAndNotify();
112 return G_SOURCE_REMOVE;
114 g_source_attach(m_source.get(), nullptr);
116 m_threadID = createThread("WebCore: MemoryPressureHandler", [this] { readAndNotify(); }
120 MemoryPressureHandler::EventFDPoller::~EventFDPoller()
124 g_source_destroy(m_source.get());
126 detachThread(m_threadID);
130 static inline bool isFatalReadError(int error)
133 // We don't really need to read the buffer contents, if the poller
134 // notified us, but read would block or is no longer available, is
135 // enough to trigger the memory pressure handler.
136 return error != EAGAIN && error != EWOULDBLOCK;
142 void MemoryPressureHandler::EventFDPoller::readAndNotify() const
145 LOG(MemoryPressure, "Invalidate eventfd.");
150 if (read(m_fd.value(), &buffer, sizeof(buffer)) == -1) {
151 if (isFatalReadError(errno)) {
152 LOG(MemoryPressure, "Failed to read eventfd.");
160 inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
163 LOG(MemoryPressure, "%s, error : %m", log);
166 close(m_eventFD.value());
169 if (m_pressureLevelFD) {
170 close(m_pressureLevelFD.value());
171 m_pressureLevelFD = Nullopt;
175 bool MemoryPressureHandler::tryEnsureEventFD()
180 // Try to use cgroups instead.
181 int fd = eventfd(0, EFD_CLOEXEC);
183 LOG(MemoryPressure, "eventfd() failed: %m");
188 fd = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
190 logErrorAndCloseFDs("Failed to open memory.pressure_level");
193 m_pressureLevelFD = fd;
195 fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
197 logErrorAndCloseFDs("Failed to open cgroup.event_control");
201 char line[128] = {0, };
202 if (snprintf(line, sizeof(line), "%d %d low", m_eventFD.value(), m_pressureLevelFD.value()) < 0
203 || write(fd, line, strlen(line) + 1) < 0) {
204 logErrorAndCloseFDs("Failed to write cgroup.event_control");
213 void MemoryPressureHandler::install()
215 if (m_installed || m_holdOffTimer.isActive())
218 if (!tryEnsureEventFD())
221 m_eventFDPoller = std::make_unique<EventFDPoller>(m_eventFD.value(), [this] {
222 // FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
223 // So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
224 // to get a detailed memory pressure level in the future, we should update here accordingly.
225 bool critical = true;
226 if (ReliefLogger::loggingEnabled())
227 LOG(MemoryPressure, "Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
229 setUnderMemoryPressure(critical);
231 respondToMemoryPressure(critical ? Critical::Yes : Critical::No);
233 RunLoop::main().dispatch([this, critical] { respondToMemoryPressure(critical ? Critical::Yes : Critical::No); });
236 if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure())
237 LOG(MemoryPressure, "System is no longer under memory pressure.");
239 setUnderMemoryPressure(false);
243 void MemoryPressureHandler::uninstall()
248 m_holdOffTimer.stop();
249 m_eventFDPoller = nullptr;
251 if (m_pressureLevelFD) {
252 close(m_pressureLevelFD.value());
253 m_pressureLevelFD = Nullopt;
255 // Only close the eventFD used for cgroups.
257 close(m_eventFD.value());
265 void MemoryPressureHandler::holdOffTimerFired()
270 void MemoryPressureHandler::holdOff(unsigned seconds)
272 m_holdOffTimer.startOneShot(seconds);
275 static size_t processMemoryUsage()
277 ProcessMemoryStatus memoryStatus;
278 currentProcessMemoryStatus(memoryStatus);
279 return (memoryStatus.resident - memoryStatus.shared);
282 void MemoryPressureHandler::respondToMemoryPressure(Critical critical, Synchronous synchronous)
286 double startTime = monotonicallyIncreasingTime();
287 releaseMemory(critical, synchronous);
288 unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
289 holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
292 void MemoryPressureHandler::platformReleaseMemory(Critical)
295 ReliefLogger log("Run malloc_trim");
300 size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
302 return processMemoryUsage();
305 void MemoryPressureHandler::setMemoryPressureMonitorHandle(int fd)
311 } // namespace WebCore