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

Side by Side Diff: base/files/file_path_watcher_fsevents.cc

Issue 312333003: Diff of FSEvents code (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: As a diff Created 6 years, 6 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
« no previous file with comments | « base/files/file_path_watcher_fsevents.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 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 "content/common/file_path_watcher/file_path_watcher.h" 5 #include "base/files/file_path_watcher_fsevents.h"
6 6
7 #include <CoreServices/CoreServices.h> 7 #include <list>
8 #include <set>
9 8
10 #include "base/file_path.h" 9 #include "base/bind.h"
11 #include "base/file_util.h" 10 #include "base/file_util.h"
11 #include "base/lazy_instance.h"
12 #include "base/logging.h" 12 #include "base/logging.h"
13 #include "base/mac/libdispatch_task_runner.h"
13 #include "base/mac/scoped_cftyperef.h" 14 #include "base/mac/scoped_cftyperef.h"
14 #include "base/memory/singleton.h" 15 #include "base/message_loop/message_loop.h"
15 #include "base/message_loop.h"
16 #include "base/time.h"
17 16
18 // Note to future well meaning engineers. Unless kqueue semantics have changed 17 namespace base {
19 // considerably, do NOT try to reimplement this class using kqueue. The main
20 // problem is that this class requires the ability to watch a directory
21 // and notice changes to any files within it. A kqueue on a directory can watch
22 // for creation and deletion of files, but not for modifications to files within
23 // the directory. To do this with the current kqueue semantics would require
24 // kqueueing every file in the directory, and file descriptors are a limited
25 // resource. If you have a good idea on how to get around this, the source for a
26 // reasonable implementation of this class using kqueues is attached here:
27 // http://code.google.com/p/chromium/issues/detail?id=54822#c13
28 18
29 namespace { 19 namespace {
30 20
31 // The latency parameter passed to FSEventsStreamCreate(). 21 // The latency parameter passed to FSEventsStreamCreate().
32 const CFAbsoluteTime kEventLatencySeconds = 0.3; 22 const CFAbsoluteTime kEventLatencySeconds = 0.3;
33 23
34 // Mac-specific file watcher implementation based on the FSEvents API. 24 class FSEventsTaskRunner : public mac::LibDispatchTaskRunner {
35 class FilePathWatcherImpl : public FilePathWatcher::PlatformDelegate,
36 public MessageLoop::DestructionObserver {
37 public: 25 public:
38 FilePathWatcherImpl(); 26 FSEventsTaskRunner()
27 : mac::LibDispatchTaskRunner("chromium.org.FilePathWatcherFSEvents") {
28 }
39 29
40 // Called from the FSEvents callback whenever there is a change to the paths 30 protected:
41 void OnFilePathChanged(); 31 virtual ~FSEventsTaskRunner() {}
32 };
42 33
43 // (Re-)Initialize the event stream to start reporting events from 34 static LazyInstance<FSEventsTaskRunner>::Leaky g_task_runner =
44 // |start_event|. 35 LAZY_INSTANCE_INITIALIZER;
45 void UpdateEventStream(FSEventStreamEventId start_event);
46 36
47 // FilePathWatcher::PlatformDelegate overrides. 37 // Resolve any symlinks in the path.
48 virtual bool Watch(const FilePath& path, 38 FilePath ResolvePath(const FilePath& path) {
49 FilePathWatcher::Delegate* delegate, 39 const unsigned kMaxLinksToResolve = 255;
50 base::MessageLoopProxy* loop) OVERRIDE;
51 virtual void Cancel() OVERRIDE;
52 40
53 // Deletion of the FilePathWatcher will call Cancel() to dispose of this 41 std::vector<FilePath::StringType> component_vector;
54 // object in the right thread. This also observes destruction of the required 42 path.GetComponents(&component_vector);
55 // cleanup thread, in case it quits before Cancel() is called. 43 std::list<FilePath::StringType>
56 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; 44 components(component_vector.begin(), component_vector.end());
57 45
58 scoped_refptr<base::MessageLoopProxy> run_loop_message_loop() { 46 FilePath result;
59 return run_loop_message_loop_; 47 unsigned resolve_count = 0;
48 while (resolve_count < kMaxLinksToResolve && !components.empty()) {
49 FilePath component(*components.begin());
50 components.pop_front();
51
52 FilePath current;
53 if (component.IsAbsolute()) {
54 current = component;
55 } else {
56 current = result.Append(component);
57 }
58
59 FilePath target;
60 if (ReadSymbolicLink(current, &target)) {
61 if (target.IsAbsolute())
62 result.clear();
63 std::vector<FilePath::StringType> target_components;
64 target.GetComponents(&target_components);
65 components.insert(components.begin(), target_components.begin(),
66 target_components.end());
67 resolve_count++;
68 } else {
69 result = current;
70 }
60 } 71 }
61 72
62 private: 73 if (resolve_count >= kMaxLinksToResolve)
63 virtual ~FilePathWatcherImpl() {} 74 result.clear();
64 75 return result;
65 // Destroy the event stream. 76 }
66 void DestroyEventStream();
67
68 // Start observing the destruction of the |run_loop_message_loop_| thread,
69 // and watching the FSEventStream.
70 void StartObserverAndEventStream(FSEventStreamEventId start_event);
71
72 // Cleans up and stops observing the |run_loop_message_loop_| thread.
73 void CancelOnMessageLoopThread() OVERRIDE;
74
75 // Delegate to notify upon changes.
76 scoped_refptr<FilePathWatcher::Delegate> delegate_;
77
78 // Target path to watch (passed to delegate).
79 FilePath target_;
80
81 // Keep track of the last modified time of the file. We use nulltime
82 // to represent the file not existing.
83 base::Time last_modified_;
84
85 // The time at which we processed the first notification with the
86 // |last_modified_| time stamp.
87 base::Time first_notification_;
88
89 // Backend stream we receive event callbacks from (strong reference).
90 FSEventStreamRef fsevent_stream_;
91
92 // Run loop for FSEventStream to run on.
93 scoped_refptr<base::MessageLoopProxy> run_loop_message_loop_;
94
95 DISALLOW_COPY_AND_ASSIGN(FilePathWatcherImpl);
96 };
97 77
98 // The callback passed to FSEventStreamCreate(). 78 // The callback passed to FSEventStreamCreate().
99 void FSEventsCallback(ConstFSEventStreamRef stream, 79 void FSEventsCallback(ConstFSEventStreamRef stream,
100 void* event_watcher, size_t num_events, 80 void* event_watcher, size_t num_events,
101 void* event_paths, const FSEventStreamEventFlags flags[], 81 void* event_paths, const FSEventStreamEventFlags flags[],
102 const FSEventStreamEventId event_ids[]) { 82 const FSEventStreamEventId event_ids[]) {
103 FilePathWatcherImpl* watcher = 83 FilePathWatcherFSEvents* watcher =
104 reinterpret_cast<FilePathWatcherImpl*>(event_watcher); 84 reinterpret_cast<FilePathWatcherFSEvents*>(event_watcher);
105 DCHECK(watcher->run_loop_message_loop()->BelongsToCurrentThread()); 85 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
106 86
107 bool root_changed = false; 87 bool root_changed = watcher->ResolveTargetPath();
88 std::vector<FilePath> paths;
108 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream); 89 FSEventStreamEventId root_change_at = FSEventStreamGetLatestEventId(stream);
109 for (size_t i = 0; i < num_events; i++) { 90 for (size_t i = 0; i < num_events; i++) {
110 if (flags[i] & kFSEventStreamEventFlagRootChanged) 91 if (flags[i] & kFSEventStreamEventFlagRootChanged)
111 root_changed = true; 92 root_changed = true;
112 if (event_ids[i]) 93 if (event_ids[i])
113 root_change_at = std::min(root_change_at, event_ids[i]); 94 root_change_at = std::min(root_change_at, event_ids[i]);
95 paths.push_back(FilePath(
96 reinterpret_cast<char**>(event_paths)[i]).StripTrailingSeparators());
114 } 97 }
115 98
116 // Reinitialize the event stream if we find changes to the root. This is 99 // Reinitialize the event stream if we find changes to the root. This is
117 // necessary since FSEvents doesn't report any events for the subtree after 100 // necessary since FSEvents doesn't report any events for the subtree after
118 // the directory to be watched gets created. 101 // the directory to be watched gets created.
119 if (root_changed) { 102 if (root_changed) {
120 // Resetting the event stream from within the callback fails (FSEvents spews 103 // Resetting the event stream from within the callback fails (FSEvents spews
121 // bad file descriptor errors), so post a task to do the reset. 104 // bad file descriptor errors), so post a task to do the reset.
122 watcher->run_loop_message_loop()->PostTask(FROM_HERE, 105 g_task_runner.Get().PostTask(
123 NewRunnableMethod(watcher, &FilePathWatcherImpl::UpdateEventStream, 106 FROM_HERE,
124 root_change_at)); 107 Bind(&FilePathWatcherFSEvents::UpdateEventStream, watcher,
108 root_change_at));
125 } 109 }
126 110
127 watcher->OnFilePathChanged(); 111 watcher->OnFilePathsChanged(paths);
128 } 112 }
129 113
130 // FilePathWatcherImpl implementation: 114 } // namespace
131 115
132 FilePathWatcherImpl::FilePathWatcherImpl() 116 FilePathWatcherFSEvents::FilePathWatcherFSEvents() : fsevent_stream_(NULL) {
133 : fsevent_stream_(NULL) {
134 } 117 }
135 118
136 void FilePathWatcherImpl::OnFilePathChanged() { 119 void FilePathWatcherFSEvents::OnFilePathsChanged(
137 // Switch to the CFRunLoop based thread if necessary, so we can tear down 120 const std::vector<FilePath>& paths) {
138 // the event stream.
139 if (!message_loop()->BelongsToCurrentThread()) { 121 if (!message_loop()->BelongsToCurrentThread()) {
140 message_loop()->PostTask( 122 message_loop()->PostTask(
141 FROM_HERE, 123 FROM_HERE,
142 NewRunnableMethod(this, &FilePathWatcherImpl::OnFilePathChanged)); 124 Bind(&FilePathWatcherFSEvents::OnFilePathsChanged, this, paths));
143 return; 125 return;
144 } 126 }
145 127
146 DCHECK(message_loop()->BelongsToCurrentThread()); 128 DCHECK(message_loop()->BelongsToCurrentThread());
147 DCHECK(!target_.empty()); 129 if (resolved_target_.empty())
130 return;
148 131
149 base::PlatformFileInfo file_info; 132 for (size_t i = 0; i < paths.size(); i++) {
150 bool file_exists = file_util::GetFileInfo(target_, &file_info); 133 if (resolved_target_.IsParent(paths[i]) || resolved_target_ == paths[i]) {
151 if (file_exists && (last_modified_.is_null() || 134 callback_.Run(target_, false);
152 last_modified_ != file_info.last_modified)) { 135 return;
153 last_modified_ = file_info.last_modified;
154 first_notification_ = base::Time::Now();
155 delegate_->OnFilePathChanged(target_);
156 } else if (file_exists && !first_notification_.is_null()) {
157 // The target's last modification time is equal to what's on record. This
158 // means that either an unrelated event occurred, or the target changed
159 // again (file modification times only have a resolution of 1s). Comparing
160 // file modification times against the wall clock is not reliable to find
161 // out whether the change is recent, since this code might just run too
162 // late. Moreover, there's no guarantee that file modification time and wall
163 // clock times come from the same source.
164 //
165 // Instead, the time at which the first notification carrying the current
166 // |last_notified_| time stamp is recorded. Later notifications that find
167 // the same file modification time only need to be forwarded until wall
168 // clock has advanced one second from the initial notification. After that
169 // interval, client code is guaranteed to having seen the current revision
170 // of the file.
171 if (base::Time::Now() - first_notification_ >
172 base::TimeDelta::FromSeconds(1)) {
173 // Stop further notifications for this |last_modification_| time stamp.
174 first_notification_ = base::Time();
175 } 136 }
176 delegate_->OnFilePathChanged(target_);
177 } else if (!file_exists && !last_modified_.is_null()) {
178 last_modified_ = base::Time();
179 delegate_->OnFilePathChanged(target_);
180 } 137 }
181 } 138 }
182 139
183 bool FilePathWatcherImpl::Watch(const FilePath& path, 140 bool FilePathWatcherFSEvents::Watch(const FilePath& path,
184 FilePathWatcher::Delegate* delegate, 141 bool recursive,
185 base::MessageLoopProxy* loop) { 142 const FilePathWatcher::Callback& callback) {
186 DCHECK(target_.value().empty()); 143 DCHECK(resolved_target_.empty());
187 DCHECK(MessageLoopForIO::current()); 144 DCHECK(MessageLoopForIO::current());
145 DCHECK(!callback.is_null());
188 146
189 set_message_loop(base::MessageLoopProxy::CreateForCurrentThread()); 147 // This class could support non-recursive watches, but that is currently
190 run_loop_message_loop_ = loop; 148 // left to FilePathWatcherKQueue.
149 if (!recursive)
150 return false;
151
152 set_message_loop(MessageLoopProxy::current());
153 callback_ = callback;
191 target_ = path; 154 target_ = path;
192 delegate_ = delegate;
193 155
194 FSEventStreamEventId start_event = FSEventsGetCurrentEventId(); 156 FSEventStreamEventId start_event = FSEventsGetCurrentEventId();
195 157 g_task_runner.Get().PostTask(
196 base::PlatformFileInfo file_info; 158 FROM_HERE,
197 if (file_util::GetFileInfo(target_, &file_info)) { 159 Bind(&FilePathWatcherFSEvents::StartEventStream, this, start_event));
198 last_modified_ = file_info.last_modified;
199 first_notification_ = base::Time::Now();
200 }
201
202 run_loop_message_loop()->PostTask(FROM_HERE,
203 NewRunnableMethod(this, &FilePathWatcherImpl::StartObserverAndEventStream,
204 start_event));
205
206 return true; 160 return true;
207 } 161 }
208 162
209 void FilePathWatcherImpl::StartObserverAndEventStream( 163 void FilePathWatcherFSEvents::Cancel() {
210 FSEventStreamEventId start_event) { 164 if (callback_.is_null()) {
211 DCHECK(run_loop_message_loop()->BelongsToCurrentThread());
212 MessageLoop::current()->AddDestructionObserver(this);
213 UpdateEventStream(start_event);
214 }
215
216 void FilePathWatcherImpl::Cancel() {
217 if (!run_loop_message_loop().get()) {
218 // Watch was never called, so exit. 165 // Watch was never called, so exit.
219 set_cancelled(); 166 set_cancelled();
220 return; 167 return;
221 } 168 }
222 169
223 // Switch to the CFRunLoop based thread if necessary, so we can tear down 170 // Switch to the dispatch queue thread if necessary, so we can tear down
224 // the event stream. 171 // the event stream.
225 if (!run_loop_message_loop()->BelongsToCurrentThread()) { 172 if (!g_task_runner.Get().RunsTasksOnCurrentThread()) {
226 run_loop_message_loop()->PostTask(FROM_HERE, 173 g_task_runner.Get().PostTask(
227 new FilePathWatcher::CancelTask(this)); 174 FROM_HERE,
175 Bind(&FilePathWatcherFSEvents::CancelOnMessageLoopThread, this));
228 } else { 176 } else {
229 CancelOnMessageLoopThread(); 177 CancelOnMessageLoopThread();
230 } 178 }
231 } 179 }
232 180
233 void FilePathWatcherImpl::CancelOnMessageLoopThread() { 181 void FilePathWatcherFSEvents::CancelOnMessageLoopThread() {
182 // For all other implementations, the "message loop thread" is the IO thread,
183 // as returned by message_loop(). This implementation, however, needs to
184 // cancel pending work on the Dipatch Queue thread.
185 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
186
234 set_cancelled(); 187 set_cancelled();
235 if (fsevent_stream_) { 188 if (fsevent_stream_) {
236 DestroyEventStream(); 189 DestroyEventStream();
237 MessageLoop::current()->RemoveDestructionObserver(this); 190 callback_.Reset();
238 delegate_ = NULL; 191 target_.clear();
192 resolved_target_.clear();
239 } 193 }
240 } 194 }
241 195
242 void FilePathWatcherImpl::WillDestroyCurrentMessageLoop() { 196 void FilePathWatcherFSEvents::UpdateEventStream(
243 CancelOnMessageLoopThread(); 197 FSEventStreamEventId start_event) {
244 } 198 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
245
246 void FilePathWatcherImpl::UpdateEventStream(FSEventStreamEventId start_event) {
247 DCHECK(run_loop_message_loop()->BelongsToCurrentThread());
248 DCHECK(MessageLoopForUI::current());
249 199
250 // It can happen that the watcher gets canceled while tasks that call this 200 // It can happen that the watcher gets canceled while tasks that call this
251 // function are still in flight, so abort if this situation is detected. 201 // function are still in flight, so abort if this situation is detected.
252 if (is_cancelled()) 202 if (is_cancelled() || resolved_target_.empty())
253 return; 203 return;
254 204
255 if (fsevent_stream_) 205 if (fsevent_stream_)
256 DestroyEventStream(); 206 DestroyEventStream();
257 207
258 base::mac::ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString( 208 ScopedCFTypeRef<CFStringRef> cf_path(CFStringCreateWithCString(
259 NULL, target_.value().c_str(), kCFStringEncodingMacHFS)); 209 NULL, resolved_target_.value().c_str(), kCFStringEncodingMacHFS));
260 base::mac::ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString( 210 ScopedCFTypeRef<CFStringRef> cf_dir_path(CFStringCreateWithCString(
261 NULL, target_.DirName().value().c_str(), kCFStringEncodingMacHFS)); 211 NULL, resolved_target_.DirName().value().c_str(),
212 kCFStringEncodingMacHFS));
262 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() }; 213 CFStringRef paths_array[] = { cf_path.get(), cf_dir_path.get() };
263 base::mac::ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate( 214 ScopedCFTypeRef<CFArrayRef> watched_paths(CFArrayCreate(
264 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array), 215 NULL, reinterpret_cast<const void**>(paths_array), arraysize(paths_array),
265 &kCFTypeArrayCallBacks)); 216 &kCFTypeArrayCallBacks));
266 217
267 FSEventStreamContext context; 218 FSEventStreamContext context;
268 context.version = 0; 219 context.version = 0;
269 context.info = this; 220 context.info = this;
270 context.retain = NULL; 221 context.retain = NULL;
271 context.release = NULL; 222 context.release = NULL;
272 context.copyDescription = NULL; 223 context.copyDescription = NULL;
273 224
274 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context, 225 fsevent_stream_ = FSEventStreamCreate(NULL, &FSEventsCallback, &context,
275 watched_paths, 226 watched_paths,
276 start_event, 227 start_event,
277 kEventLatencySeconds, 228 kEventLatencySeconds,
278 kFSEventStreamCreateFlagWatchRoot); 229 kFSEventStreamCreateFlagWatchRoot);
279 FSEventStreamScheduleWithRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), 230 FSEventStreamSetDispatchQueue(fsevent_stream_,
280 kCFRunLoopDefaultMode); 231 g_task_runner.Get().GetDispatchQueue());
281 if (!FSEventStreamStart(fsevent_stream_)) { 232
282 message_loop()->PostTask(FROM_HERE, 233 if (!FSEventStreamStart(fsevent_stream_))
283 NewRunnableMethod(delegate_.get(), 234 message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true));
284 &FilePathWatcher::Delegate::OnError));
285 }
286 } 235 }
287 236
288 void FilePathWatcherImpl::DestroyEventStream() { 237 bool FilePathWatcherFSEvents::ResolveTargetPath() {
238 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
239 FilePath resolved = ResolvePath(target_).StripTrailingSeparators();
240 bool changed = resolved != resolved_target_;
241 resolved_target_ = resolved;
242 if (resolved_target_.empty())
243 message_loop()->PostTask(FROM_HERE, Bind(callback_, target_, true));
244 return changed;
245 }
246
247 void FilePathWatcherFSEvents::DestroyEventStream() {
289 FSEventStreamStop(fsevent_stream_); 248 FSEventStreamStop(fsevent_stream_);
290 FSEventStreamUnscheduleFromRunLoop(fsevent_stream_, CFRunLoopGetCurrent(), 249 FSEventStreamInvalidate(fsevent_stream_);
291 kCFRunLoopDefaultMode);
292 FSEventStreamRelease(fsevent_stream_); 250 FSEventStreamRelease(fsevent_stream_);
293 fsevent_stream_ = NULL; 251 fsevent_stream_ = NULL;
294 } 252 }
295 253
296 } // namespace 254 void FilePathWatcherFSEvents::StartEventStream(
255 FSEventStreamEventId start_event) {
256 DCHECK(g_task_runner.Get().RunsTasksOnCurrentThread());
257 ResolveTargetPath();
258 UpdateEventStream(start_event);
259 }
297 260
298 FilePathWatcher::FilePathWatcher() { 261 FilePathWatcherFSEvents::~FilePathWatcherFSEvents() {}
299 impl_ = new FilePathWatcherImpl(); 262
300 } 263 } // namespace base
OLDNEW
« no previous file with comments | « base/files/file_path_watcher_fsevents.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698