Bug 970895: Use I/O loop for polling memory-pressure events, r=dhylands
authorThomas Zimmermann <tdz@users.sourceforge.net>
Mon, 17 Feb 2014 12:29:26 +0100
changeset 169134 e9055e7476f1
parent 169119 6f957a000186
child 169135 d01667dc2bc3
push id26234
push userphilringnalda@gmail.com
push date2014-02-17 23:16 +0000
treeherdermozilla-central@6b6450d3fbf0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdhylands
bugs970895
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 970895: Use I/O loop for polling memory-pressure events, r=dhylands The code for polling sysfs for memory-pressure events currently runs on a separate thread. This patch implements this functionality for the I/O thread. This unifies the code base a bit and also safes some resources.
widget/gonk/GonkMemoryPressureMonitoring.cpp
--- a/widget/gonk/GonkMemoryPressureMonitoring.cpp
+++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp
@@ -1,274 +1,383 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GonkMemoryPressureMonitoring.h"
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/FileUtils.h"
-#include "mozilla/Monitor.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsMemoryPressure.h"
-#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "base/message_loop.h"
 #include <errno.h>
 #include <fcntl.h>
-#include <poll.h>
 #include <android/log.h>
 
 #define LOG(args...)  \
   __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
 
-#ifdef MOZ_NUWA_PROCESS
-#include "ipc/Nuwa.h"
-#endif
-
 using namespace mozilla;
 
 namespace {
 
-/**
- * MemoryPressureWatcher watches sysfs from its own thread to notice when the
- * system is under memory pressure.  When we observe memory pressure, we use
- * MemoryPressureRunnable to notify observers that they should release memory.
- *
- * When the system is under memory pressure, we don't want to constantly fire
- * memory-pressure events.  So instead, we try to detect when sysfs indicates
- * that we're no longer under memory pressure, and only then start firing events
- * again.
- *
- * (This is a bit problematic because we can't poll() to detect when we're no
- * longer under memory pressure; instead we have to periodically read the sysfs
- * node.  If we remain under memory pressure for a long time, this means we'll
- * continue waking up to read from the node for a long time, potentially wasting
- * battery life.  Hopefully we don't hit this case in practice!  We write to
- * logcat each time we go around this loop so it's at least noticable.)
- *
- * Shutting down safely is a bit of a chore.  XPCOM won't shut down until all
- * threads exit, so we need to exit the Run() method below on shutdown.  But our
- * thread might be blocked in one of two situations: We might be poll()'ing the
- * sysfs node waiting for memory pressure to occur, or we might be asleep
- * waiting to read() the sysfs node to see if we're no longer under memory
- * pressure.
- *
- * To let us wake up from the poll(), we poll() not just the sysfs node but also
- * a pipe, which we write to on shutdown.  To let us wake up from sleeping
- * between read()s, we sleep by Wait()'ing on a monitor, which we notify on
- * shutdown.
- */
-class MemoryPressureWatcher
-  : public nsIRunnable
-  , public nsIObserver
+//
+// MemoryPressureWatcher watches on the I/O thread for changes to the
+// lowmemkiller's sysfs interface. If the system runs low on memory,
+// MemoryPressureWatcher sends a MemoryPressureEvent, removes itself
+// from the I/O loop, and schedules a PollTask to re-start polling
+// after a timeout has been reached.
+//
+// The PollTask is allocated by MemoryPressureWatcher and handed over
+// to the I/O loop, which then owns the object and deletes it after it
+// ran. We cannot allocate the object dynamically, because the system
+// is already low on memory. Instead we overload the new and delete
+// operators for PollTask to hand-out statically allocated memory.
+// There can only be at most one instance of PollTask at a time, so
+// we can re-use the same memory on each allocation.
+//
+// There is a separate observer for shutdown events. When the system
+// shuts down, it sends a task to the I/O thread for removing the
+// watcher. If a PollTask is pending, it gets canceled. We cannot
+// delete it at this point, because it's owned by the I/O loop.
+//
+class MemoryPressureWatcher : public MessageLoopForIO::Watcher
 {
 public:
-  MemoryPressureWatcher()
-    : mMonitor("MemoryPressureWatcher")
-    , mShuttingDown(false)
+  class PollTask : public CancelableTask
   {
+  public:
+    PollTask(MemoryPressureWatcher* aWatcher)
+    : mWatcher(aWatcher)
+    {
+      MOZ_ASSERT(mWatcher);
+    }
+
+    static void* operator new(size_t aSize);
+    static void operator delete(void* aMem, size_t aSize);
+
+    void Run() MOZ_OVERRIDE
+    {
+      MOZ_ASSERT(MessageLoopForIO::current());
+
+      if (mWatcher) {
+        MOZ_ASSERT(MessageLoopForIO::current() == mWatcher->GetIOLoop());
+        mWatcher->StartWatching();
+      }
+    }
+
+    void Cancel() MOZ_OVERRIDE
+    {
+      mWatcher = nullptr;
+    }
+
+  private:
+    MemoryPressureWatcher* mWatcher;
+  };
+
+  template <size_t Size> class PollTaskAllocator
+  {
+  public:
+    void* Alloc()
+    {
+      MOZ_ASSERT(!sAllocated);
+      sAllocated = true;
+      return mMem;
+    }
+    void Release(void* aMem)
+    {
+      MOZ_ASSERT(mMem == aMem);
+      MOZ_ASSERT(sAllocated);
+      sAllocated = false;
+    }
+
+  private:
+    static bool sAllocated;
+    unsigned char mMem[Size];
+  };
+
+  MemoryPressureWatcher(MessageLoop* aIOLoop, uint32_t aPollMS)
+  : mFd(-1)
+  , mIOLoop(aIOLoop)
+  , mPollTask(nullptr)
+  , mPollMS(aPollMS)
+  , mMemoryPressure(false)
+  {
+    MOZ_ASSERT(mIOLoop);
   }
 
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  nsresult Init()
+  virtual ~MemoryPressureWatcher()
   {
-    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-    NS_ENSURE_STATE(os);
-
-    // The observer service holds us alive.
-    os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false);
+    MOZ_ASSERT(MessageLoopForIO::current() == mIOLoop);
+    MOZ_ASSERT(mFd == -1);
+  }
 
-    // While we're under memory pressure, we periodically read()
-    // notify_trigger_active to try and see when we're no longer under memory
-    // pressure.  mPollMS indicates how many milliseconds we wait between those
-    // read()s.
-    mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS",
-                                   /* default */ 5000);
-
-    int pipes[2];
-    NS_ENSURE_STATE(!pipe(pipes));
-    mShutdownPipeRead = pipes[0];
-    mShutdownPipeWrite = pipes[1];
-    return NS_OK;
+  MessageLoop* GetIOLoop () const
+  {
+    return mIOLoop;
   }
 
