OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "base/files/file_path_watcher_fsevents.h" | 5 #include "base/files/file_path_watcher_fsevents.h" |
6 | 6 |
7 #include <dispatch/dispatch.h> | 7 #include <dispatch/dispatch.h> |
8 | 8 |
9 #include <list> | 9 #include <list> |
10 | 10 |
11 #include "base/bind.h" | 11 #include "base/bind.h" |
12 #include "base/files/file_util.h" | 12 #include "base/files/file_util.h" |
13 #include "base/lazy_instance.h" | 13 #include "base/lazy_instance.h" |
14 #include "base/logging.h" | 14 #include "base/logging.h" |
15 #include "base/mac/scoped_cftyperef.h" | 15 #include "base/mac/scoped_cftyperef.h" |
16 #include "base/macros.h" | |
17 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" |
18 #include "base/threading/sequenced_task_runner_handle.h" | 17 #include "base/threading/sequenced_task_runner_handle.h" |
19 | 18 |
20 namespace base { | 19 namespace base { |
21 | 20 |
22 namespace { | 21 namespace { |
23 | 22 |
24 // The latency parameter passed to FSEventsStreamCreate(). | 23 // The latency parameter passed to FSEventsStreamCreate(). |
25 const CFAbsoluteTime kEventLatencySeconds = 0.3; | 24 const CFAbsoluteTime kEventLatencySeconds = 0.3; |
26 | 25 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
62 | 61 |
63 if (resolve_count >= kMaxLinksToResolve) | 62 if (resolve_count >= kMaxLinksToResolve) |
64 result.clear(); | 63 result.clear(); |
65 return result; | 64 return result; |
66 } | 65 } |
67 | 66 |
68 } // namespace | 67 } // namespace |
69 | 68 |
70 FilePathWatcherFSEvents::FilePathWatcherFSEvents() | 69 FilePathWatcherFSEvents::FilePathWatcherFSEvents() |
71 : queue_(dispatch_queue_create( | 70 : queue_(dispatch_queue_create( |
72 base::StringPrintf( | 71 base::StringPrintf("org.chromium.base.FilePathWatcher.%p", this) |
73 "org.chromium.base.FilePathWatcher.%p", this).c_str(), | 72 .c_str(), |
74 DISPATCH_QUEUE_SERIAL)), | 73 DISPATCH_QUEUE_SERIAL)), |
75 fsevent_stream_(nullptr) { | 74 fsevent_stream_(nullptr), |
| 75 weak_factory_(this) {} |
| 76 |
| 77 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() { |
| 78 DCHECK(!task_runner() || task_runner()->RunsTasksOnCurrentThread()); |
| 79 DCHECK(callback_.is_null()) |
| 80 << "Cancel() must be called before FilePathWatcher is destroyed."; |
76 } | 81 } |
77 | 82 |
78 bool FilePathWatcherFSEvents::Watch(const FilePath& path, | 83 bool FilePathWatcherFSEvents::Watch(const FilePath& path, |
79 bool recursive, | 84 bool recursive, |
80 const FilePathWatcher::Callback& callback) { | 85 const FilePathWatcher::Callback& callback) { |
81 DCHECK(!callback.is_null()); | 86 DCHECK(!callback.is_null()); |
82 DCHECK(callback_.is_null()); | 87 DCHECK(callback_.is_null()); |
83 | 88 |
84 // This class could support non-recursive watches, but that is currently | 89 // This class could support non-recursive watches, but that is currently |
85 // left to FilePathWatcherKQueue. | 90 // left to FilePathWatcherKQueue. |
(...skipping 12 matching lines...) Expand all Loading... |
98 dispatch_async(queue_, ^{ | 103 dispatch_async(queue_, ^{ |
99 StartEventStream(start_event, path_copy); | 104 StartEventStream(start_event, path_copy); |
100 }); | 105 }); |
101 return true; | 106 return true; |
102 } | 107 } |
103 | 108 |
104 void FilePathWatcherFSEvents::Cancel() { | 109 void FilePathWatcherFSEvents::Cancel() { |
105 set_cancelled(); | 110 set_cancelled(); |
106 callback_.Reset(); | 111 callback_.Reset(); |
107 | 112 |
108 // Switch to the dispatch queue to tear down the event stream. As the queue | 113 // Switch to the dispatch queue to tear down the event stream. As the queue is |
109 // is owned by this object, and this method is called from the destructor, | 114 // owned by |this|, and this method is called from the destructor, execute the |
110 // execute the block synchronously. | 115 // block synchronously. |
111 dispatch_sync(queue_, ^{ | 116 dispatch_sync(queue_, ^{ |
112 CancelOnMessageLoopThread(); | 117 if (fsevent_stream_) { |
| 118 DestroyEventStream(); |
| 119 target_.clear(); |
| 120 resolved_target_.clear(); |
| 121 } |
113 }); | 122 }); |
114 } | 123 } |
115 | 124 |
116 // static | 125 // static |
117 void FilePathWatcherFSEvents::FSEventsCallback( | 126 void FilePathWatcherFSEvents::FSEventsCallback( |
118 ConstFSEventStreamRef stream, | 127 ConstFSEventStreamRef stream, |
119 void* event_watcher, | 128 void* event_watcher, |
120 size_t num_events, | 129 size_t num_events, |
121 void* event_paths, | 130 void* event_paths, |
122 const FSEventStreamEventFlags flags[], | 131 const FSEventStreamEventFlags flags[], |
(...skipping 10 matching lines...) Expand all Loading... |
133 root_change_at = std::min(root_change_at, event_ids[i]); | 142 root_change_at = std::min(root_change_at, event_ids[i]); |
134 paths.push_back(FilePath( | 143 paths.push_back(FilePath( |
135 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators()); | 144 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators()); |
136 } | 145 } |
137 | 146 |
138 // Reinitialize the event stream if we find changes to the root. This is | 147 // Reinitialize the event stream if we find changes to the root. This is |
139 // necessary since FSEvents doesn't report any events for the subtree after | 148 // necessary since FSEvents doesn't report any events for the subtree after |
140 // the directory to be watched gets created. | 149 // the directory to be watched gets created. |
141 if (root_changed) { | 150 if (root_changed) { |
142 // Resetting the event stream from within the callback fails (FSEvents spews | 151 // Resetting the event stream from within the callback fails (FSEvents spews |
143 // bad file descriptor errors), so post a task to do the reset. | 152 // bad file descriptor errors), so do the reset asynchronously. |
144 dispatch_async(watcher->queue_, ^{ | 153 // |
145 watcher->UpdateEventStream(root_change_at); | 154 // We can't dispatch_async a call to UpdateEventStream() directly because |
146 }); | 155 // there would be no guarantee that |watcher| still exists when it runs. |
| 156 // |
| 157 // Instead, bounce on task_runner() and use a WeakPtr to verify that |
| 158 // |watcher| still exists. If it does, dispatch_async a call to |
| 159 // UpdateEventStream(). Because the destructor of |watcher| runs on |
| 160 // task_runner() and calls dispatch_sync, it is guaranteed that |watcher| |
| 161 // still exists when UpdateEventStream() runs. |
| 162 watcher->task_runner()->PostTask( |
| 163 FROM_HERE, Bind( |
| 164 [](WeakPtr<FilePathWatcherFSEvents> weak_watcher, |
| 165 FSEventStreamEventId root_change_at) { |
| 166 if (!weak_watcher) |
| 167 return; |
| 168 FilePathWatcherFSEvents* watcher = weak_watcher.get(); |
| 169 dispatch_async(watcher->queue_, ^{ |
| 170 watcher->UpdateEventStream(root_change_at); |
| 171 }); |
| 172 }, |
| 173 watcher->weak_factory_.GetWeakPtr(), root_change_at)); |
147 } | 174 } |
148 | 175 |
149 watcher->OnFilePathsChanged(paths); | 176 watcher->OnFilePathsChanged(paths); |
150 } | 177 } |
151 | 178 |
152 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() { | |
153 // This method may be called on either the libdispatch or task_runner() | |
154 // thread. Checking callback_ on the libdispatch thread here is safe because | |
155 // it is executing in a task posted by Cancel() which first reset callback_. | |
156 // PostTask forms a sufficient memory barrier to ensure that the value is | |
157 // consistent on the target thread. | |
158 DCHECK(callback_.is_null()) | |
159 << "Cancel() must be called before FilePathWatcher is destroyed."; | |
160 } | |
161 | |
162 void FilePathWatcherFSEvents::OnFilePathsChanged( | 179 void FilePathWatcherFSEvents::OnFilePathsChanged( |
163 const std::vector<FilePath>& paths) { | 180 const std::vector<FilePath>& paths) { |
164 DCHECK(!resolved_target_.empty()); | 181 DCHECK(!resolved_target_.empty()); |
165 task_runner()->PostTask( | 182 task_runner()->PostTask( |
166 FROM_HERE, Bind(&FilePathWatcherFSEvents::DispatchEvents, this, paths, | 183 FROM_HERE, |
167 target_, resolved_target_)); | 184 Bind(&FilePathWatcherFSEvents::DispatchEvents, weak_factory_.GetWeakPtr(), |
| 185 paths, target_, resolved_target_)); |
168 } | 186 } |
169 | 187 |
170 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths, | 188 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths, |
171 const FilePath& target, | 189 const FilePath& target, |
172 const FilePath& resolved_target) { | 190 const FilePath& resolved_target) { |
173 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 191 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
174 | 192 |
175 // Don't issue callbacks after Cancel() has been called. | 193 // Don't issue callbacks after Cancel() has been called. |
176 if (is_cancelled() || callback_.is_null()) { | 194 if (is_cancelled() || callback_.is_null()) { |
177 return; | 195 return; |
178 } | 196 } |
179 | 197 |
180 for (const FilePath& path : paths) { | 198 for (const FilePath& path : paths) { |
181 if (resolved_target.IsParent(path) || resolved_target == path) { | 199 if (resolved_target.IsParent(path) || resolved_target == path) { |
182 callback_.Run(target, false); | 200 callback_.Run(target, false); |
183 return; | 201 return; |
184 } | 202 } |
185 } | 203 } |
186 } | 204 } |
187 | 205 |
188 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() { | |
189 // For all other implementations, the "message loop thread" is the IO thread, | |
190 // as returned by task_runner(). This implementation, however, needs to | |
191 // cancel pending work on the Dispatch Queue thread. | |
192 | |
193 if (fsevent_stream_) { | |
194 DestroyEventStream(); | |
195 target_.clear(); | |
196 resolved_target_.clear(); | |
197 } | |
198 } | |
199 | |
200 void FilePathWatcherFSEvents::UpdateEventStream( | 206 void FilePathWatcherFSEvents::UpdateEventStream( |
201 FSEventStreamEventId start_event) { | 207 FSEventStreamEventId start_event) { |
202 // It can happen that the watcher gets canceled while tasks that call this | 208 // It can happen that the watcher gets canceled while tasks that call this |
203 // function are still in flight, so abort if this situation is detected. | 209 // function are still in flight, so abort if this situation is detected. |
204 if (resolved_target_.empty()) | 210 if (resolved_target_.empty()) |
205 return; | 211 return; |
206 | 212 |
207 if (fsevent_stream_) | 213 if (fsevent_stream_) |
208 DestroyEventStream(); | 214 DestroyEventStream(); |
209 | 215 |
(...skipping 15 matching lines...) Expand all Loading... |
225 context.copyDescription = NULL; | 231 context.copyDescription = NULL; |
226 | 232 |
227 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, | 233 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, |
228 watched_paths, | 234 watched_paths, |
229 start_event, | 235 start_event, |
230 kEventLatencySeconds, | 236 kEventLatencySeconds, |
231 kFSEventStreamCreateFlagWatchRoot); | 237 kFSEventStreamCreateFlagWatchRoot); |
232 FSEventStreamSetDispatchQueue(fsevent_stream_, queue_); | 238 FSEventStreamSetDispatchQueue(fsevent_stream_, queue_); |
233 | 239 |
234 if (!FSEventStreamStart(fsevent_stream_)) { | 240 if (!FSEventStreamStart(fsevent_stream_)) { |
235 task_runner()->PostTask( | 241 task_runner()->PostTask(FROM_HERE, |
236 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_)); | 242 Bind(&FilePathWatcherFSEvents::ReportError, |
| 243 weak_factory_.GetWeakPtr(), target_)); |
237 } | 244 } |
238 } | 245 } |
239 | 246 |
240 bool FilePathWatcherFSEvents::ResolveTargetPath() { | 247 bool FilePathWatcherFSEvents::ResolveTargetPath() { |
241 FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); | 248 FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); |
242 bool changed = resolved != resolved_target_; | 249 bool changed = resolved != resolved_target_; |
243 resolved_target_ = resolved; | 250 resolved_target_ = resolved; |
244 if (resolved_target_.empty()) { | 251 if (resolved_target_.empty()) { |
245 task_runner()->PostTask( | 252 task_runner()->PostTask(FROM_HERE, |
246 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_)); | 253 Bind(&FilePathWatcherFSEvents::ReportError, |
| 254 weak_factory_.GetWeakPtr(), target_)); |
247 } | 255 } |
248 return changed; | 256 return changed; |
249 } | 257 } |
250 | 258 |
251 void FilePathWatcherFSEvents::ReportError(const FilePath& target) { | 259 void FilePathWatcherFSEvents::ReportError(const FilePath& target) { |
252 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 260 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
253 if (!callback_.is_null()) { | 261 if (!callback_.is_null()) { |
254 callback_.Run(target, true); | 262 callback_.Run(target, true); |
255 } | 263 } |
256 } | 264 } |
257 | 265 |
258 void FilePathWatcherFSEvents::DestroyEventStream() { | 266 void FilePathWatcherFSEvents::DestroyEventStream() { |
259 FSEventStreamStop(fsevent_stream_); | 267 FSEventStreamStop(fsevent_stream_); |
260 FSEventStreamInvalidate(fsevent_stream_); | 268 FSEventStreamInvalidate(fsevent_stream_); |
261 FSEventStreamRelease(fsevent_stream_); | 269 FSEventStreamRelease(fsevent_stream_); |
262 fsevent_stream_ = NULL; | 270 fsevent_stream_ = NULL; |
263 } | 271 } |
264 | 272 |
265 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event, | 273 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event, |
266 const FilePath& path) { | 274 const FilePath& path) { |
267 DCHECK(resolved_target_.empty()); | 275 DCHECK(resolved_target_.empty()); |
268 | 276 |
269 target_ = path; | 277 target_ = path; |
270 ResolveTargetPath(); | 278 ResolveTargetPath(); |
271 UpdateEventStream(start_event); | 279 UpdateEventStream(start_event); |
272 } | 280 } |
273 | 281 |
274 } // namespace base | 282 } // namespace base |
OLD | NEW |