low_memory_listener.cc revision 868fa2fe829687343ffae624259930155e16dbd8
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "chromeos/memory/low_memory_listener.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <fcntl.h> 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/chromeos/chromeos_version.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/message_loop.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/time.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/timer.h" 15c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "chromeos/memory/low_memory_listener_delegate.h" 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/browser_thread.h" 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/zygote_host_linux.h" 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::BrowserThread; 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace chromeos { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// This is the file that will exist if low memory notification is available 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// on the device. Whenever it becomes readable, it signals a low memory 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// condition. 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kLowMemFile[] = "/dev/chromeos-low-mem"; 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// This is the minimum amount of time in milliseconds between checks for 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// low memory. 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kLowMemoryCheckTimeoutMs = 750; 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//////////////////////////////////////////////////////////////////////////////// 35c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// LowMemoryListenerImpl 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Does the actual work of observing. The observation work happens on the FILE 38c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// thread, and notification happens on the UI thread. If low memory is 39c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// detected, then we notify, wait kLowMemoryCheckTimeoutMs milliseconds and then 40c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// start watching again to see if we're still in a low memory state. This is to 41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// keep from sending out multiple notifications before the UI has a chance to 42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// respond (it may take the UI a while to actually deallocate memory). A timer 43c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// isn't the perfect solution, but without any reliable indicator that a tab has 44c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// had all its parts deallocated, it's the next best thing. 45c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)class LowMemoryListenerImpl 46c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) : public base::RefCountedThreadSafe<LowMemoryListenerImpl> { 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 48c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) LowMemoryListenerImpl() : watcher_delegate_(this), file_descriptor_(-1) {} 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Start watching the low memory file for readability. 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Calls to StartObserving should always be matched with calls to 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // StopObserving. This method should only be called from the FILE thread. 53c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) // |low_memory_callback| is run when memory is low. 54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) void StartObservingOnFileThread(const base::Closure& low_memory_callback); 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Stop watching the low memory file for readability. 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // May be safely called if StartObserving has not been called. 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // This method should only be called from the FILE thread. 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void StopObservingOnFileThread(); 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 62c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) friend class base::RefCountedThreadSafe<LowMemoryListenerImpl>; 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 64c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) ~LowMemoryListenerImpl() { 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StopObservingOnFileThread(); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Start a timer to resume watching the low memory file descriptor. 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void ScheduleNextObservation(); 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Actually start watching the file descriptor. 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void StartWatchingDescriptor(); 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Delegate to receive events from WatchFileDescriptor. 75868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) class FileWatcherDelegate : public base::MessageLoopForIO::Watcher { 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 77c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) explicit FileWatcherDelegate(LowMemoryListenerImpl* owner) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) : owner_(owner) {} 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual ~FileWatcherDelegate() {} 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Overrides for MessageLoopForIO::Watcher 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {} 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) LOG(WARNING) << "Low memory condition detected. Discarding a tab."; 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We can only discard tabs on the UI thread. 86c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) BrowserThread::PostTask(BrowserThread::UI, 87c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) FROM_HERE, 88c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) owner_->low_memory_callback_); 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) owner_->ScheduleNextObservation(); 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 93c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) LowMemoryListenerImpl* owner_; 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(FileWatcherDelegate); 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 97868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> watcher_; 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileWatcherDelegate watcher_delegate_; 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int file_descriptor_; 100c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::OneShotTimer<LowMemoryListenerImpl> timer_; 101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Closure low_memory_callback_; 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(LowMemoryListenerImpl); 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 106c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListenerImpl::StartObservingOnFileThread( 107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) const base::Closure& low_memory_callback) { 108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) low_memory_callback_ = low_memory_callback; 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_LE(file_descriptor_, 0) 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) << "Attempted to start observation when it was already started."; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(watcher_.get() == NULL); 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 113868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) DCHECK(base::MessageLoopForIO::current()); 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_descriptor_ = ::open(kLowMemFile, O_RDONLY); 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Don't report this error unless we're really running on ChromeOS 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // to avoid testing spam. 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (file_descriptor_ < 0 && base::chromeos::IsRunningOnChromeOS()) { 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) PLOG(ERROR) << "Unable to open " << kLowMemFile; 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 122868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher); 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StartWatchingDescriptor(); 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 126c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListenerImpl::StopObservingOnFileThread() { 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If StartObserving failed, StopObserving will still get called. 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timer_.Stop(); 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (file_descriptor_ >= 0) { 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) watcher_.reset(NULL); 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ::close(file_descriptor_); 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_descriptor_ = -1; 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListenerImpl::ScheduleNextObservation() { 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timer_.Start(FROM_HERE, 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::TimeDelta::FromMilliseconds(kLowMemoryCheckTimeoutMs), 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this, 141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) &LowMemoryListenerImpl::StartWatchingDescriptor); 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 144c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListenerImpl::StartWatchingDescriptor() { 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(watcher_.get()); 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 147868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) DCHECK(base::MessageLoopForIO::current()); 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (file_descriptor_ < 0) 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 150868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!base::MessageLoopForIO::current()->WatchFileDescriptor( 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_descriptor_, 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) false, // persistent=false: We want it to fire once and reschedule. 153868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::MessageLoopForIO::WATCH_READ, 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) watcher_.get(), 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) &watcher_delegate_)) { 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) LOG(ERROR) << "Unable to watch " << kLowMemFile; 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)//////////////////////////////////////////////////////////////////////////////// 161c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)// LowMemoryListener 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 163c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)LowMemoryListener::LowMemoryListener(LowMemoryListenerDelegate* delegate) 164c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) : observer_(new LowMemoryListenerImpl), 165c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) delegate_(delegate), 166c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) weak_factory_(this) { 167c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 169c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)LowMemoryListener::~LowMemoryListener() { 170c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) Stop(); 171c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListener::Start() { 174c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Closure memory_low_callback = 175c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Bind(&LowMemoryListener::OnMemoryLow, weak_factory_.GetWeakPtr()); 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) BrowserThread::PostTask( 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) BrowserThread::FILE, 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 179c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Bind(&LowMemoryListenerImpl::StartObservingOnFileThread, 180c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) observer_.get(), 181c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) memory_low_callback)); 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 184c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListener::Stop() { 185c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) weak_factory_.InvalidateWeakPtrs(); 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) BrowserThread::PostTask( 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) BrowserThread::FILE, 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 189c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) base::Bind(&LowMemoryListenerImpl::StopObservingOnFileThread, 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) observer_.get())); 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 193c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)void LowMemoryListener::OnMemoryLow() { 194c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 195c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) delegate_->OnMemoryLow(); 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace chromeos 199