-  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
-                     const char16_t* aData)
+  nsresult Open()
   {
-    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
-    LOG("Observed XPCOM shutdown.");
+    MOZ_ASSERT(MessageLoopForIO::current() == mIOLoop);
+
+    int fd;
 
-    MonitorAutoLock lock(mMonitor);
-    mShuttingDown = true;
-    mMonitor.Notify();
+    do {
+      fd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
+                O_RDONLY | O_CLOEXEC);
+    } while (fd == -1 && errno == EINTR);
 
-    int rv;
-    do {
-      // Write something to the pipe; doesn't matter what.
-      uint32_t dummy = 0;
-      rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy));
-    } while(rv == -1 && errno == EINTR);
+    if (NS_WARN_IF(fd == -1)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    mFd = fd;
 
     return NS_OK;
   }
 
-  NS_IMETHOD Run()
+  void Close()
   {
-    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(MessageLoopForIO::current() == mIOLoop);
 
-#ifdef MOZ_NUWA_PROCESS
-    if (IsNuwaProcess()) {
-      NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
-                   "NuwaMarkCurrentThread is undefined!");
-      NuwaMarkCurrentThread(nullptr, nullptr);
+    if (NS_WARN_IF(mFd == -1)) {
+      return;
     }
-#endif
+
+    int res;
+
+    do {
+      res = close(mFd);
+    } while (res == -1 && errno == EINTR);
 
-    int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
-                        O_RDONLY | O_CLOEXEC);
-    NS_ENSURE_STATE(lowMemFd != -1);
-    ScopedClose autoClose(lowMemFd);
+    NS_WARN_IF(res == -1);
+    mFd = -1;
+  }
 
-    nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr);
-    NS_ENSURE_SUCCESS(rv, rv);
+  void StartWatching()
+  {
+    MessageLoopForIO* ioLoop = MessageLoopForIO::current();
+    MOZ_ASSERT(ioLoop == mIOLoop);
+    ioLoop->WatchFileDescriptor(mFd, true, MessageLoopForIO::WATCH_READ,
+                                &mReadWatcher, this);
+    mPollTask = nullptr;
+  }
 
-    while (true) {
-      // Wait for a notification on lowMemFd or for data to be written to
-      // mShutdownPipeWrite.  (poll(lowMemFd, POLLPRI) blocks until we're under
-      // memory pressure.)
-      struct pollfd pollfds[2];
-      pollfds[0].fd = lowMemFd;
-      pollfds[0].events = POLLPRI;
-      pollfds[1].fd = mShutdownPipeRead;
-      pollfds[1].events = POLLIN;
+  void StopWatching()
+  {
+    if (mPollTask) {
+      mPollTask->Cancel();
+    }
+    mReadWatcher.StopWatchingFileDescriptor();
+  }
+
+  virtual void OnFileCanWriteWithoutBlocking(int aFd) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(MessageLoopForIO::current() == mIOLoop);
+    NS_WARNING("Must not write to memory monitor");
+  }
 
