Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/file_path_watcher/file_path_watcher.h" | 5 #include "chrome/browser/file_path_watcher/file_path_watcher.h" |
| 6 | 6 |
| 7 #include <CoreServices/CoreServices.h> | 7 #include <CoreServices/CoreServices.h> |
| 8 #include <set> | 8 #include <set> |
| 9 | 9 |
| 10 #include "base/file_path.h" | |
| 11 #include "base/file_util.h" | 10 #include "base/file_util.h" |
| 12 #include "base/logging.h" | 11 #include "base/logging.h" |
| 13 #include "base/mac/scoped_cftyperef.h" | 12 #include "base/mac/scoped_cftyperef.h" |
| 14 #include "base/singleton.h" | 13 #include "base/singleton.h" |
| 15 #include "base/time.h" | 14 #include "base/time.h" |
| 15 #include "content/browser/browser_thread.h" | |
| 16 | 16 |
| 17 namespace { | 17 namespace { |
| 18 | 18 |
| 19 // The latency parameter passed to FSEventsStreamCreate(). | 19 // The latency parameter passed to FSEventsStreamCreate(). |
| 20 const CFAbsoluteTime kEventLatencySeconds = 0.3; | 20 const CFAbsoluteTime kEventLatencySeconds = 0.3; |
| 21 | 21 |
| 22 // Mac-specific file watcher implementation based on the FSEvents API. | 22 // Mac-specific file watcher implementation based on the FSEvents API. |
| 23 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate { | 23 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate, |
| 24 public MessageLoop::DestructionObserver { | |
| 24 public: | 25 public: |
| 25 FilePathWatcherImpl(); | 26 FilePathWatcherImpl(); |
| 26 | 27 |
| 27 // Called from the FSEvents callback whenever there is a change to the paths | 28 // Called from the FSEvents callback whenever there is a change to the paths |
| 28 void OnFilePathChanged(); | 29 void OnFilePathChanged(); |
| 29 | 30 |
| 30 // (Re-)Initialize the event stream to start reporting events from | 31 // (Re-)Initialize the event stream to start reporting events from |
| 31 // |start_event|. | 32 // |start_event|. |
| 32 void UpdateEventStream(FSEventStreamEventId start_event); | 33 void UpdateEventStream(FSEventStreamEventId start_event); |
| 33 | 34 |
| 34 // FilePathWatcher::PlatformDelegate overrides. | 35 // FilePathWatcher::PlatformDelegate overrides. |
| 35 virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); | 36 virtual bool Watch(const FilePath& path, FilePathWatcher::Delegate* delegate); |
| 36 virtual void Cancel(); | 37 virtual void Cancel(); |
| 37 | 38 |
| 39 // MessageLoop::DestructionObserver overrides. | |
| 40 // This allows cleaning up when the UI thread is shutting down. | |
| 41 virtual void WillDestroyCurrentMessageLoop(); | |
| 42 | |
| 38 private: | 43 private: |
| 39 virtual ~FilePathWatcherImpl() {} | 44 virtual ~FilePathWatcherImpl(); |
| 40 | 45 |
| 41 // Destroy the event stream. | 46 // Destroy the event stream. |
| 42 void DestroyEventStream(); | 47 void DestroyEventStream(); |
| 43 | 48 |
| 49 // Start observing the destruction of the UI thread. | |
| 50 void StartObservingUIThread(); | |
| 51 | |
| 44 // Delegate to notify upon changes. | 52 // Delegate to notify upon changes. |
| 45 scoped_refptr<FilePathWatcher::Delegate> delegate_; | 53 scoped_refptr<FilePathWatcher::Delegate> delegate_; |
| 46 | 54 |
| 47 // Target path to watch (passed to delegate). | 55 // Target path to watch (passed to delegate). |
| 48 FilePath target_; | 56 FilePath target_; |
| 49 | 57 |
| 50 // Keep track of the last modified time of the file. We use nulltime | 58 // Keep track of the last modified time of the file. We use nulltime |
| 51 // to represent the file not existing. | 59 // to represent the file not existing. |
| 52 base::Time last_modified_; | 60 base::Time last_modified_; |
| 53 | 61 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 97 NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); | 105 NewRunnableMethod(watcher, &FilePathWatcherImpl::OnFilePathChanged)); |
| 98 } | 106 } |
| 99 | 107 |
| 100 // FilePathWatcherImpl implementation: | 108 // FilePathWatcherImpl implementation: |
| 101 | 109 |
| 102 FilePathWatcherImpl::FilePathWatcherImpl() | 110 FilePathWatcherImpl::FilePathWatcherImpl() |
| 103 : fsevent_stream_(NULL), | 111 : fsevent_stream_(NULL), |
| 104 canceled_(false) { | 112 canceled_(false) { |
| 105 } | 113 } |
| 106 | 114 |
| 115 FilePathWatcherImpl::~FilePathWatcherImpl() { | |
| 116 // Deletion of the FilePathWatcher will call Cancel() to dispose of this | |
| 117 // object in the right thread. In that case, |this| is already cleaned up. | |
| 118 // | |
| 119 // However, Cancel() might have to switch threads and its Task might never be | |
| 120 // called during shutdown. In that case, either |this| gets notified that the | |
| 121 // thread is shutting down and cleans up, or the Task (that wasn't executed) | |
|
Bernhard Bauer
2011/03/15 22:00:07
I guess the first case refers to WillDestroyCurren
Joao da Silva
2011/03/18 16:09:17
Done.
| |
| 122 // is deleted and releases a handle on |this|, triggering the dtor. | |
| 123 // In either case, we are on the right thread. | |
| 124 | |
| 125 DestroyEventStream(); | |
|
Mattias Nissler (ping if slow)
2011/03/16 09:41:14
If I understand correctly, the DestroyEventStream(
Joao da Silva
2011/03/18 16:09:17
This covers the case when Cancel() tries to switch
| |
| 126 } | |
| 127 | |
| 107 void FilePathWatcherImpl::OnFilePathChanged() { | 128 void FilePathWatcherImpl::OnFilePathChanged() { |
| 108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | 129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| 109 DCHECK(!target_.empty()); | 130 DCHECK(!target_.empty()); |
| 110 | 131 |
| 111 base::PlatformFileInfo file_info; | 132 base::PlatformFileInfo file_info; |
| 112 bool file_exists = file_util::GetFileInfo(target_, &file_info); | 133 bool file_exists = file_util::GetFileInfo(target_, &file_info); |
| 113 if (file_exists && (last_modified_.is_null() || | 134 if (file_exists && (last_modified_.is_null() || |
| 114 last_modified_ != file_info.last_modified)) { | 135 last_modified_ != file_info.last_modified)) { |
| 115 last_modified_ = file_info.last_modified; | 136 last_modified_ = file_info.last_modified; |
| 116 first_notification_ = base::Time::Now(); | 137 first_notification_ = base::Time::Now(); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 150 delegate_ = delegate; | 171 delegate_ = delegate; |
| 151 | 172 |
| 152 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); | 173 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); |
| 153 | 174 |
| 154 base::PlatformFileInfo file_info; | 175 base::PlatformFileInfo file_info; |
| 155 if (file_util::GetFileInfo(target_, &file_info)) { | 176 if (file_util::GetFileInfo(target_, &file_info)) { |
| 156 last_modified_ = file_info.last_modified; | 177 last_modified_ = file_info.last_modified; |
| 157 first_notification_ = base::Time::Now(); | 178 first_notification_ = base::Time::Now(); |
| 158 } | 179 } |
| 159 | 180 |
| 181 // Start observing for destruction of the UI thread before starting to | |
| 182 // receive FSEventStream events. | |
| 183 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
| 184 NewRunnableMethod(this, &FilePathWatcherImpl::StartObservingUIThread)); | |
| 185 | |
| 160 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 186 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 161 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, | 187 NewRunnableMethod(this, &FilePathWatcherImpl::UpdateEventStream, |
| 162 start_event)); | 188 start_event)); |
| 163 | 189 |
| 164 return true; | 190 return true; |
| 165 } | 191 } |
| 166 | 192 |
| 167 void FilePathWatcherImpl::Cancel() { | 193 void FilePathWatcherImpl::Cancel() { |
| 168 // Switch to the UI thread if necessary, so we can tear down the event stream. | 194 // Switch to the UI thread if necessary, so we can tear down the event stream. |
| 169 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 195 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| 170 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | 196 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| 171 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); | 197 NewRunnableMethod(this, &FilePathWatcherImpl::Cancel)); |
| 172 return; | 198 return; |
| 173 } | 199 } |
| 174 | 200 |
| 175 canceled_ = true; | 201 DestroyEventStream(); |
| 176 if (fsevent_stream_) | 202 } |
| 177 DestroyEventStream(); | 203 |
| 204 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { | |
| 205 // This should only execute on the UI thread, but DCHECK'ing | |
| 206 // BrowserThread::CurrentlyOn(BrowserThread::UI) won't work because | |
| 207 // BrowserThread::message_loop() might be returning NULL during shutdown. | |
| 208 DestroyEventStream(); | |
| 209 } | |
| 210 | |
| 211 void FilePathWatcherImpl::StartObservingUIThread() { | |
| 212 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 213 MessageLoop::current()->AddDestructionObserver(this); | |
| 178 } | 214 } |
| 179 | 215 |
| 180 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { | 216 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) { |
| 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 182 | 218 |
| 183 // It can happen that the watcher gets canceled while tasks that call this | 219 // It can happen that the watcher gets canceled while tasks that call this |
| 184 // function are still in flight, so abort if this situation is detected. | 220 // function are still in flight, so abort if this situation is detected. |
| 185 if (canceled_) | 221 if (canceled_) |
| 186 return; | 222 return; |
| 187 | 223 |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 212 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 248 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), |
| 213 kCFRunLoopDefaultMode); | 249 kCFRunLoopDefaultMode); |
| 214 if (!FSEventStreamStart(fsevent_stream_)) { | 250 if (!FSEventStreamStart(fsevent_stream_)) { |
| 215 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 251 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 216 NewRunnableMethod(delegate_.get(), | 252 NewRunnableMethod(delegate_.get(), |
| 217 &FilePathWatcher::Delegate::OnError)); | 253 &FilePathWatcher::Delegate::OnError)); |
| 218 } | 254 } |
| 219 } | 255 } |
| 220 | 256 |
| 221 void FilePathWatcherImpl::DestroyEventStream() { | 257 void FilePathWatcherImpl::DestroyEventStream() { |
| 222 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 258 if (canceled_) |
| 223 FSEventStreamStop(fsevent_stream_); | 259 return; |
| 224 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | 260 |
| 225 kCFRunLoopDefaultMode); | 261 canceled_ = true; |
|
Mattias Nissler (ping if slow)
2011/03/16 09:41:14
Hm, I think we have a problem here. DestroyEventSt
Joao da Silva
2011/03/18 16:09:17
Horrible mistake, thanks for spotting. The try bot
| |
| 226 FSEventStreamRelease(fsevent_stream_); | 262 |
| 227 fsevent_stream_ = NULL; | 263 if (fsevent_stream_) { |
| 264 FSEventStreamStop(fsevent_stream_); | |
| 265 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), | |
| 266 kCFRunLoopDefaultMode); | |
| 267 FSEventStreamRelease(fsevent_stream_); | |
| 268 fsevent_stream_ = NULL; | |
| 269 | |
| 270 MessageLoop::current()->RemoveDestructionObserver(this); | |
|
Mattias Nissler (ping if slow)
2011/03/16 09:41:14
Same problem as above, this will kill our destruct
| |
| 271 delegate_ = NULL; | |
| 272 } | |
| 228 } | 273 } |
| 229 | 274 |
| 230 } // namespace | 275 } // namespace |
| 231 | 276 |
| 232 FilePathWatcher::FilePathWatcher() { | 277 FilePathWatcher::FilePathWatcher() { |
| 233 impl_ = new FilePathWatcherImpl(); | 278 impl_ = new FilePathWatcherImpl(); |
| 234 } | 279 } |
| OLD | NEW |