root/chrome/browser/chromeos/memory/low_memory_observer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. file_descriptor_
  2. OnFileCanWriteWithoutBlocking
  3. OnFileCanReadWithoutBlocking
  4. DiscardTab
  5. StartObservingOnFileThread
  6. StopObservingOnFileThread
  7. ScheduleNextObservation
  8. StartWatchingDescriptor
  9. Start
  10. Stop

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/memory/low_memory_observer.h"

#include <fcntl.h>

#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/sys_info.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chromeos/memory/oom_priority_manager.h"
#include "content/public/browser/browser_thread.h"

using content::BrowserThread;

namespace chromeos {

namespace {
// This is the file that will exist if low memory notification is available
// on the device.  Whenever it becomes readable, it signals a low memory
// condition.
const char kLowMemFile[] = "/dev/chromeos-low-mem";

// This is the minimum amount of time in milliseconds between checks for
// low memory.
const int kLowMemoryCheckTimeoutMs = 750;
}  // namespace

////////////////////////////////////////////////////////////////////////////////
// LowMemoryObserverImpl
//
// Does the actual work of observing.  The observation work happens on the FILE
// thread, and the discarding of tabs happens on the UI thread.
// If low memory is detected, then we discard a tab, wait
// kLowMemoryCheckTimeoutMs milliseconds and then start watching again to see
// if we're still in a low memory state.  This is to keep from discarding all
// tabs the first time we enter the state, because it takes time for the
// tabs to deallocate their memory.  A timer isn't the perfect solution, but
// without any reliable indicator that a tab has had all its parts deallocated,
// it's the next best thing.
class LowMemoryObserverImpl
    : public base::RefCountedThreadSafe<LowMemoryObserverImpl> {
 public:
  LowMemoryObserverImpl() : watcher_delegate_(this), file_descriptor_(-1) {}

  // Start watching the low memory file for readability.
  // Calls to StartObserving should always be matched with calls to
  // StopObserving.  This method should only be called from the FILE thread.
  void StartObservingOnFileThread();

  // Stop watching the low memory file for readability.
  // May be safely called if StartObserving has not been called.
  // This method should only be called from the FILE thread.
  void StopObservingOnFileThread();

 private:
  friend class base::RefCountedThreadSafe<LowMemoryObserverImpl>;

  ~LowMemoryObserverImpl() {
    StopObservingOnFileThread();
  }

  // Start a timer to resume watching the low memory file descriptor.
  void ScheduleNextObservation();

  // Actually start watching the file descriptor.
  void StartWatchingDescriptor();

  // Delegate to receive events from WatchFileDescriptor.
  class FileWatcherDelegate : public base::MessageLoopForIO::Watcher {
   public:
    explicit FileWatcherDelegate(LowMemoryObserverImpl* owner)
        : owner_(owner) {}
    virtual ~FileWatcherDelegate() {}

    // Overrides for base::MessageLoopForIO::Watcher
    virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
    virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
      LOG(WARNING) << "Low memory condition detected.  Discarding a tab.";
      // We can only discard tabs on the UI thread.
      base::Callback<void(void)> callback = base::Bind(&DiscardTab);
      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
      owner_->ScheduleNextObservation();
    }

    // Sends off a discard request to the OomPriorityManager.  Must be run on
    // the UI thread.
    static void DiscardTab() {
      CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
      if (g_browser_process &&
          g_browser_process->platform_part()->oom_priority_manager()) {
        g_browser_process->platform_part()->
            oom_priority_manager()->LogMemoryAndDiscardTab();
      }
    }

   private:
    LowMemoryObserverImpl* owner_;
    DISALLOW_COPY_AND_ASSIGN(FileWatcherDelegate);
  };

  scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> watcher_;
  FileWatcherDelegate watcher_delegate_;
  int file_descriptor_;
  base::OneShotTimer<LowMemoryObserverImpl> timer_;

  DISALLOW_COPY_AND_ASSIGN(LowMemoryObserverImpl);
};

void LowMemoryObserverImpl::StartObservingOnFileThread() {
  DCHECK_LE(file_descriptor_, 0)
      << "Attempted to start observation when it was already started.";
  DCHECK(watcher_.get() == NULL);
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  DCHECK(base::MessageLoopForIO::current());

  file_descriptor_ = ::open(kLowMemFile, O_RDONLY);
  // Don't report this error unless we're really running on ChromeOS
  // to avoid testing spam.
  if (file_descriptor_ < 0 && base::SysInfo::IsRunningOnChromeOS()) {
    PLOG(ERROR) << "Unable to open " << kLowMemFile;
    return;
  }
  watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher);
  StartWatchingDescriptor();
}

void LowMemoryObserverImpl::StopObservingOnFileThread() {
  // If StartObserving failed, StopObserving will still get called.
  timer_.Stop();
  if (file_descriptor_ >= 0) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    watcher_.reset(NULL);
    ::close(file_descriptor_);
    file_descriptor_ = -1;
  }
}

void LowMemoryObserverImpl::ScheduleNextObservation() {
  timer_.Start(FROM_HERE,
               base::TimeDelta::FromMilliseconds(kLowMemoryCheckTimeoutMs),
               this,
               &LowMemoryObserverImpl::StartWatchingDescriptor);
}

void LowMemoryObserverImpl::StartWatchingDescriptor() {
  DCHECK(watcher_.get());
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  DCHECK(base::MessageLoopForIO::current());
  if (file_descriptor_ < 0)
    return;
  if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
          file_descriptor_,
          false,  // persistent=false: We want it to fire once and reschedule.
          base::MessageLoopForIO::WATCH_READ,
          watcher_.get(),
          &watcher_delegate_)) {
    LOG(ERROR) << "Unable to watch " << kLowMemFile;
  }
}

////////////////////////////////////////////////////////////////////////////////
// LowMemoryObserver

LowMemoryObserver::LowMemoryObserver() : observer_(new LowMemoryObserverImpl) {}

LowMemoryObserver::~LowMemoryObserver() { Stop(); }

void LowMemoryObserver::Start() {
  BrowserThread::PostTask(
      BrowserThread::FILE,
      FROM_HERE,
      base::Bind(&LowMemoryObserverImpl::StartObservingOnFileThread,
                 observer_.get()));
}

void LowMemoryObserver::Stop() {
  BrowserThread::PostTask(
      BrowserThread::FILE,
      FROM_HERE,
      base::Bind(&LowMemoryObserverImpl::StopObservingOnFileThread,
                 observer_.get()));
}

}  // namespace chromeos

/* [<][>][^][v][top][bottom][index][help] */