-      int pollRv;
-      do {
-        pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1);
-      } while (pollRv == -1 && errno == EINTR);
+  virtual void OnFileCanReadWithoutBlocking(int aFd) MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(MessageLoopForIO::current() == mIOLoop);
+
+    bool memoryPressure;
+    nsresult rv = CheckForMemoryPressure(memoryPressure);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
 
-      if (pollfds[1].revents) {
-        // Something was written to our shutdown pipe; we're outta here.
-        LOG("shutting down (1)");
-        return NS_OK;
+    if (memoryPressure) {
+      LOG("Memory pressure detected.");
+      StopWatching();
+      if (mMemoryPressure) {
+        rv = NS_DispatchMemoryPressure(MemPressure_Ongoing);
+      } else {
+        rv = NS_DispatchMemoryPressure(MemPressure_New);
       }
-
-      // If pollfds[1] isn't happening, pollfds[0] ought to be!
-      if (!(pollfds[0].revents & POLLPRI)) {
-        LOG("Unexpected revents value after poll(): %d. "
-            "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents);
-        return NS_ERROR_FAILURE;
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return;
       }
 
-      // POLLPRI on lowMemFd indicates that we're in a low-memory situation.  We
-      // could read lowMemFd to double-check, but we've observed that the read
-      // sometimes completes after the memory-pressure event is over, so let's
-      // just believe the result of poll().
-
-      // We use low-memory-no-forward because each process has its own watcher
-      // and thus there is no need for the main process to forward this event.
-      rv = NS_DispatchMemoryPressure(MemPressure_New);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Manually check lowMemFd until we observe that memory pressure is over.
-      // We won't fire any more low-memory events until we observe that
-      // we're no longer under pressure. Instead, we fire low-memory-ongoing
-      // events, which cause processes to keep flushing caches but will not
-      // trigger expensive GCs and other attempts to save memory that are
-      // likely futile at this point.
-      bool memoryPressure;
-      do {
-        {
-          MonitorAutoLock lock(mMonitor);
-
-          // We need to check mShuttingDown before we wait here, in order to
-          // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead
-          // above but before we started waiting on the monitor.  But we don't
-          // need to check after we wait, because we'll either do another
-          // iteration of this inner loop, in which case we'll check
-          // mShuttingDown, or we'll exit this loop and do another iteration
-          // of the outer loop, in which case we'll check the shutdown pipe.
-          if (mShuttingDown) {
-            LOG("shutting down (2)");
-            return NS_OK;
-          }
-          mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
-        }
-
-        LOG("Checking to see if memory pressure is over.");
-        rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (memoryPressure) {
-          rv = NS_DispatchMemoryPressure(MemPressure_Ongoing);
-          NS_ENSURE_SUCCESS(rv, rv);
-          continue;
-        }
-      } while (false);
-
+      // While we're under memory pressure, we periodically read()
+      // notify_trigger_active to try and see when we're no longer under
+      // memory pressure.  mPollMS indicates how many milliseconds we wait
+      // between those read()s.
+      mPollTask = new PollTask(this);
+      mIOLoop->PostDelayedTask(FROM_HERE, mPollTask, mPollMS);
+    } else if (mMemoryPressure) {
       LOG("Memory pressure is over.");
     }
-
-    return NS_OK;
+    mMemoryPressure = memoryPressure;
   }
 
 private:
   /**
    * Read from aLowMemFd, which we assume corresponds to the
    * notify_trigger_active sysfs node, and determine whether we're currently
    * under memory pressure.
    *
    * We don't expect this method to block.
    */
-  nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut)
+  nsresult CheckForMemoryPressure(bool& aOut)
   {
-    if (aOut) {
-      *aOut = false;
+    aOut = false;
+
+    off_t off = lseek(mFd, 0, SEEK_SET);
+    if (NS_WARN_IF(off)) {
+      return NS_ERROR_UNEXPECTED;
     }
 
-    lseek(aLowMemFd, 0, SEEK_SET);
-
     char buf[2];
     int nread;
     do {
-      nread = read(aLowMemFd, buf, sizeof(buf));
+      nread = read(mFd, buf, sizeof(buf));
     } while(nread == -1 && errno == EINTR);
-    NS_ENSURE_STATE(nread == 2);
+    if (NS_WARN_IF(nread != 2)) {
+      return NS_ERROR_UNEXPECTED;
+    }
 
     // The notify_trigger_active sysfs node should contain either "0\n" or
     // "1\n".  The latter indicates memory pressure.
-    if (aOut) {
-      *aOut = buf[0] == '1' && buf[1] == '\n';
-    }
+    aOut = buf[0] == '1' && buf[1] == '\n';
+
     return NS_OK;
   }
 
-  Monitor mMonitor;
+  int mFd;
+  MessageLoop* mIOLoop;
+  MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
+  PollTask* mPollTask;
   uint32_t mPollMS;
-  bool mShuttingDown;
+  bool mMemoryPressure;
+};
+
+static
+  MemoryPressureWatcher::PollTaskAllocator<sizeof(MemoryPressureWatcher::PollTask)>
+    sPollTaskAllocator;
+
+template<>
+bool
+  MemoryPressureWatcher::PollTaskAllocator<sizeof(MemoryPressureWatcher::PollTask)>::sAllocated(false);
+
+void*
+MemoryPressureWatcher::PollTask::operator new(size_t aSize)
+{
+  return sPollTaskAllocator.Alloc();
+}
+
+void
+MemoryPressureWatcher::PollTask::operator delete(void* aMem, size_t aSize)
+{
+  sPollTaskAllocator.Release(aMem);
+}
 
