MemoryPressureHandler doesn't work if cgroups aren't present in Linux
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Jul 2016 09:04:10 +0000 (09:04 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 18 Jul 2016 09:04:10 +0000 (09:04 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155255

Reviewed by Sergio Villar Senin.

Source/WebCore:

Allow to pass an eventFD file descriptor to the MemoryPressureHandler to be monitorized in case cgroups are not
available.

* platform/MemoryPressureHandler.h:
* platform/linux/MemoryPressureHandlerLinux.cpp:

Source/WebKit2:

There's no way to get notifications about memory pressure in Linux without using cgroups that doesn't require a
manual polling. We can get that information from /proc/meminfo, but that's not pollable so it requires to
manually check its contents in a loop sleeping for a while between checks. This means we would be waking up the
process on every poll iteration, most of the times for nothing. That's specially problematic on devices running
on battery. And taking into account that there's a memory pressure handler in every secondary process (Web,
Network and Plugin), we would be waking up all those process all the time. However, not having a memory pressure
handler is even more problematic than the manual polling.
This patch adds a class MemoryPressureMonitor to the manual polling of /proc/meminfo, but runs in the UI
process, to avoid the weakups in all other secondary processes, and uses an eventFD to notify all other
processes. It's only used in case cgroups is not available. The eventFD descriptor is sent to all other
processes at startup, and passed to the MemoryPressureHandler before install() is called for the first
time. To minimize the wakeups even in the UI process, the poll interval is calculated from 1 to 5 seconds
depending on the current memory used, so in case of low memory level we sleep for a longer time.
It's also important to make the memory calculations as accurate as possible to avoid cleaning resources in the
secondary processes unnecessarily.

* NetworkProcess/NetworkProcess.cpp:
(WebKit::NetworkProcess::initializeNetworkProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.
* NetworkProcess/NetworkProcess.h:
* NetworkProcess/NetworkProcessCreationParameters.cpp:
(WebKit::NetworkProcessCreationParameters::encode): Encode memory pressure monitor handle.
(WebKit::NetworkProcessCreationParameters::decode): Decode memory pressure monitor handle.
* NetworkProcess/NetworkProcessCreationParameters.h:
* PlatformEfl.cmake: Add new file to compilation, and update include dirs.
* PlatformGTK.cmake: Ditto.
* PluginProcess/PluginProcess.cpp:
(WebKit::PluginProcess::initializePluginProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.
* Shared/Plugins/PluginProcessCreationParameters.cpp:
(WebKit::PluginProcessCreationParameters::encode): Encode memory pressure monitor handle.
(WebKit::PluginProcessCreationParameters::decode): Decode memory pressure monitor handle.
* Shared/Plugins/PluginProcessCreationParameters.h:
* Shared/WebProcessCreationParameters.cpp:
(WebKit::WebProcessCreationParameters::encode): Encode memory pressure monitor handle.
(WebKit::WebProcessCreationParameters::decode): Decode memory pressure monitor handle.
* Shared/WebProcessCreationParameters.h:
* UIProcess/Plugins/PluginProcessProxy.cpp:
(WebKit::PluginProcessProxy::didFinishLaunching): Create the memory pressure monitor handle for the plugin
process if needed.
* UIProcess/WebProcessPool.cpp:
(WebKit::WebProcessPool::ensureNetworkProcess): Create the memory pressure monitor handle for the network
process if needed.
(WebKit::WebProcessPool::createNewWebProcess): Create the memory pressure monitor handle for the web process if
needed.
* UIProcess/linux/MemoryPressureMonitor.cpp: Added.
(WebKit::lowWatermarkPages):
(WebKit::systemPageSize):
(WebKit::calculateMemoryAvailable):
(WebKit::systemMemoryUsedAsPercentage):
(WebKit::pollIntervalForUsedMemoryPercentage):
(WebKit::isSystemdMemoryPressureMonitorAvailable):
(WebKit::MemoryPressureMonitor::isEnabled):
(WebKit::MemoryPressureMonitor::singleton):
(WebKit::MemoryPressureMonitor::MemoryPressureMonitor):
(WebKit::MemoryPressureMonitor::createHandle):
* UIProcess/linux/MemoryPressureMonitor.h:
* WebProcess/WebProcess.cpp:
(WebKit::WebProcess::initializeWebProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@203342 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/MemoryPressureHandler.h
Source/WebCore/platform/linux/MemoryPressureHandlerLinux.cpp
Source/WebKit2/ChangeLog
Source/WebKit2/NetworkProcess/NetworkProcess.cpp
Source/WebKit2/NetworkProcess/NetworkProcess.h
Source/WebKit2/NetworkProcess/NetworkProcessCreationParameters.cpp
Source/WebKit2/NetworkProcess/NetworkProcessCreationParameters.h
Source/WebKit2/PlatformEfl.cmake
Source/WebKit2/PlatformGTK.cmake
Source/WebKit2/PluginProcess/PluginProcess.cpp
Source/WebKit2/Shared/Plugins/PluginProcessCreationParameters.cpp
Source/WebKit2/Shared/Plugins/PluginProcessCreationParameters.h
Source/WebKit2/Shared/WebProcessCreationParameters.cpp
Source/WebKit2/Shared/WebProcessCreationParameters.h
Source/WebKit2/UIProcess/Plugins/PluginProcessProxy.cpp
Source/WebKit2/UIProcess/WebProcessPool.cpp
Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.cpp [new file with mode: 0644]
Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.h [new file with mode: 0644]
Source/WebKit2/WebProcess/WebProcess.cpp

index 966f625..b933bac 100644 (file)
@@ -1,3 +1,16 @@
+2016-07-18  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        MemoryPressureHandler doesn't work if cgroups aren't present in Linux
+        https://bugs.webkit.org/show_bug.cgi?id=155255
+
+        Reviewed by Sergio Villar Senin.
+
+        Allow to pass an eventFD file descriptor to the MemoryPressureHandler to be monitorized in case cgroups are not
+        available.
+
+        * platform/MemoryPressureHandler.h:
+        * platform/linux/MemoryPressureHandlerLinux.cpp:
+
 2016-07-17  Gyuyoung Kim  <gyuyoung.kim@webkit.org>
 
         Clean up PassRefPtr uses in Modules/encryptedmedia, Modules/speech, and Modules/quota
index 01fd814..8b04d48 100644 (file)
@@ -84,6 +84,8 @@ public:
     WEBCORE_EXPORT void clearMemoryPressure();
     WEBCORE_EXPORT bool shouldWaitForMemoryClearMessage();
     void respondToMemoryPressureIfNeeded();
+#elif OS(LINUX)
+    void setMemoryPressureMonitorHandle(int fd);
 #endif
 
     class ReliefLogger {
@@ -179,6 +181,7 @@ private:
     RunLoop::Timer<MemoryPressureHandler> m_holdOffTimer;
     void holdOffTimerFired();
     void logErrorAndCloseFDs(const char* error);
+    bool tryEnsureEventFD();
 #endif
 };
 
index 47131af..90cf640 100644 (file)
@@ -193,29 +193,30 @@ inline void MemoryPressureHandler::logErrorAndCloseFDs(const char* log)
     }
 }
 
-void MemoryPressureHandler::install()
+bool MemoryPressureHandler::tryEnsureEventFD()
 {
-    if (m_installed || m_holdOffTimer.isActive())
-        return;
+    if (m_eventFD)
+        return true;
 
+    // Try to use cgroups instead.
     int fd = eventfd(0, EFD_CLOEXEC);
     if (fd == -1) {
         LOG(MemoryPressure, "eventfd() failed: %m");
-        return;
+        return false;
     }
     m_eventFD = fd;
 
     fd = open(s_cgroupMemoryPressureLevel, O_CLOEXEC | O_RDONLY);
     if (fd == -1) {
         logErrorAndCloseFDs("Failed to open memory.pressure_level");
-        return;
+        return false;
     }
     m_pressureLevelFD = fd;
 
     fd = open(s_cgroupEventControl, O_CLOEXEC | O_WRONLY);
     if (fd == -1) {
         logErrorAndCloseFDs("Failed to open cgroup.event_control");
-        return;
+        return false;
     }
 
     char line[128] = {0, };
@@ -223,10 +224,21 @@ void MemoryPressureHandler::install()
         || write(fd, line, strlen(line) + 1) < 0) {
         logErrorAndCloseFDs("Failed to write cgroup.event_control");
         close(fd);
-        return;
+        return false;
     }
     close(fd);
 
+    return true;
+}
+
+void MemoryPressureHandler::install()
+{
+    if (m_installed || m_holdOffTimer.isActive())
+        return;
+
+    if (!tryEnsureEventFD())
+        return;
+
     m_eventFDPoller = std::make_unique<EventFDPoller>(m_eventFD.value(), [this] {
         // FIXME: Current memcg does not provide any way for users to know how serious the memory pressure is.
         // So we assume all notifications from memcg are critical for now. If memcg had better inferfaces
@@ -257,7 +269,17 @@ void MemoryPressureHandler::uninstall()
     m_holdOffTimer.stop();
     m_eventFDPoller = nullptr;
 
-    logErrorAndCloseFDs(nullptr);
+    if (m_pressureLevelFD) {
+        close(m_pressureLevelFD.value());
+        m_pressureLevelFD = Nullopt;
+
+        // Only close the eventFD used for cgroups.
+        if (m_eventFD) {
+            close(m_eventFD.value());
+            m_eventFD = Nullopt;
+        }
+    }
+
     m_installed = false;
 }
 
@@ -309,6 +331,12 @@ size_t MemoryPressureHandler::ReliefLogger::platformMemoryUsage()
     return vmSize;
 }
 
+void MemoryPressureHandler::setMemoryPressureMonitorHandle(int fd)
+{
+    ASSERT(!m_eventFD);
+    m_eventFD = fd;
+}
+
 } // namespace WebCore
 
 #endif // OS(LINUX)
index ef8eee0..ec45480 100644 (file)
@@ -1,3 +1,68 @@
+2016-07-18  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        MemoryPressureHandler doesn't work if cgroups aren't present in Linux
+        https://bugs.webkit.org/show_bug.cgi?id=155255
+
+        Reviewed by Sergio Villar Senin.
+
+        There's no way to get notifications about memory pressure in Linux without using cgroups that doesn't require a
+        manual polling. We can get that information from /proc/meminfo, but that's not pollable so it requires to
+        manually check its contents in a loop sleeping for a while between checks. This means we would be waking up the
+        process on every poll iteration, most of the times for nothing. That's specially problematic on devices running
+        on battery. And taking into account that there's a memory pressure handler in every secondary process (Web,
+        Network and Plugin), we would be waking up all those process all the time. However, not having a memory pressure
+        handler is even more problematic than the manual polling.
+        This patch adds a class MemoryPressureMonitor to the manual polling of /proc/meminfo, but runs in the UI
+        process, to avoid the weakups in all other secondary processes, and uses an eventFD to notify all other
+        processes. It's only used in case cgroups is not available. The eventFD descriptor is sent to all other
+        processes at startup, and passed to the MemoryPressureHandler before install() is called for the first
+        time. To minimize the wakeups even in the UI process, the poll interval is calculated from 1 to 5 seconds
+        depending on the current memory used, so in case of low memory level we sleep for a longer time.
+        It's also important to make the memory calculations as accurate as possible to avoid cleaning resources in the
+        secondary processes unnecessarily.
+
+        * NetworkProcess/NetworkProcess.cpp:
+        (WebKit::NetworkProcess::initializeNetworkProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.
+        * NetworkProcess/NetworkProcess.h:
+        * NetworkProcess/NetworkProcessCreationParameters.cpp:
+        (WebKit::NetworkProcessCreationParameters::encode): Encode memory pressure monitor handle.
+        (WebKit::NetworkProcessCreationParameters::decode): Decode memory pressure monitor handle.
+        * NetworkProcess/NetworkProcessCreationParameters.h:
+        * PlatformEfl.cmake: Add new file to compilation, and update include dirs.
+        * PlatformGTK.cmake: Ditto.
+        * PluginProcess/PluginProcess.cpp:
+        (WebKit::PluginProcess::initializePluginProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.
+        * Shared/Plugins/PluginProcessCreationParameters.cpp:
+        (WebKit::PluginProcessCreationParameters::encode): Encode memory pressure monitor handle.
+        (WebKit::PluginProcessCreationParameters::decode): Decode memory pressure monitor handle.
+        * Shared/Plugins/PluginProcessCreationParameters.h:
+        * Shared/WebProcessCreationParameters.cpp:
+        (WebKit::WebProcessCreationParameters::encode): Encode memory pressure monitor handle.
+        (WebKit::WebProcessCreationParameters::decode): Decode memory pressure monitor handle.
+        * Shared/WebProcessCreationParameters.h:
+        * UIProcess/Plugins/PluginProcessProxy.cpp:
+        (WebKit::PluginProcessProxy::didFinishLaunching): Create the memory pressure monitor handle for the plugin
+        process if needed.
+        * UIProcess/WebProcessPool.cpp:
+        (WebKit::WebProcessPool::ensureNetworkProcess): Create the memory pressure monitor handle for the network
+        process if needed.
+        (WebKit::WebProcessPool::createNewWebProcess): Create the memory pressure monitor handle for the web process if
+        needed.
+        * UIProcess/linux/MemoryPressureMonitor.cpp: Added.
+        (WebKit::lowWatermarkPages):
+        (WebKit::systemPageSize):
+        (WebKit::calculateMemoryAvailable):
+        (WebKit::systemMemoryUsedAsPercentage):
+        (WebKit::pollIntervalForUsedMemoryPercentage):
+        (WebKit::isSystemdMemoryPressureMonitorAvailable):
+        (WebKit::MemoryPressureMonitor::isEnabled):
+        (WebKit::MemoryPressureMonitor::singleton):
+        (WebKit::MemoryPressureMonitor::MemoryPressureMonitor):
+        (WebKit::MemoryPressureMonitor::createHandle):
+        * UIProcess/linux/MemoryPressureMonitor.h:
+        * WebProcess/WebProcess.cpp:
+        (WebKit::WebProcess::initializeWebProcess): Pass the memory pressure monitor file descriptor to the MemoryPressureHandler.
+
 2016-07-17  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         REGRESSION(r202855): [GTK] ASSERTION FAILED: m_webPage.bounds().contains(bounds)
index 895393c..a2ad0d6 100644 (file)
@@ -193,7 +193,7 @@ void NetworkProcess::lowMemoryHandler(Critical critical)
     WTF::releaseFastMallocFreeMemory();
 }
 
-void NetworkProcess::initializeNetworkProcess(const NetworkProcessCreationParameters& parameters)
+void NetworkProcess::initializeNetworkProcess(NetworkProcessCreationParameters&& parameters)
 {
     platformInitializeNetworkProcess(parameters);
 
@@ -202,6 +202,10 @@ void NetworkProcess::initializeNetworkProcess(const NetworkProcessCreationParame
     m_suppressMemoryPressureHandler = parameters.shouldSuppressMemoryPressureHandler;
     if (!m_suppressMemoryPressureHandler) {
         auto& memoryPressureHandler = MemoryPressureHandler::singleton();
+#if OS(LINUX)
+        if (parameters.memoryPressureMonitorHandle.fileDescriptor() != -1)
+            memoryPressureHandler.setMemoryPressureMonitorHandle(parameters.memoryPressureMonitorHandle.releaseFileDescriptor());
+#endif
         memoryPressureHandler.setLowMemoryHandler([this] (Critical critical, Synchronous) {
             lowMemoryHandler(critical);
         });
index c897998..b732517 100644 (file)
@@ -159,7 +159,7 @@ private:
     // Message Handlers
     void didReceiveNetworkProcessMessage(IPC::Connection&, IPC::MessageDecoder&);
     void didReceiveSyncNetworkProcessMessage(IPC::Connection&, IPC::MessageDecoder&, std::unique_ptr<IPC::MessageEncoder>&);
-    void initializeNetworkProcess(const NetworkProcessCreationParameters&);
+    void initializeNetworkProcess(NetworkProcessCreationParameters&&);
     void createNetworkConnectionToWebProcess();
     void destroyPrivateBrowsingSession(WebCore::SessionID);
 
index b48fd00..bc35404 100644 (file)
@@ -82,6 +82,9 @@ void NetworkProcessCreationParameters::encode(IPC::ArgumentEncoder& encoder) con
     encoder << ignoreTLSErrors;
     encoder << languages;
 #endif
+#if OS(LINUX)
+    encoder << memoryPressureMonitorHandle;
+#endif
 }
 
 bool NetworkProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, NetworkProcessCreationParameters& result)
@@ -158,6 +161,11 @@ bool NetworkProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, Net
         return false;
 #endif
 
+#if OS(LINUX)
+    if (!decoder.decode(result.memoryPressureMonitorHandle))
+        return false;
+#endif
+
     return true;
 }
 
index 4a58e4a..5688194 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef NetworkProcessCreationParameters_h
 #define NetworkProcessCreationParameters_h
 
+#include "Attachment.h"
 #include "CacheModel.h"
 #include "SandboxExtension.h"
 #include <wtf/Vector.h>
@@ -95,6 +96,10 @@ struct NetworkProcessCreationParameters {
     bool ignoreTLSErrors;
     Vector<String> languages;
 #endif
+
+#if OS(LINUX)
+    IPC::Attachment memoryPressureMonitorHandle;
+#endif
 };
 
 } // namespace WebKit
