blob: fee3101b815fc76c516f64a655478dc68f6df135 [file] [log] [blame]
// 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 "chromeos/memory/low_memory_listener.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 "chromeos/memory/low_memory_listener_delegate.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/zygote_host_linux.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
////////////////////////////////////////////////////////////////////////////////
// LowMemoryListenerImpl
//
// Does the actual work of observing. The observation work happens on the FILE
// thread, and notification happens on the UI thread. If low memory is
// detected, then we notify, wait kLowMemoryCheckTimeoutMs milliseconds and then
// start watching again to see if we're still in a low memory state. This is to
// keep from sending out multiple notifications before the UI has a chance to
// respond (it may take the UI a while to actually deallocate 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 LowMemoryListenerImpl
: public base::RefCountedThreadSafe<LowMemoryListenerImpl> {
public:
LowMemoryListenerImpl() : 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.
// |low_memory_callback| is run when memory is low.
void StartObservingOnFileThread(const base::Closure& low_memory_callback);
// 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<LowMemoryListenerImpl>;
~LowMemoryListenerImpl() {
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(LowMemoryListenerImpl* owner)
: owner_(owner) {}
virtual ~FileWatcherDelegate() {}
// Overrides for 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.
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
owner_->low_memory_callback_);
owner_->ScheduleNextObservation();
}
private:
LowMemoryListenerImpl* owner_;
DISALLOW_COPY_AND_ASSIGN(FileWatcherDelegate);
};
scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> watcher_;
FileWatcherDelegate watcher_delegate_;
int file_descriptor_;
base::OneShotTimer<LowMemoryListenerImpl> timer_;
base::Closure low_memory_callback_;
DISALLOW_COPY_AND_ASSIGN(LowMemoryListenerImpl);
};
void LowMemoryListenerImpl::StartObservingOnFileThread(
const base::Closure& low_memory_callback) {
low_memory_callback_ = low_memory_callback;
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 LowMemoryListenerImpl::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 LowMemoryListenerImpl::ScheduleNextObservation() {
timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kLowMemoryCheckTimeoutMs),
this,
&LowMemoryListenerImpl::StartWatchingDescriptor);
}
void LowMemoryListenerImpl::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;
}
}
////////////////////////////////////////////////////////////////////////////////
// LowMemoryListener
LowMemoryListener::LowMemoryListener(LowMemoryListenerDelegate* delegate)
: observer_(new LowMemoryListenerImpl),
delegate_(delegate),
weak_factory_(this) {
}
LowMemoryListener::~LowMemoryListener() {
Stop();
}
void LowMemoryListener::Start() {
base::Closure memory_low_callback =
base::Bind(&LowMemoryListener::OnMemoryLow, weak_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&LowMemoryListenerImpl::StartObservingOnFileThread,
observer_.get(),
memory_low_callback));
}
void LowMemoryListener::Stop() {
weak_factory_.InvalidateWeakPtrs();
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&LowMemoryListenerImpl::StopObservingOnFileThread,
observer_.get()));
}
void LowMemoryListener::OnMemoryLow() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
delegate_->OnMemoryLow();
}
} // namespace chromeos