-  ScopedClose mShutdownPipeRead;
-  ScopedClose mShutdownPipeWrite;
+// Initializes MemoryPressureWatcher on I/O thread
+//
+class InitMemoryPressureWatcherTask : public Task
+{
+public:
+  InitMemoryPressureWatcherTask(MemoryPressureWatcher* aWatcher)
+  : mWatcher(aWatcher)
+  {
+    MOZ_ASSERT(mWatcher);
+  }
+
+  void Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(MessageLoopForIO::current() == mWatcher->GetIOLoop());
+
+    nsresult rv = mWatcher->Open();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+    mWatcher->StartWatching();
+  }
+
+private:
+  MemoryPressureWatcher* mWatcher;
 };
 
-NS_IMPL_ISUPPORTS2(MemoryPressureWatcher, nsIRunnable, nsIObserver);
+// Releases MemoryPressureWatcher on I/O thread
+//
+class ShutdownMemoryPressureWatcherTask : public Task
+{
+public:
+  ShutdownMemoryPressureWatcherTask(MemoryPressureWatcher* aWatcher)
+  : mWatcher(aWatcher)
+  {
+    MOZ_ASSERT(mWatcher);
+  }
+
+  void Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(MessageLoopForIO::current() == mWatcher->GetIOLoop());
+
+    mWatcher->StopWatching();
+    mWatcher->Close();
+  }
+
+private:
+  nsAutoPtr<MemoryPressureWatcher> mWatcher;
+};
+
+// Closes MemoryPressureWatcher on shutdown
+//
+class ShutdownObserver : public nsIObserver
+{
+public:
+  ShutdownObserver(MemoryPressureWatcher* aWatcher)
+  : mWatcher(aWatcher)
+  {
+    MOZ_ASSERT(mWatcher);
+  }
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+                     const char16_t* aData)
+  {
+    MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+    LOG("Observed XPCOM shutdown.");
+
+    Task* task = new ShutdownMemoryPressureWatcherTask(mWatcher);
+    mWatcher->GetIOLoop()->PostTask(FROM_HERE, task);
+
+    return NS_OK;
+  }
+
+private:
+  MemoryPressureWatcher* mWatcher;
+};
+
+NS_IMPL_ISUPPORTS1(ShutdownObserver, nsIObserver);
 
 } // anonymous namespace
 
 namespace mozilla {
 
 void
 InitGonkMemoryPressureMonitoring()
 {
-  // memoryPressureWatcher is held alive by the observer service.
-  nsRefPtr<MemoryPressureWatcher> memoryPressureWatcher =
-    new MemoryPressureWatcher();
-  NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init());
+  MessageLoop* ioLoop = XRE_GetIOMessageLoop();
+  uint32_t pollMS =
+    Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS", 5000);
+  MemoryPressureWatcher* watcher = new MemoryPressureWatcher(ioLoop, pollMS);
 
-  nsCOMPtr<nsIThread> thread;
-  NS_NewThread(getter_AddRefs(thread), memoryPressureWatcher);
+  // Start watcher on I/O thread
+  Task* task = new InitMemoryPressureWatcherTask(watcher);
+  watcher->GetIOLoop()->PostTask(FROM_HERE, task);
+
+  // Install shutdown observer
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (NS_WARN_IF(!os)) {
+    return;
+  }
+  nsRefPtr<ShutdownObserver> observer = new ShutdownObserver(watcher);
+  os->AddObserver(observer, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 }
 
 } // namespace mozilla