e88144102eaedba42c4bdc5b874ee8fc312226ca
[WebKit-https.git] / Source / WTF / wtf / 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 <errno.h>
33 #include <fcntl.h>
34 #include <malloc.h>
35 #include <sys/eventfd.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <unistd.h>
39 #include <wtf/CurrentTime.h>
40 #include <wtf/MainThread.h>
41 #include <wtf/MemoryFootprint.h>
42 #include <wtf/linux/CurrentProcessMemoryStatus.h>
43 #include <wtf/text/WTFString.h>
44
45 #if USE(GLIB)
46 #include <glib-unix.h>
47 #include <wtf/glib/RunLoopSourcePriority.h>
48 #endif
49
50 #define LOG_CHANNEL_PREFIX Log
51
52 namespace WTF {
53
54 // Disable memory event reception for a minimum of s_minimumHoldOffTime
55 // seconds after receiving an event. Don't let events fire any sooner than
56 // s_holdOffMultiplier times the last cleanup processing time. Effectively
57 // this is 1 / s_holdOffMultiplier percent of the time.
58 // If after releasing the memory we don't free at least s_minimumBytesFreedToUseMinimumHoldOffTime,
59 // we wait longer to try again (s_maximumHoldOffTime).
60 // These value seems reasonable and testing verifies that it throttles frequent
61 // low memory events, greatly reducing CPU usage.
62 static const Seconds s_minimumHoldOffTime { 5_s };
63 static const Seconds s_maximumHoldOffTime { 30_s };
64 static const size_t s_minimumBytesFreedToUseMinimumHoldOffTime = 1 * MB;
65 static const unsigned s_holdOffMultiplier = 20;
66
67 static const char* s_cgroupMemoryPressureLevel = "/sys/fs/cgroup/memory/memory.pressure_level";
68 static const char* s_cgroupEventControl = "/sys/fs/cgroup/memory/cgroup.event_control";
69
70 #if USE(GLIB)
71 typedef struct {
72     GSource source;
73     gpointer fdTag;
74     GIOCondition condition;
75 } EventFDSource;
76
77 static const unsigned eventFDSourceCondition = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
78
79 static GSourceFuncs eventFDSourceFunctions = {
80     nullptr, // prepare
81     nullptr, // check
82     // dispatch
83     [](GSource* source, GSourceFunc callback, gpointer userData) -> gboolean
84     {
85         EventFDSource* eventFDSource = reinterpret_cast<EventFDSource*>(source);
86         unsigned events = g_source_query_unix_fd(source, eventFDSource->fdTag) & eventFDSourceCondition;
87         if (events & G_IO_HUP || events & G_IO_ERR || events & G_IO_NVAL)
88             return G_SOURCE_REMOVE;
89
90         gboolean returnValue = G_SOURCE_CONTINUE;
91         if (events & G_IO_IN)
92             returnValue = callback(userData);
93         g_source_set_ready_time(source, -1);
94         return returnValue;
95     },
96     nullptr, // finalize
97     nullptr, // closure_callback
98     nullptr, // closure_marshall
99 };
100 #endif
101
102 MemoryPressureHandler::EventFDPoller::EventFDPoller(int fd, WTF::Function<void ()>&& notifyHandler)
103     : m_fd(fd)
104     , m_notifyHandler(WTFMove(notifyHandler))
105 {
106 #if USE(GLIB)
107     m_source = adoptGRef(g_source_new(&eventFDSourceFunctions, sizeof(EventFDSource)));
108     g_source_set_priority(m_source.get(), RunLoopSourcePriority::MemoryPressureHandlerTimer);
109     g_source_set_name(m_source.get(), "WTF: MemoryPressureHandler");
110     if (!g_unix_set_fd_nonblocking(m_fd.value(), TRUE, nullptr)) {
111         LOG(MemoryPressure, "Failed to set eventfd nonblocking");
112         return;
113     }
114
115     EventFDSource* eventFDSource = reinterpret_cast<EventFDSource*>(m_source.get());
116     eventFDSource->fdTag = g_source_add_unix_fd(m_source.get(), m_fd.value(), static_cast<GIOCondition>(eventFDSourceCondition));
117     g_source_set_callback(m_source.get(), [](gpointer userData) -> gboolean {
118         static_cast<EventFDPoller*>(userData)->readAndNotify();
119         return G_SOURCE_REMOVE;
120     }, this, nullptr);
121     g_source_attach(m_source.get(), nullptr);
122 #else
123     m_thread = Thread::create("WTF: MemoryPressureHandler",
124         [this] {
125             readAndNotify();
126         });
127 #endif
128 }
129
130 MemoryPressureHandler::EventFDPoller::~EventFDPoller()
131 {
132     m_fd = std::nullopt;
133 #if USE(GLIB)
134     g_source_destroy(m_source.get());
135 #else
136     m_thread->detach();
137 #endif
138 }
139
140 static inline bool isFatalReadError(int error)
141 {
142 #if USE(GLIB)
143     // We don't really need to read the buffer contents, if the poller
144     // notified us, but read would block or is no longer available, is
145     // enough to trigger the memory pressure handler.
146     return error != EAGAIN && error != EWOULDBLOCK;
147 #else
148     return true;
149 #endif
150 }
151
152 void MemoryPressureHandler::EventFDPoller::readAndNotify() const
153 {
154     if (!m_fd) {
155         LOG(MemoryPressure, "Invalidate eventfd.");
156         return;
157     }
158
159     uint64_t buffer;
160     if (read(m_fd.value(), &buffer, sizeof(buffer)) == -1) {
161         if (isFatalReadError(errno)) {
162             LOG(MemoryPressure, "Failed to read eventfd.");
163             return;
164         }
165     }
166
167     m_notifyHandler();
168 }
169
170 inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
171 {
172     if (log)
173         LOG(MemoryPressure, "%s, error : %m", log);
174
175     if (m_eventFD) {
176         close(m_eventFD.value());
177         m_eventFD = std::nullopt;
178     }
179     if (m_pressureLevelFD) {
180         close(m_pressureLevelFD.value());
181         m_pressureLevelFD = std::nullopt;
182     }
183 }
184
185 bool MemoryPressureHandler::tryEnsureEventFD()
186 {
187     if (m_eventFD)
188         return true;
189
190     // Try to use cgroups instead.
191     int fd = eventfd(0, EFD_CLOEXEC);
192     if (fd == -1) {
193         LOG(MemoryPressure, "eventfd() failed: %m");
194         return false;
195     }
196     m_eventFD = fd;
197
198     fd = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
199     if (fd == -1) {
200         logErrorAndCloseFDs("Failed to open memory.pressure_level");
201         return false;
202     }
203     m_pressureLevelFD = fd;
204
205     fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
206     if (fd == -1) {
207         logErrorAndCloseFDs("Failed to open cgroup.event_control");
208         return false;
209     }
210
211     char line[128] = {0, };
212     if (snprintf(line, sizeof(line), "%d %d low", m_eventFD.value(), m_pressureLevelFD.value()) < 0
213         || write(fd, line, strlen(line) + 1) < 0) {
214         logErrorAndCloseFDs("Failed to write cgroup.event_control");
215         close(fd);
216         return false;
217     }
218     close(fd);
219
220     return true;
221 }
222
223 void MemoryPressureHandler::install()
224 {
225     if (m_installed || m_holdOffTimer.isActive())
226         return;
227
228     if (!tryEnsureEventFD())
229         return;
230
231     m_eventFDPoller = std::make_unique<EventFDPoller>(m_eventFD.value(), [this] {
232         // FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
233         // So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
234         // to get a detailed memory pressure level in the future, we should update here accordingly.
235         bool critical = true;
236         if (ReliefLogger::loggingEnabled())
237             LOG(MemoryPressure, "Got memory pressure notification (%s)", critical ? "critical" : "non-critical");
238
239         setUnderMemoryPressure(critical);
240         if (isMainThread())
241             respondToMemoryPressure(critical ? Critical::Yes : Critical::No);
242         else
243             RunLoop::main().dispatch([this, critical] { respondToMemoryPressure(critical ? Critical::Yes : Critical::No); });
244     });
245
246     if (ReliefLogger::loggingEnabled() && isUnderMemoryPressure())
247         LOG(MemoryPressure, "System is no longer under memory pressure.");
248
249     setUnderMemoryPressure(false);
250     m_installed = true;
251 }
252
253 void MemoryPressureHandler::uninstall()
254 {
255     if (!m_installed)
256         return;
257
258     m_holdOffTimer.stop();
259     m_eventFDPoller = nullptr;
260
261     if (m_pressureLevelFD) {
262         close(m_pressureLevelFD.value());
263         m_pressureLevelFD = std::nullopt;
264
265         // Only close the eventFD used for cgroups.
266         if (m_eventFD) {
267             close(m_eventFD.value());
268             m_eventFD = std::nullopt;
269         }
270     }
271
272     m_installed = false;
273 }
274
275 void MemoryPressureHandler::holdOffTimerFired()
276 {
277     install();
278 }
279
280 void MemoryPressureHandler::holdOff(Seconds seconds)
281 {
282     m_holdOffTimer.startOneShot(seconds);
283 }
284
285 static size_t processMemoryUsage()
286 {
287     ProcessMemoryStatus memoryStatus;
288     currentProcessMemoryStatus(memoryStatus);
289     return (memoryStatus.resident - memoryStatus.shared);
290 }
291
292 void MemoryPressureHandler::respondToMemoryPressure(Critical critical, Synchronous synchronous)
293 {
294     uninstall();
295
296     MonotonicTime startTime = MonotonicTime::now();
297     int64_t processMemory = processMemoryUsage();
298     releaseMemory(critical, synchronous);
299     int64_t bytesFreed = processMemory - processMemoryUsage();
300     Seconds holdOffTime = s_maximumHoldOffTime;
301     if (bytesFreed > 0 && static_cast<size_t>(bytesFreed) >= s_minimumBytesFreedToUseMinimumHoldOffTime)
302         holdOffTime = (MonotonicTime::now() - startTime) * s_holdOffMultiplier;
303     holdOff(std::max(holdOffTime, s_minimumHoldOffTime));
304 }
305
306 void MemoryPressureHandler::platformReleaseMemory(Critical)
307 {
308 #ifdef __GLIBC__
309     malloc_trim(0);
310 #endif
311 }
312
313 std::optional<MemoryPressureHandler::ReliefLogger::MemoryUsage> MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
314 {
315     size_t physical = 0;
316     auto footprint = memoryFootprint();
317     if (footprint)
318         physical = footprint.value();
319     return MemoryUsage {processMemoryUsage(), physical};
320 }
321
322 void MemoryPressureHandler::setMemoryPressureMonitorHandle(int fd)
323 {
324     ASSERT(!m_eventFD);
325     m_eventFD = fd;
326 }
327
328 } // namespace WTF
329
330 #endif // OS(LINUX)