Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(138)

Side by Side Diff: chrome/browser/file_path_watcher/file_path_watcher_mac.cc

Issue 6697020: Fixed shutdown concurrency issues in FilePathWatcher. (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Created 9 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698