1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chromeos/memory/low_memory_listener.h"
6
7#include <fcntl.h>
8
9#include "base/bind.h"
10#include "base/chromeos/chromeos_version.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/message_loop/message_loop.h"
13#include "base/time/time.h"
14#include "base/timer/timer.h"
15#include "chromeos/memory/low_memory_listener_delegate.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/zygote_host_linux.h"
18
19using content::BrowserThread;
20
21namespace chromeos {
22
23namespace {
24// This is the file that will exist if low memory notification is available
25// on the device.  Whenever it becomes readable, it signals a low memory
26// condition.
27const char kLowMemFile[] = "/dev/chromeos-low-mem";
28
29// This is the minimum amount of time in milliseconds between checks for
30// low memory.
31const int kLowMemoryCheckTimeoutMs = 750;
32}  // namespace
33
34////////////////////////////////////////////////////////////////////////////////
35// LowMemoryListenerImpl
36//
37// Does the actual work of observing.  The observation work happens on the FILE
38// thread, and notification happens on the UI thread.  If low memory is
39// detected, then we notify, wait kLowMemoryCheckTimeoutMs milliseconds and then
40// start watching again to see if we're still in a low memory state.  This is to
41// keep from sending out multiple notifications before the UI has a chance to
42// respond (it may take the UI a while to actually deallocate memory). A timer
43// isn't the perfect solution, but without any reliable indicator that a tab has
44// had all its parts deallocated, it's the next best thing.
45class LowMemoryListenerImpl
46    : public base::RefCountedThreadSafe<LowMemoryListenerImpl> {
47 public:
48  LowMemoryListenerImpl() : watcher_delegate_(this), file_descriptor_(-1) {}
49
50  // Start watching the low memory file for readability.
51  // Calls to StartObserving should always be matched with calls to
52  // StopObserving.  This method should only be called from the FILE thread.
53  // |low_memory_callback| is run when memory is low.
54  void StartObservingOnFileThread(const base::Closure& low_memory_callback);
55
56  // Stop watching the low memory file for readability.
57  // May be safely called if StartObserving has not been called.
58  // This method should only be called from the FILE thread.
59  void StopObservingOnFileThread();
60
61 private:
62  friend class base::RefCountedThreadSafe<LowMemoryListenerImpl>;
63
64  ~LowMemoryListenerImpl() {
65    StopObservingOnFileThread();
66  }
67
68  // Start a timer to resume watching the low memory file descriptor.
69  void ScheduleNextObservation();
70
71  // Actually start watching the file descriptor.
72  void StartWatchingDescriptor();
73
74  // Delegate to receive events from WatchFileDescriptor.
75  class FileWatcherDelegate : public base::MessageLoopForIO::Watcher {
76   public:
77    explicit FileWatcherDelegate(LowMemoryListenerImpl* owner)
78        : owner_(owner) {}
79    virtual ~FileWatcherDelegate() {}
80
81    // Overrides for MessageLoopForIO::Watcher
82    virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {}
83    virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
84      LOG(WARNING) << "Low memory condition detected.  Discarding a tab.";
85      // We can only discard tabs on the UI thread.
86      BrowserThread::PostTask(BrowserThread::UI,
87                              FROM_HERE,
88                              owner_->low_memory_callback_);
89      owner_->ScheduleNextObservation();
90    }
91
92   private:
93    LowMemoryListenerImpl* owner_;
94    DISALLOW_COPY_AND_ASSIGN(FileWatcherDelegate);
95  };
96
97  scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> watcher_;
98  FileWatcherDelegate watcher_delegate_;
99  int file_descriptor_;
100  base::OneShotTimer<LowMemoryListenerImpl> timer_;
101  base::Closure low_memory_callback_;
102
103  DISALLOW_COPY_AND_ASSIGN(LowMemoryListenerImpl);
104};
105
106void LowMemoryListenerImpl::StartObservingOnFileThread(
107    const base::Closure& low_memory_callback) {
108  low_memory_callback_ = low_memory_callback;
109  DCHECK_LE(file_descriptor_, 0)
110      << "Attempted to start observation when it was already started.";
111  DCHECK(watcher_.get() == NULL);
112  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
113  DCHECK(base::MessageLoopForIO::current());
114
115  file_descriptor_ = ::open(kLowMemFile, O_RDONLY);
116  // Don't report this error unless we're really running on ChromeOS
117  // to avoid testing spam.
118  if (file_descriptor_ < 0 && base::chromeos::IsRunningOnChromeOS()) {
119    PLOG(ERROR) << "Unable to open " << kLowMemFile;
120    return;
121  }
122  watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher);
123  StartWatchingDescriptor();
124}
125
126void LowMemoryListenerImpl::StopObservingOnFileThread() {
127  // If StartObserving failed, StopObserving will still get called.
128  timer_.Stop();
129  if (file_descriptor_ >= 0) {
130    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
131    watcher_.reset(NULL);
132    ::close(file_descriptor_);
133    file_descriptor_ = -1;
134  }
135}
136
137void LowMemoryListenerImpl::ScheduleNextObservation() {
138  timer_.Start(FROM_HERE,
139               base::TimeDelta::FromMilliseconds(kLowMemoryCheckTimeoutMs),
140               this,
141               &LowMemoryListenerImpl::StartWatchingDescriptor);
142}
143
144void LowMemoryListenerImpl::StartWatchingDescriptor() {
145  DCHECK(watcher_.get());
146  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
147  DCHECK(base::MessageLoopForIO::current());
148  if (file_descriptor_ < 0)
149    return;
150  if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
151          file_descriptor_,
152          false,  // persistent=false: We want it to fire once and reschedule.
153          base::MessageLoopForIO::WATCH_READ,
154          watcher_.get(),
155          &watcher_delegate_)) {
156    LOG(ERROR) << "Unable to watch " << kLowMemFile;
157  }
158}
159
160////////////////////////////////////////////////////////////////////////////////
161// LowMemoryListener
162
163LowMemoryListener::LowMemoryListener(LowMemoryListenerDelegate* delegate)
164    : observer_(new LowMemoryListenerImpl),
165      delegate_(delegate),
166      weak_factory_(this) {
167}
168
169LowMemoryListener::~LowMemoryListener() {
170  Stop();
171}
172
173void LowMemoryListener::Start() {
174  base::Closure memory_low_callback =
175      base::Bind(&LowMemoryListener::OnMemoryLow, weak_factory_.GetWeakPtr());
176  BrowserThread::PostTask(
177      BrowserThread::FILE,
178      FROM_HERE,
179      base::Bind(&LowMemoryListenerImpl::StartObservingOnFileThread,
180                 observer_.get(),
181                 memory_low_callback));
182}
183
184void LowMemoryListener::Stop() {
185  weak_factory_.InvalidateWeakPtrs();
186  BrowserThread::PostTask(
187      BrowserThread::FILE,
188      FROM_HERE,
189      base::Bind(&LowMemoryListenerImpl::StopObservingOnFileThread,
190                 observer_.get()));
191}
192
193void LowMemoryListener::OnMemoryLow() {
194  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
195  delegate_->OnMemoryLow();
196}
197
198}  // namespace chromeos
199