widget/gonk/GonkMemoryPressureMonitoring.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,290 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "GonkMemoryPressureMonitoring.h"
    1.11 +#include "mozilla/ArrayUtils.h"
    1.12 +#include "mozilla/FileUtils.h"
    1.13 +#include "mozilla/Monitor.h"
    1.14 +#include "mozilla/Preferences.h"
    1.15 +#include "mozilla/ProcessPriorityManager.h"
    1.16 +#include "mozilla/Services.h"
    1.17 +#include "nsIObserver.h"
    1.18 +#include "nsIObserverService.h"
    1.19 +#include "nsMemoryPressure.h"
    1.20 +#include "nsThreadUtils.h"
    1.21 +#include <errno.h>
    1.22 +#include <fcntl.h>
    1.23 +#include <poll.h>
    1.24 +#include <android/log.h>
    1.25 +
    1.26 +#define LOG(args...)  \
    1.27 +  __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
    1.28 +
    1.29 +#ifdef MOZ_NUWA_PROCESS
    1.30 +#include "ipc/Nuwa.h"
    1.31 +#endif
    1.32 +
    1.33 +using namespace mozilla;
    1.34 +
    1.35 +namespace {
    1.36 +
    1.37 +/**
    1.38 + * MemoryPressureWatcher watches sysfs from its own thread to notice when the
    1.39 + * system is under memory pressure.  When we observe memory pressure, we use
    1.40 + * MemoryPressureRunnable to notify observers that they should release memory.
    1.41 + *
    1.42 + * When the system is under memory pressure, we don't want to constantly fire
    1.43 + * memory-pressure events.  So instead, we try to detect when sysfs indicates
    1.44 + * that we're no longer under memory pressure, and only then start firing events
    1.45 + * again.
    1.46 + *
    1.47 + * (This is a bit problematic because we can't poll() to detect when we're no
    1.48 + * longer under memory pressure; instead we have to periodically read the sysfs
    1.49 + * node.  If we remain under memory pressure for a long time, this means we'll
    1.50 + * continue waking up to read from the node for a long time, potentially wasting
    1.51 + * battery life.  Hopefully we don't hit this case in practice!  We write to
    1.52 + * logcat each time we go around this loop so it's at least noticable.)
    1.53 + *
    1.54 + * Shutting down safely is a bit of a chore.  XPCOM won't shut down until all
    1.55 + * threads exit, so we need to exit the Run() method below on shutdown.  But our
    1.56 + * thread might be blocked in one of two situations: We might be poll()'ing the
    1.57 + * sysfs node waiting for memory pressure to occur, or we might be asleep
    1.58 + * waiting to read() the sysfs node to see if we're no longer under memory
    1.59 + * pressure.
    1.60 + *
    1.61 + * To let us wake up from the poll(), we poll() not just the sysfs node but also
    1.62 + * a pipe, which we write to on shutdown.  To let us wake up from sleeping
    1.63 + * between read()s, we sleep by Wait()'ing on a monitor, which we notify on
    1.64 + * shutdown.
    1.65 + */
    1.66 +class MemoryPressureWatcher
    1.67 +  : public nsIRunnable
    1.68 +  , public nsIObserver
    1.69 +{
    1.70 +public:
    1.71 +  MemoryPressureWatcher()
    1.72 +    : mMonitor("MemoryPressureWatcher")
    1.73 +    , mShuttingDown(false)
    1.74 +  {
    1.75 +  }
    1.76 +
    1.77 +  NS_DECL_THREADSAFE_ISUPPORTS
    1.78 +
    1.79 +  nsresult Init()
    1.80 +  {
    1.81 +    nsCOMPtr<nsIObserverService> os = services::GetObserverService();
    1.82 +    NS_ENSURE_STATE(os);
    1.83 +
    1.84 +    // The observer service holds us alive.
    1.85 +    os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* holdsWeak */ false);
    1.86 +
    1.87 +    // While we're under memory pressure, we periodically read()
    1.88 +    // notify_trigger_active to try and see when we're no longer under memory
    1.89 +    // pressure.  mPollMS indicates how many milliseconds we wait between those
    1.90 +    // read()s.
    1.91 +    mPollMS = Preferences::GetUint("gonk.systemMemoryPressureRecoveryPollMS",
    1.92 +                                   /* default */ 5000);
    1.93 +
    1.94 +    int pipes[2];
    1.95 +    NS_ENSURE_STATE(!pipe(pipes));
    1.96 +    mShutdownPipeRead = pipes[0];
    1.97 +    mShutdownPipeWrite = pipes[1];
    1.98 +    return NS_OK;
    1.99 +  }
   1.100 +
   1.101 +  NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
   1.102 +                     const char16_t* aData)
   1.103 +  {
   1.104 +    MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
   1.105 +    LOG("Observed XPCOM shutdown.");
   1.106 +
   1.107 +    MonitorAutoLock lock(mMonitor);
   1.108 +    mShuttingDown = true;
   1.109 +    mMonitor.Notify();
   1.110 +
   1.111 +    int rv;
   1.112 +    do {
   1.113 +      // Write something to the pipe; doesn't matter what.
   1.114 +      uint32_t dummy = 0;
   1.115 +      rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy));
   1.116 +    } while(rv == -1 && errno == EINTR);
   1.117 +
   1.118 +    return NS_OK;
   1.119 +  }
   1.120 +
   1.121 +  NS_IMETHOD Run()
   1.122 +  {
   1.123 +    MOZ_ASSERT(!NS_IsMainThread());
   1.124 +
   1.125 +#ifdef MOZ_NUWA_PROCESS
   1.126 +    if (IsNuwaProcess()) {
   1.127 +      NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
   1.128 +                   "NuwaMarkCurrentThread is undefined!");
   1.129 +      NuwaMarkCurrentThread(nullptr, nullptr);
   1.130 +    }
   1.131 +#endif
   1.132 +
   1.133 +    int lowMemFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
   1.134 +                        O_RDONLY | O_CLOEXEC);
   1.135 +    NS_ENSURE_STATE(lowMemFd != -1);
   1.136 +    ScopedClose autoClose(lowMemFd);
   1.137 +
   1.138 +    nsresult rv = CheckForMemoryPressure(lowMemFd, nullptr);
   1.139 +    NS_ENSURE_SUCCESS(rv, rv);
   1.140 +
   1.141 +    while (true) {
   1.142 +      // Wait for a notification on lowMemFd or for data to be written to
   1.143 +      // mShutdownPipeWrite.  (poll(lowMemFd, POLLPRI) blocks until we're under
   1.144 +      // memory pressure.)
   1.145 +      struct pollfd pollfds[2];
   1.146 +      pollfds[0].fd = lowMemFd;
   1.147 +      pollfds[0].events = POLLPRI;
   1.148 +      pollfds[1].fd = mShutdownPipeRead;
   1.149 +      pollfds[1].events = POLLIN;
   1.150 +
   1.151 +      int pollRv;
   1.152 +      do {
   1.153 +        pollRv = poll(pollfds, ArrayLength(pollfds), /* timeout */ -1);
   1.154 +      } while (pollRv == -1 && errno == EINTR);
   1.155 +
   1.156 +      if (pollfds[1].revents) {
   1.157 +        // Something was written to our shutdown pipe; we're outta here.
   1.158 +        LOG("shutting down (1)");
   1.159 +        return NS_OK;
   1.160 +      }
   1.161 +
   1.162 +      // If pollfds[1] isn't happening, pollfds[0] ought to be!
   1.163 +      if (!(pollfds[0].revents & POLLPRI)) {
   1.164 +        LOG("Unexpected revents value after poll(): %d. "
   1.165 +            "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents);
   1.166 +        return NS_ERROR_FAILURE;
   1.167 +      }
   1.168 +
   1.169 +      // POLLPRI on lowMemFd indicates that we're in a low-memory situation.  We
   1.170 +      // could read lowMemFd to double-check, but we've observed that the read
   1.171 +      // sometimes completes after the memory-pressure event is over, so let's
   1.172 +      // just believe the result of poll().
   1.173 +
   1.174 +      // We use low-memory-no-forward because each process has its own watcher
   1.175 +      // and thus there is no need for the main process to forward this event.
   1.176 +      rv = DispatchMemoryPressure(MemPressure_New);
   1.177 +      NS_ENSURE_SUCCESS(rv, rv);
   1.178 +
   1.179 +      // Manually check lowMemFd until we observe that memory pressure is over.
   1.180 +      // We won't fire any more low-memory events until we observe that
   1.181 +      // we're no longer under pressure. Instead, we fire low-memory-ongoing
   1.182 +      // events, which cause processes to keep flushing caches but will not
   1.183 +      // trigger expensive GCs and other attempts to save memory that are
   1.184 +      // likely futile at this point.
   1.185 +      bool memoryPressure;
   1.186 +      do {
   1.187 +        {
   1.188 +          MonitorAutoLock lock(mMonitor);
   1.189 +
   1.190 +          // We need to check mShuttingDown before we wait here, in order to
   1.191 +          // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead
   1.192 +          // above but before we started waiting on the monitor.  But we don't
   1.193 +          // need to check after we wait, because we'll either do another
   1.194 +          // iteration of this inner loop, in which case we'll check
   1.195 +          // mShuttingDown, or we'll exit this loop and do another iteration
   1.196 +          // of the outer loop, in which case we'll check the shutdown pipe.
   1.197 +          if (mShuttingDown) {
   1.198 +            LOG("shutting down (2)");
   1.199 +            return NS_OK;
   1.200 +          }
   1.201 +          mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
   1.202 +        }
   1.203 +
   1.204 +        LOG("Checking to see if memory pressure is over.");
   1.205 +        rv = CheckForMemoryPressure(lowMemFd, &memoryPressure);
   1.206 +        NS_ENSURE_SUCCESS(rv, rv);
   1.207 +
   1.208 +        if (memoryPressure) {
   1.209 +          rv = DispatchMemoryPressure(MemPressure_Ongoing);
   1.210 +          NS_ENSURE_SUCCESS(rv, rv);
   1.211 +          continue;
   1.212 +        }
   1.213 +      } while (false);
   1.214 +
   1.215 +      LOG("Memory pressure is over.");
   1.216 +    }
   1.217 +
   1.218 +    return NS_OK;
   1.219 +  }
   1.220 +
   1.221 +private:
   1.222 +  /**
   1.223 +   * Read from aLowMemFd, which we assume corresponds to the
   1.224 +   * notify_trigger_active sysfs node, and determine whether we're currently
   1.225 +   * under memory pressure.
   1.226 +   *
   1.227 +   * We don't expect this method to block.
   1.228 +   */
   1.229 +  nsresult CheckForMemoryPressure(int aLowMemFd, bool* aOut)
   1.230 +  {
   1.231 +    if (aOut) {
   1.232 +      *aOut = false;
   1.233 +    }
   1.234 +
   1.235 +    lseek(aLowMemFd, 0, SEEK_SET);
   1.236 +
   1.237 +    char buf[2];
   1.238 +    int nread;
   1.239 +    do {
   1.240 +      nread = read(aLowMemFd, buf, sizeof(buf));
   1.241 +    } while(nread == -1 && errno == EINTR);
   1.242 +    NS_ENSURE_STATE(nread == 2);
   1.243 +
   1.244 +    // The notify_trigger_active sysfs node should contain either "0\n" or
   1.245 +    // "1\n".  The latter indicates memory pressure.
   1.246 +    if (aOut) {
   1.247 +      *aOut = buf[0] == '1' && buf[1] == '\n';
   1.248 +    }
   1.249 +    return NS_OK;
   1.250 +  }
   1.251 +
   1.252 +  /**
   1.253 +   * Dispatch the specified memory pressure event unless a high-priority
   1.254 +   * process is present. If a high-priority process is present then it's likely
   1.255 +   * responding to an urgent event (an incoming call or message for example) so
   1.256 +   * avoid wasting CPU time responding to low-memory events.
   1.257 +   */
   1.258 +  nsresult DispatchMemoryPressure(MemoryPressureState state)
   1.259 +  {
   1.260 +    if (ProcessPriorityManager::AnyProcessHasHighPriority()) {
   1.261 +      return NS_OK;
   1.262 +    }
   1.263 +
   1.264 +    return NS_DispatchMemoryPressure(state);
   1.265 +  }
   1.266 +
   1.267 +  Monitor mMonitor;
   1.268 +  uint32_t mPollMS;
   1.269 +  bool mShuttingDown;
   1.270 +
   1.271 +  ScopedClose mShutdownPipeRead;
   1.272 +  ScopedClose mShutdownPipeWrite;
   1.273 +};
   1.274 +
   1.275 +NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver);
   1.276 +
   1.277 +} // anonymous namespace
   1.278 +
   1.279 +namespace mozilla {
   1.280 +
   1.281 +void
   1.282 +InitGonkMemoryPressureMonitoring()
   1.283 +{
   1.284 +  // memoryPressureWatcher is held alive by the observer service.
   1.285 +  nsRefPtr<MemoryPressureWatcher> memoryPressureWatcher =
   1.286 +    new MemoryPressureWatcher();
   1.287 +  NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init());
   1.288 +
   1.289 +  nsCOMPtr<nsIThread> thread;
   1.290 +  NS_NewThread(getter_AddRefs(thread), memoryPressureWatcher);
   1.291 +}
   1.292 +
   1.293 +} // namespace mozilla

mercurial