c2d4929309e7b966f34fe9830ea154af8a0dbc3f
[WebKit-https.git] / Source / WebCore / platform / linux / MemoryPressureHandlerLinux.cpp
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All Rights Reserved.
3  * Copyright (C) 2014 Raspberry Pi Foundation. All Rights Reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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.
25  */
26
27 #include "config.h"
28 #include "MemoryPressureHandler.h"
29
30 #if OS(LINUX)
31
32 #include "Logging.h"
33
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <malloc.h>
37 #include <sys/eventfd.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <wtf/CurrentTime.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/text/WTFString.h>
44
45 #if USE(GLIB)
46 #include <glib-unix.h>
47 #endif
48
49 namespace WebCore {
50
51 // Disable memory event reception for a minimum of s_minimumHoldOffTime
52 // seconds after receiving an event. Don't let events fire any sooner than
53 // s_holdOffMultiplier times the last cleanup processing time. Effectively
54 // this is 1 / s_holdOffMultiplier percent of the time.
55 // These value seems reasonable and testing verifies that it throttles frequent
56 // low memory events, greatly reducing CPU usage.
57 static const unsigned s_minimumHoldOffTime = 5;
58 static const unsigned s_holdOffMultiplier = 20;
59
60 static const char* s_cgroupMemoryPressureLevel = "/sys/fs/cgroup/memory/memory.pressure_level";
61 static const char* s_cgroupEventControl = "/sys/fs/cgroup/memory/cgroup.event_control";
62 static const char* s_processStatus = "/proc/self/status";
63
64 #if USE(GLIB)
65 typedef struct {
66     GSource source;
67     gpointer fdTag;
68     GIOCondition condition;
69 } EventFDSource;
70
71 static const unsigned eventFDSourceCondition = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
72
73 static GSourceFuncs eventFDSourceFunctions = {
74     nullptr, // prepare
75     nullptr, // check
76     // dispatch
77     [](GSource* source, GSourceFunc callback, gpointer userData) -> gboolean
78     {
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;
83
84         gboolean returnValue = G_SOURCE_CONTINUE;
85         if (events & G_IO_IN)
86             returnValue = callback(userData);
87         g_source_set_ready_time(source, -1);
88         return returnValue;
89     },
90     nullptr, // finalize
91     nullptr, // closure_callback
92     nullptr, // closure_marshall
93 };
94 #endif
95
96 MemoryPressureHandler::EventFDPoller::EventFDPoller(int fd, std::function<void ()>&& notifyHandler)
97     : m_fd(fd)
98     , m_notifyHandler(WTFMove(notifyHandler))
99 {
100 #if USE(GLIB)
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");
105         return;
106     }
107
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;
113     }, this, nullptr);
114     g_source_attach(m_source.get(), nullptr);
115 #else
116     m_threadID = createThread("WebCore: MemoryPressureHandler", [this] { readAndNotify(); }
117 #endif
118 }
119
120 MemoryPressureHandler::EventFDPoller::~EventFDPoller()
121 {
122     m_fd = Nullopt;
123 #if USE(GLIB)
124     g_source_destroy(m_source.get());
125 #else
126     detachThread(m_threadID);
127 #endif
128 }
129
130 static inline bool isFatalReadError(int error)
131 {
132 #if USE(GLIB)
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;
137 #else
138     return true;
139 #endif
140 }
141
142 void MemoryPressureHandler::EventFDPoller::readAndNotify() const
143 {
144     if (!m_fd) {
145         LOG(MemoryPressure, "Invalidate eventfd.");
146         return;
147     }
148
149     uint64_t buffer;
150     if (read(m_fd.value(), &buffer, sizeof(buffer)) == -1) {
151         if (isFatalReadError(errno)) {
152             LOG(MemoryPressure, "Failed to read eventfd.");
153             return;
154         }
155     }
156
157     m_notifyHandler();
158 }
159
160 static inline String nextToken(FILE* file)
161 {
162     if (!file)
163         return String();
164
165     static const unsigned bufferSize = 128;
166     char buffer[bufferSize] = {0, };
167     unsigned index = 0;
168     while (index < bufferSize) {
169         int ch = fgetc(file);
170         if (ch == EOF || (isASCIISpace(ch) && index)) // Break on non-initial ASCII space.
171             break;
172         if (!isASCIISpace(ch)) {
173             buffer[index] = ch;
174             index++;
175         }
176     }
177
178     return String(buffer);
179 }
180
181 inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
182 {
183     if (log)
184         LOG(MemoryPressure, "%s, error : %m", log);
185
186     if (m_eventFD) {
187         close(m_eventFD.value());
188         m_eventFD = Nullopt;
189     }
190     if (m_pressureLevelFD) {
191         close(m_pressureLevelFD.value());
192         m_pressureLevelFD = Nullopt;
193     }
194 }
195
196 bool MemoryPressureHandler::tryEnsureEventFD()
197 {
198     if (m_eventFD)
199         return true;
200
201     // Try to use cgroups instead.
202     int fd = eventfd(0, EFD_CLOEXEC);
203     if (fd == -1) {
204         LOG(MemoryPressure, "eventfd() failed: %m");
205         return false;
206     }
207     m_eventFD = fd;
208
209     fd = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
210     if (fd == -1) {
211         logErrorAndCloseFDs("Failed to open memory.pressure_level");
212         return false;
213     }
214     m_pressureLevelFD = fd;
215
216     fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
217     if (fd == -1) {
218         logErrorAndCloseFDs("Failed to open cgroup.event_control");
219         return false;
220     }
221
222     char line[128] = {0, };
223     if (snprintf(line, sizeof(line), "%d %d low", m_eventFD.value(), m_pressureLevelFD.value()) < 0
224         || write(fd, line, strlen(line) + 1) < 0) {
225         logErrorAndCloseFDs("Failed to write cgroup.event_control");
226         close(fd);
227         return false;
228     }
229     close(fd);
230
231     return true;
232 }
233
234 void MemoryPressureHandler::install()
235 {
236     if (m_installed || m_holdOffTimer.isActive())
237         return;
238
239     if (!tryEnsureEventFD())
240         return;
241
242     m_eventFDPoller = std::make_unique<EventFDPoller>(m_eventFD.value(), [this] {
243         // FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
244         // So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
245         // to get a detailed memory pressure level in the future, we should update here accordingly.
246         bool critical = true;
247         if (ReliefLogger::loggingEnabled())
248             LOG(MemoryPressure, "Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
249
250         setUnderMemoryPressure(critical);
251         if (isMainThread())
252             respondToMemoryPressure(critical ? Critical::Yes : Critical::No);
253         else
254             RunLoop::main().dispatch([this, critical] { respondToMemoryPressure(critical ? Critical::Yes : Critical::No); });
255     });
256
257     if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure())
258         LOG(MemoryPressure, "System is no longer under memory pressure.");
259
260     setUnderMemoryPressure(false);
261     m_installed = true;
262 }
263
264 void MemoryPressureHandler::uninstall()
265 {
266     if (!m_installed)
267         return;
268
269     m_holdOffTimer.stop();
270     m_eventFDPoller = nullptr;
271
272     if (m_pressureLevelFD) {
273         close(m_pressureLevelFD.value());
274         m_pressureLevelFD = Nullopt;
275
276         // Only close the eventFD used for cgroups.
277         if (m_eventFD) {
278             close(m_eventFD.value());
279             m_eventFD = Nullopt;
280         }
281     }
282
283     m_installed = false;
284 }
285
286 void MemoryPressureHandler::holdOffTimerFired()
287 {
288     install();
289 }
290
291 void MemoryPressureHandler::holdOff(unsigned seconds)
292 {
293     m_holdOffTimer.startOneShot(seconds);
294 }
295
296 void MemoryPressureHandler::respondToMemoryPressure(Critical critical, Synchronous synchronous)
297 {
298     uninstall();
299
300     double startTime = monotonicallyIncreasingTime();
301     releaseMemory(critical, synchronous);
302     unsigned holdOffTime = (monotonicallyIncreasingTime() - startTime) * s_holdOffMultiplier;
303     holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
304 }
305
306 void MemoryPressureHandler::platformReleaseMemory(Critical)
307 {
308 #ifdef __GLIBC__
309     ReliefLogger log("Run malloc_trim");
310     malloc_trim(0);
311 #endif
312 }
313
314 size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
315 {
316     FILE* file = fopen(s_processStatus, "r");
317     if (!file)
318         return static_cast<size_t>(-1);
319
320     size_t vmSize = static_cast<size_t>(-1); // KB
321     String token = nextToken(file);
322     while (!token.isEmpty()) {
323         if (token == "VmSize:") {
324             vmSize = nextToken(file).toInt() * KB;
325             break;
326         }
327         token = nextToken(file);
328     }
329     fclose(file);
330
331     return vmSize;
332 }
333
334 void MemoryPressureHandler::setMemoryPressureMonitorHandle(int fd)
335 {
336     ASSERT(!m_eventFD);
337     m_eventFD = fd;
338 }
339
340 } // namespace WebCore
341
342 #endif // OS(LINUX)