index 10856c8..2020fbe 100644 (file)
@@ -171,6 +171,8 @@ list(APPEND WebKit2_SOURCES
     UIProcess/gstreamer/InstallMissingMediaPluginsPermissionRequest.cpp
     UIProcess/gstreamer/WebPageProxyGStreamer.cpp
 
+    UIProcess/linux/MemoryPressureMonitor.cpp
+
     UIProcess/soup/WebCookieManagerProxySoup.cpp
     UIProcess/soup/WebProcessPoolSoup.cpp
 
@@ -256,6 +258,7 @@ list(APPEND WebKit2_INCLUDE_DIRECTORIES
     "${WEBKIT2_DIR}/UIProcess/CoordinatedGraphics"
     "${WEBKIT2_DIR}/UIProcess/Network/CustomProtocols/soup"
     "${WEBKIT2_DIR}/UIProcess/efl"
+    "${WEBKIT2_DIR}/UIProcess/linux"
     "${WEBKIT2_DIR}/UIProcess/soup"
     "${WEBKIT2_DIR}/WebProcess/efl"
     "${WEBKIT2_DIR}/WebProcess/soup"
index b3c2adb..fd21680 100644 (file)
@@ -273,6 +273,8 @@ list(APPEND WebKit2_SOURCES
 
     UIProcess/Launcher/gtk/ProcessLauncherGtk.cpp
 
+    UIProcess/linux/MemoryPressureMonitor.cpp
+
     UIProcess/Network/CustomProtocols/soup/CustomProtocolManagerProxySoup.cpp
     UIProcess/Network/CustomProtocols/soup/WebSoupCustomProtocolRequestManager.cpp
     UIProcess/Network/CustomProtocols/soup/WebSoupCustomProtocolRequestManagerClient.cpp
@@ -513,6 +515,7 @@ list(APPEND WebKit2_INCLUDE_DIRECTORIES
     "${WEBKIT2_DIR}/UIProcess/Plugins/gtk"
     "${WEBKIT2_DIR}/UIProcess/gstreamer"
     "${WEBKIT2_DIR}/UIProcess/gtk"
+    "${WEBKIT2_DIR}/UIProcess/linux"
     "${WEBKIT2_DIR}/UIProcess/soup"
     "${WEBKIT2_DIR}/WebProcess/InjectedBundle/API/gtk"
     "${WEBKIT2_DIR}/WebProcess/Plugins/Netscape/unix"
index 7555702..61e66f0 100644 (file)
@@ -71,13 +71,6 @@ void PluginProcess::initializeProcess(const ChildProcessInitializationParameters
 {
     m_pluginPath = parameters.extraInitializationData.get("plugin-path");
     platformInitializeProcess(parameters);
-
-    auto& memoryPressureHandler = MemoryPressureHandler::singleton();
-    memoryPressureHandler.setLowMemoryHandler([this] (Critical, Synchronous) {
-        if (shouldTerminate())
-            terminate();
-    });
-    memoryPressureHandler.install();
 }
 
 void PluginProcess::removeWebProcessConnection(WebProcessConnection* webProcessConnection)
@@ -137,6 +130,17 @@ void PluginProcess::initializePluginProcess(PluginProcessCreationParameters&& pa
 {
     ASSERT(!m_pluginModule);
 
+    auto& memoryPressureHandler = MemoryPressureHandler::singleton();
+#if OS(LINUX)
+    if (parameters.memoryPressureMonitorHandle.fileDescriptor() != -1)
+        memoryPressureHandler.setMemoryPressureMonitorHandle(parameters.memoryPressureMonitorHandle.releaseFileDescriptor());
+#endif
+    memoryPressureHandler.setLowMemoryHandler([this] (Critical, Synchronous) {
+        if (shouldTerminate())
+            terminate();
+    });
+    memoryPressureHandler.install();
+
     m_supportsAsynchronousPluginInitialization = parameters.supportsAsynchronousPluginInitialization;
     setMinimumLifetime(parameters.minimumLifetime);
     setTerminationTimeout(parameters.terminationTimeout);
index e3e1ffd..30154c7 100644 (file)
@@ -51,6 +51,9 @@ void PluginProcessCreationParameters::encode(IPC::ArgumentEncoder& encoder) cons
     IPC::encode(encoder, networkATSContext.get());
 #endif
 #endif
+#if OS(LINUX)
+    encoder << memoryPressureMonitorHandle;
+#endif
 }
 
 bool PluginProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, PluginProcessCreationParameters& result)
@@ -71,6 +74,10 @@ bool PluginProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, Plug
         return false;
 #endif
 #endif
+#if OS(LINUX)
+    if (!decoder.decode(result.memoryPressureMonitorHandle))
+        return false;
+#endif
 
     return true;
 }
index ac7d638..d617139 100644 (file)
@@ -28,6 +28,7 @@
 
 #if ENABLE(NETSCAPE_PLUGIN_API)
 
+#include "Attachment.h"
 #include "PluginProcessAttributes.h"
 
 #if PLATFORM(COCOA)
@@ -59,6 +60,9 @@ struct PluginProcessCreationParameters {
     RetainPtr<CFDataRef> networkATSContext;
 #endif
 #endif
+#if OS(LINUX)
+    IPC::Attachment memoryPressureMonitorHandle;
+#endif
 };
 
 } // namespace WebKit
index a531e45..1a2b0d5 100644 (file)
@@ -141,6 +141,10 @@ void WebProcessCreationParameters::encode(IPC::ArgumentEncoder& encoder) const
 #if TARGET_OS_IPHONE || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
     IPC::encode(encoder, networkATSContext.get());
 #endif
+
+#if OS(LINUX)
+    encoder << memoryPressureMonitorHandle;
+#endif
 }
 
 bool WebProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, WebProcessCreationParameters& parameters)
@@ -296,6 +300,11 @@ bool WebProcessCreationParameters::decode(IPC::ArgumentDecoder& decoder, WebProc
         return false;
 #endif
 
+#if OS(LINUX)
+    if (!decoder.decode(parameters.memoryPressureMonitorHandle))
+        return false;
+#endif
+
     return true;
 }
 
index add84f3..c9aa2c9 100644 (file)
@@ -166,6 +166,10 @@ struct WebProcessCreationParameters {
 #if TARGET_OS_IPHONE || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
     RetainPtr<CFDataRef> networkATSContext;
 #endif
+
+#if OS(LINUX)
+    IPC::Attachment memoryPressureMonitorHandle;
+#endif
 };
 
 } // namespace WebKit
