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