Chromium Code Reviews| 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()); | |
|
gab
2017/01/05 21:22:53
Is this a new requirement? Where is it documented?
fdoray
2017/01/05 23:04:11
The DCHECK below is not new.
The DCHECK above doc
| |
| 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). Therefore, post a task to task_runner() that |
| 144 dispatch_async(watcher->queue_, ^{ | 153 // will itself schedule a call to UpdateEventStream() on the dispatch queue. |
| 145 watcher->UpdateEventStream(root_change_at); | 154 // Bouncing on task_runner() and using a weak ptr ensures that |
| 146 }); | 155 // UpdateEventStream() won't be called after |watcher| is destroyed (i.e. |
| 156 // after the watch is cancelled). | |
| 157 watcher->task_runner()->PostTask( | |
| 158 FROM_HERE, Bind( | |
| 159 [](WeakPtr<FilePathWatcherFSEvents> weak_watcher, | |
| 160 FSEventStreamEventId root_change_at) { | |
| 161 if (!weak_watcher) | |
| 162 return; | |
| 163 FilePathWatcherFSEvents* watcher = weak_watcher.get(); | |
| 164 dispatch_async(watcher->queue_, ^{ | |
|
gab
2017/01/05 21:22:53
Is the async dispatch required here? i.e. it was r
fdoray
2017/01/05 23:04:11
Yes it's required. UpdateEventStream() must run on
| |
| 165 watcher->UpdateEventStream(root_change_at); | |
| 166 }); | |
| 167 }, | |
| 168 watcher->weak_factory_.GetWeakPtr(), root_change_at)); | |
| 147 } | 169 } |
| 148 | 170 |
| 149 watcher->OnFilePathsChanged(paths); | 171 watcher->OnFilePathsChanged(paths); |
| 150 } | 172 } |
| 151 | 173 |
| 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( | 174 void FilePathWatcherFSEvents::OnFilePathsChanged( |
| 163 const std::vector<FilePath>& paths) { | 175 const std::vector<FilePath>& paths) { |
| 164 DCHECK(!resolved_target_.empty()); | 176 DCHECK(!resolved_target_.empty()); |
| 165 task_runner()->PostTask( | 177 task_runner()->PostTask( |
| 166 FROM_HERE, Bind(&FilePathWatcherFSEvents::DispatchEvents, this, paths, | 178 FROM_HERE, |
| 167 target_, resolved_target_)); | 179 Bind(&FilePathWatcherFSEvents::DispatchEvents, weak_factory_.GetWeakPtr(), |
| 180 paths, target_, resolved_target_)); | |
| 168 } | 181 } |
| 169 | 182 |
| 170 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths, | 183 void FilePathWatcherFSEvents::DispatchEvents(const std::vector<FilePath>& paths, |
| 171 const FilePath& target, | 184 const FilePath& target, |
| 172 const FilePath& resolved_target) { | 185 const FilePath& resolved_target) { |
| 173 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 186 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
| 174 | 187 |
| 175 // Don't issue callbacks after Cancel() has been called. | 188 // Don't issue callbacks after Cancel() has been called. |
| 176 if (is_cancelled() || callback_.is_null()) { | 189 if (is_cancelled() || callback_.is_null()) { |
| 177 return; | 190 return; |
| 178 } | 191 } |
| 179 | 192 |
| 180 for (const FilePath& path : paths) { | 193 for (const FilePath& path : paths) { |
| 181 if (resolved_target.IsParent(path) || resolved_target == path) { | 194 if (resolved_target.IsParent(path) || resolved_target == path) { |
| 182 callback_.Run(target, false); | 195 callback_.Run(target, false); |
| 183 return; | 196 return; |
| 184 } | 197 } |
| 185 } | 198 } |
| 186 } | 199 } |
| 187 | 200 |
| 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( | 201 void FilePathWatcherFSEvents::UpdateEventStream( |
| 201 FSEventStreamEventId start_event) { | 202 FSEventStreamEventId start_event) { |
| 202 // It can happen that the watcher gets canceled while tasks that call this | 203 // 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. | 204 // function are still in flight, so abort if this situation is detected. |
| 204 if (resolved_target_.empty()) | 205 if (resolved_target_.empty()) |
| 205 return; | 206 return; |
| 206 | 207 |
| 207 if (fsevent_stream_) | 208 if (fsevent_stream_) |
| 208 DestroyEventStream(); | 209 DestroyEventStream(); |
| 209 | 210 |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 225 context.copyDescription = NULL; | 226 context.copyDescription = NULL; |
| 226 | 227 |
| 227 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, | 228 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, |
| 228 watched_paths, | 229 watched_paths, |
| 229 start_event, | 230 start_event, |
| 230 kEventLatencySeconds, | 231 kEventLatencySeconds, |
| 231 kFSEventStreamCreateFlagWatchRoot); | 232 kFSEventStreamCreateFlagWatchRoot); |
| 232 FSEventStreamSetDispatchQueue(fsevent_stream_, queue_); | 233 FSEventStreamSetDispatchQueue(fsevent_stream_, queue_); |
| 233 | 234 |
| 234 if (!FSEventStreamStart(fsevent_stream_)) { | 235 if (!FSEventStreamStart(fsevent_stream_)) { |
| 235 task_runner()->PostTask( | 236 task_runner()->PostTask(FROM_HERE, |
| 236 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_)); | 237 Bind(&FilePathWatcherFSEvents::ReportError, |
| 238 weak_factory_.GetWeakPtr(), target_)); | |
| 237 } | 239 } |
| 238 } | 240 } |
| 239 | 241 |
| 240 bool FilePathWatcherFSEvents::ResolveTargetPath() { | 242 bool FilePathWatcherFSEvents::ResolveTargetPath() { |
| 241 FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); | 243 FilePath resolved = ResolvePath(target_).StripTrailingSeparators(); |
| 242 bool changed = resolved != resolved_target_; | 244 bool changed = resolved != resolved_target_; |
| 243 resolved_target_ = resolved; | 245 resolved_target_ = resolved; |
| 244 if (resolved_target_.empty()) { | 246 if (resolved_target_.empty()) { |
| 245 task_runner()->PostTask( | 247 task_runner()->PostTask(FROM_HERE, |
| 246 FROM_HERE, Bind(&FilePathWatcherFSEvents::ReportError, this, target_)); | 248 Bind(&FilePathWatcherFSEvents::ReportError, |
| 249 weak_factory_.GetWeakPtr(), target_)); | |
| 247 } | 250 } |
| 248 return changed; | 251 return changed; |
| 249 } | 252 } |
| 250 | 253 |
| 251 void FilePathWatcherFSEvents::ReportError(const FilePath& target) { | 254 void FilePathWatcherFSEvents::ReportError(const FilePath& target) { |
| 252 DCHECK(task_runner()->RunsTasksOnCurrentThread()); | 255 DCHECK(task_runner()->RunsTasksOnCurrentThread()); |
| 253 if (!callback_.is_null()) { | 256 if (!callback_.is_null()) { |
| 254 callback_.Run(target, true); | 257 callback_.Run(target, true); |
| 255 } | 258 } |
| 256 } | 259 } |
| 257 | 260 |
| 258 void FilePathWatcherFSEvents::DestroyEventStream() { | 261 void FilePathWatcherFSEvents::DestroyEventStream() { |
| 259 FSEventStreamStop(fsevent_stream_); | 262 FSEventStreamStop(fsevent_stream_); |
| 260 FSEventStreamInvalidate(fsevent_stream_); | 263 FSEventStreamInvalidate(fsevent_stream_); |
| 261 FSEventStreamRelease(fsevent_stream_); | 264 FSEventStreamRelease(fsevent_stream_); |
| 262 fsevent_stream_ = NULL; | 265 fsevent_stream_ = NULL; |
| 263 } | 266 } |
| 264 | 267 |
| 265 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event, | 268 void FilePathWatcherFSEvents::StartEventStream(FSEventStreamEventId start_event, |
| 266 const FilePath& path) { | 269 const FilePath& path) { |
| 267 DCHECK(resolved_target_.empty()); | 270 DCHECK(resolved_target_.empty()); |
| 268 | 271 |
| 269 target_ = path; | 272 target_ = path; |
| 270 ResolveTargetPath(); | 273 ResolveTargetPath(); |
| 271 UpdateEventStream(start_event); | 274 UpdateEventStream(start_event); |
| 272 } | 275 } |
| 273 | 276 |
| 274 } // namespace base | 277 } // namespace base |
| OLD | NEW |