index 8f505a6..941e56b 100644 (file)
 #include <WebCore/NotImplemented.h>
 #include <wtf/RunLoop.h>
 
+#if OS(LINUX)
+#include "MemoryPressureMonitor.h"
+#endif
+
 using namespace WebCore;
 
 namespace WebKit {
@@ -229,6 +233,12 @@ void PluginProcessProxy::didFinishLaunching(ProcessLauncher*, IPC::Connection::I
         parameters.minimumLifetime = minimumLifetime;
         parameters.terminationTimeout = shutdownTimeout;
     }
+
+#if OS(LINUX)
+    if (MemoryPressureMonitor::isEnabled())
+        parameters.memoryPressureMonitorHandle = MemoryPressureMonitor::singleton().createHandle();
+#endif
+
     platformInitializePluginProcess(parameters);
 
     // Initialize the plug-in host process.
index 2f9bf09..92dc2ea 100644 (file)
 #include "WebSoupCustomProtocolRequestManager.h"
 #endif
 
+#if OS(LINUX)
+#include "MemoryPressureMonitor.h"
+#endif
+
 #ifndef NDEBUG
 #include <wtf/RefCountedLeakCounter.h>
 #endif
@@ -370,6 +374,11 @@ NetworkProcessProxy& WebProcessPool::ensureNetworkProcess()
         SandboxExtension::createHandle(parentBundleDirectory, SandboxExtension::ReadOnly, parameters.parentBundleDirectoryExtensionHandle);
 #endif
 
+#if OS(LINUX)
+    if (MemoryPressureMonitor::isEnabled())
+        parameters.memoryPressureMonitorHandle = MemoryPressureMonitor::singleton().createHandle();
+#endif
+
     parameters.shouldUseTestingNetworkSession = m_shouldUseTestingNetworkSession;
 
     // Add any platform specific parameters
@@ -626,6 +635,8 @@ WebProcessProxy& WebProcessPool::createNewWebProcess()
 
 #if OS(LINUX)
     parameters.shouldEnableMemoryPressureReliefLogging = true;
+    if (MemoryPressureMonitor::isEnabled())
+        parameters.memoryPressureMonitorHandle = MemoryPressureMonitor::singleton().createHandle();
 #endif
 
     parameters.resourceLoadStatisticsEnabled = resourceLoadStatisticsEnabled();
diff --git a/Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.cpp b/Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.cpp
new file mode 100644 (file)
index 0000000..0882603
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2016 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "MemoryPressureMonitor.h"
+
+#if OS(LINUX)
+
+#include "Attachment.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <mutex>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/eventfd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <wtf/CurrentTime.h>
+#include <wtf/Threading.h>
+#include <wtf/UniStdExtras.h>
+
+namespace WebKit {
+
+static const size_t notSet = static_cast<size_t>(-1);
+
+static const double s_minPollingIntervalInSeconds = 1;
+static const double s_maxPollingIntervalInSeconds = 5;
+static const double s_minUsedMemoryPercentageForPolling = 50;
+static const double s_maxUsedMemoryPercentageForPolling = 90;
+static const int s_memoryPresurePercentageThreshold = 95;
+
+static size_t lowWatermarkPages()
+{
+    FILE* file = fopen("/proc/zoneinfo", "r");
+    if (!file)
+        return notSet;
+
+    size_t low = 0;
+    bool inZone = false;
+    bool foundLow = false;
+    char buffer[128];
+    while (char* line = fgets(buffer, 128, file)) {
+        if (!strcmp(line, "Node")) {
+            inZone = true;
+            foundLow = false;
+            continue;
+        }
+
+        char* token = strtok(line, " ");
+        if (!token)
+            continue;
+
+        if (!strcmp(token, "low")) {
+            if (!inZone || foundLow) {
+                low = notSet;
+                break;
+            }
+            token = strtok(nullptr, " ");
+            if (!token) {
+                low = notSet;
+                break;
+            }
+            low += atoll(token);
+            foundLow = true;
+        }
+    }
+    fclose(file);
+
+    return low;
+}
+
+static inline size_t systemPageSize()
+{
+    static size_t pageSize = 0;
+    if (!pageSize)
+        pageSize = sysconf(_SC_PAGE_SIZE);
+    return pageSize;
+}
+
+// If MemAvailable was not present in /proc/meminfo, because it's an old kernel version,
+// we can do the same calculation with the information we have from meminfo and the low watermaks.
+// See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+static size_t calculateMemoryAvailable(size_t memoryFree, size_t activeFile, size_t inactiveFile, size_t slabReclaimable)
+{
+    if (memoryFree == notSet || activeFile == notSet || inactiveFile == notSet || slabReclaimable == notSet)
+        return notSet;
+
+    size_t lowWatermark = lowWatermarkPages();
+    if (lowWatermark == notSet)
+        return notSet;
+
+    lowWatermark *= systemPageSize() / KB;
+
+    // Estimate the amount of memory available for userspace allocations, without causing swapping.
+    // Free memory cannot be taken below the low watermark, before the system starts swapping.
+    lowWatermark *= systemPageSize() / KB;
+    size_t memoryAvailable = memoryFree - lowWatermark;
+
+    // Not all the page cache can be freed, otherwise the system will start swapping. Assume at least
+    // half of the page cache, or the low watermark worth of cache, needs to stay.
+    size_t pageCache = activeFile + inactiveFile;
+    pageCache -= std::min(pageCache / 2, lowWatermark);
+    memoryAvailable += pageCache;
+
+    // Part of the reclaimable slab consists of items that are in use, and cannot be freed.
+    // Cap this estimate at the low watermark.
+    memoryAvailable += slabReclaimable - std::min(slabReclaimable / 2, lowWatermark);
+    return memoryAvailable;
+}
+
+static int systemMemoryUsedAsPercentage()
+{
+    FILE* file = fopen("/proc/meminfo", "r");
+    if (!file)
+        return -1;
+
+    size_t memoryAvailable, memoryTotal, memoryFree, activeFile, inactiveFile, slabReclaimable;
+    memoryAvailable = memoryTotal = memoryFree = activeFile = inactiveFile = slabReclaimable = notSet;
+    char buffer[128];
+    while (char* line = fgets(buffer, 128, file)) {
+        char* token = strtok(line, " ");
+        if (!token)
+            break;
+
+        if (!strcmp(token, "MemAvailable:")) {
+            if ((token = strtok(nullptr, " "))) {
+                memoryAvailable = atoll(token);
+                if (memoryTotal != notSet)
+                    break;
+            }
+        } else if (!strcmp(token, "MemTotal:")) {
+            if ((token = strtok(nullptr, " ")))
+                memoryTotal = atoll(token);
+            else
+                break;
+        } else if (!strcmp(token, "MemFree:")) {
+            if ((token = strtok(nullptr, " ")))
+                memoryFree = atoll(token);
+            else
+                break;
+        } else if (!strcmp(token, "Active(file):")) {
+            if ((token = strtok(nullptr, " ")))
+                activeFile = atoll(token);
+            else
+                break;
+        } else if (!strcmp(token, "Inactive(file):")) {
+            if ((token = strtok(nullptr, " ")))
+                inactiveFile = atoll(token);
+            else
+                break;
+        } else if (!strcmp(token, "SReclaimable:")) {
+            if ((token = strtok(nullptr, " ")))
+                slabReclaimable = atoll(token);
+            else
+                break;
+        }
+
+        if (memoryTotal != notSet && memoryFree != notSet && activeFile != notSet && inactiveFile != notSet && slabReclaimable != notSet)
+            break;
+    }
+    fclose(file);
+
+    if (!memoryTotal || memoryTotal == notSet)
+        return -1;
+
+    if (memoryAvailable == notSet) {
+        memoryAvailable = calculateMemoryAvailable(memoryFree, activeFile, inactiveFile, slabReclaimable);
+        if (memoryAvailable == notSet)
+            return -1;
+    }
+
+    if (memoryAvailable > memoryTotal)
+        return -1;
+
+    return ((memoryTotal - memoryAvailable) * 100) / memoryTotal;
+}
+
+static inline double pollIntervalForUsedMemoryPercentage(int usedPercentage)
+{
+    // Use a different poll interval depending on the currently memory used,
+    // to avoid polling too often when the system is under low memory usage.
+    if (usedPercentage < s_minUsedMemoryPercentageForPolling)
+        return s_maxPollingIntervalInSeconds;
+
+    if (usedPercentage >= s_maxUsedMemoryPercentageForPolling)
+        return s_minPollingIntervalInSeconds;
+
+    return s_minPollingIntervalInSeconds + (s_maxPollingIntervalInSeconds - s_minPollingIntervalInSeconds) *
+        ((usedPercentage - s_minUsedMemoryPercentageForPolling) / (s_maxUsedMemoryPercentageForPolling - s_minUsedMemoryPercentageForPolling));
+}
+
+static bool isSystemdMemoryPressureMonitorAvailable()
+{
+    int fd = open("/sys/fs/cgroup/memory/memory.pressure_level", O_CLOEXEC | O_RDONLY);
+    if (fd == -1)
+        return false;
+    close(fd);
+
+    fd = open("/sys/fs/cgroup/memory/cgroup.event_control", O_CLOEXEC | O_WRONLY);
+    if (fd == -1)
+        return false;
+    close(fd);
+
+    return true;
+}
+
+bool MemoryPressureMonitor::isEnabled()
+{
+    static std::once_flag onceFlag;
+    static bool enabled;
+    std::call_once(onceFlag, [] { enabled = !isSystemdMemoryPressureMonitorAvailable(); });
+    return enabled;
+}
+
+MemoryPressureMonitor& MemoryPressureMonitor::singleton()
+{
+    ASSERT(isEnabled());
+    static NeverDestroyed<MemoryPressureMonitor> memoryMonitor;
+    return memoryMonitor;
+}
+
+MemoryPressureMonitor::MemoryPressureMonitor()
+    : m_eventFD(eventfd(0, EFD_CLOEXEC))
+{
+    if (m_eventFD == -1)
+        return;
+
+    ThreadIdentifier threadIdentifier = createThread("MemoryPressureMonitor", [this] {
+        double pollInterval = s_maxPollingIntervalInSeconds;
+        while (true) {
+            sleep(pollInterval);
+
+            int usedPercentage = systemMemoryUsedAsPercentage();
+            if (usedPercentage >= s_memoryPresurePercentageThreshold) {
+                uint64_t fdEvent = 1;
+                ssize_t bytesWritten = write(m_eventFD, &fdEvent, sizeof(uint64_t));
+                if (bytesWritten != sizeof(uint64_t)) {
+                    WTFLogAlways("Error writing to MemoryPressureMonitor eventFD: %s", strerror(errno));
+                    break;
+                }
+            }
+            pollInterval = pollIntervalForUsedMemoryPercentage(usedPercentage);
+        }
+        close(m_eventFD);
+    });
+    detachThread(threadIdentifier);
+}
+
+IPC::Attachment MemoryPressureMonitor::createHandle() const
+{
+    int duplicatedHandle = dupCloseOnExec(m_eventFD);
+    if (duplicatedHandle == -1)
+        return { };
+    return IPC::Attachment(duplicatedHandle);
+}
+
+} // namespace WebKit
+
+#endif // OS(LINUX)
diff --git a/Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.h b/Source/WebKit2/UIProcess/linux/MemoryPressureMonitor.h
new file mode 100644 (file)
index 0000000..3874d90
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if OS(LINUX)
+
+#include <wtf/NeverDestroyed.h>
+#include <wtf/Noncopyable.h>
+
+namespace IPC {
+class Attachment;
+}
+
+namespace WebKit {
+
+class MemoryPressureMonitor {
+    WTF_MAKE_NONCOPYABLE(MemoryPressureMonitor);
+    friend class NeverDestroyed<MemoryPressureMonitor>;
+public:
+    static MemoryPressureMonitor& singleton();
+    static bool isEnabled();
+
+    ~MemoryPressureMonitor();
+
+    IPC::Attachment createHandle() const;
+
+private:
+    MemoryPressureMonitor();
+
+    int m_eventFD { -1 };
+};
+
+} // namespace WebKit
+
+#endif // OS(LINUX)
index 7f6fae6..ac3e1cf 100644 (file)
@@ -267,7 +267,9 @@ void WebProcess::initializeWebProcess(WebProcessCreationParameters&& parameters)
     ASSERT(m_pageMap.isEmpty());
 
 #if OS(LINUX)
-    WebCore::MemoryPressureHandler::ReliefLogger::setLoggingEnabled(parameters.shouldEnableMemoryPressureReliefLogging);
+    if (parameters.memoryPressureMonitorHandle.fileDescriptor() != -1)
+        MemoryPressureHandler::singleton().setMemoryPressureMonitorHandle(parameters.memoryPressureMonitorHandle.releaseFileDescriptor());
+    MemoryPressureHandler::ReliefLogger::setLoggingEnabled(parameters.shouldEnableMemoryPressureReliefLogging);
 #endif
 
     platformInitializeWebProcess(WTFMove(parameters));