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

Side by Side Diff: pkg/watcher/lib/src/directory_watcher/mac_os.dart

Issue 134963007: Fix a flaky test and work around issue 16003 in pkg/watcher. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 11 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 | « no previous file | pkg/watcher/test/directory_watcher/mac_os_test.dart » ('j') | 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) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library watcher.directory_watcher.mac_os; 5 library watcher.directory_watcher.mac_os;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:io'; 8 import 'dart:io';
9 9
10 import 'package:path/path.dart' as p; 10 import 'package:path/path.dart' as p;
11 import 'package:stack_trace/stack_trace.dart'; 11 import 'package:stack_trace/stack_trace.dart';
12 12
13 import '../constructable_file_system_event.dart'; 13 import '../constructable_file_system_event.dart';
14 import '../path_set.dart'; 14 import '../path_set.dart';
15 import '../utils.dart'; 15 import '../utils.dart';
16 import '../watch_event.dart'; 16 import '../watch_event.dart';
17 import 'resubscribable.dart'; 17 import 'resubscribable.dart';
18 18
19 /// Uses the FSEvents subsystem to watch for filesystem events. 19 /// Uses the FSEvents subsystem to watch for filesystem events.
20 /// 20 ///
21 /// FSEvents has two main idiosyncrasies that this class works around. First, it 21 /// FSEvents has two main idiosyncrasies that this class works around. First, it
22 /// will occasionally report events that occurred before the filesystem watch 22 /// will occasionally report events that occurred before the filesystem watch
23 /// was initiated. Second, if multiple events happen to the same file in close 23 /// was initiated. Second, if multiple events happen to the same file in close
24 /// succession, it won't report them in the order they occurred. See issue 24 /// succession, it won't report them in the order they occurred. See issue
25 /// 14373. 25 /// 14373.
26 /// 26 ///
27 /// This also works around issue 14849 in the implementation of 27 /// This also works around issues 16003 and 14849 in the implementation of
28 /// [Directory.watch]. 28 /// [Directory.watch].
29 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher { 29 class MacOSDirectoryWatcher extends ResubscribableDirectoryWatcher {
30 // TODO(nweiz): remove these when issue 15042 is fixed. 30 // TODO(nweiz): remove these when issue 15042 is fixed.
31 static var logDebugInfo = false; 31 static var logDebugInfo = false;
32 static var _count = 0; 32 static var _count = 0;
33 33
34 MacOSDirectoryWatcher(String directory) 34 MacOSDirectoryWatcher(String directory)
35 : super(directory, () => new _MacOSDirectoryWatcher(directory, _count++)); 35 : super(directory, () => new _MacOSDirectoryWatcher(directory, _count++));
36 } 36 }
37 37
(...skipping 11 matching lines...) Expand all
49 49
50 Future get ready => _readyCompleter.future; 50 Future get ready => _readyCompleter.future;
51 final _readyCompleter = new Completer(); 51 final _readyCompleter = new Completer();
52 52
53 /// The number of event batches that have been received from 53 /// The number of event batches that have been received from
54 /// [Directory.watch]. 54 /// [Directory.watch].
55 /// 55 ///
56 /// This is used to determine if the [Directory.watch] stream was falsely 56 /// This is used to determine if the [Directory.watch] stream was falsely
57 /// closed due to issue 14849. A close caused by events in the past will only 57 /// closed due to issue 14849. A close caused by events in the past will only
58 /// happen before or immediately after the first batch of events. 58 /// happen before or immediately after the first batch of events.
59 int batches = 0; 59 int _batches = 0;
60 60
61 /// The set of files that are known to exist recursively within the watched 61 /// The set of files that are known to exist recursively within the watched
62 /// directory. 62 /// directory.
63 /// 63 ///
64 /// The state of files on the filesystem is compared against this to determine 64 /// The state of files on the filesystem is compared against this to determine
65 /// the real change that occurred when working around issue 14373. This is 65 /// the real change that occurred when working around issue 14373. This is
66 /// also used to emit REMOVE events when subdirectories are moved out of the 66 /// also used to emit REMOVE events when subdirectories are moved out of the
67 /// watched directory. 67 /// watched directory.
68 final PathSet _files; 68 final PathSet _files;
69 69
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
122 for (var event in batch) { 122 for (var event in batch) {
123 print("[$_id] ${_formatEvent(event)}"); 123 print("[$_id] ${_formatEvent(event)}");
124 } 124 }
125 125
126 print("[$_id] known files:"); 126 print("[$_id] known files:");
127 for (var file in _files.toSet()) { 127 for (var file in _files.toSet()) {
128 print("[$_id] ${p.relative(file, from: directory)}"); 128 print("[$_id] ${p.relative(file, from: directory)}");
129 } 129 }
130 } 130 }
131 131
132 batches++; 132 _batches++;
133 133
134 _sortEvents(batch).forEach((path, events) { 134 _sortEvents(batch).forEach((path, events) {
135 var relativePath = p.relative(path, from: directory); 135 var relativePath = p.relative(path, from: directory);
136 if (MacOSDirectoryWatcher.logDebugInfo) { 136 if (MacOSDirectoryWatcher.logDebugInfo) {
137 print("[$_id] events for $relativePath:\n"); 137 print("[$_id] events for $relativePath:");
138 for (var event in events) { 138 for (var event in events) {
139 print("[$_id] ${_formatEvent(event)}"); 139 print("[$_id] ${_formatEvent(event)}");
140 } 140 }
141 } 141 }
142 142
143 var canonicalEvent = _canonicalEvent(events); 143 var canonicalEvent = _canonicalEvent(events);
144 events = canonicalEvent == null ? 144 events = canonicalEvent == null ?
145 _eventsBasedOnFileSystem(path) : [canonicalEvent]; 145 _eventsBasedOnFileSystem(path) : [canonicalEvent];
146 if (MacOSDirectoryWatcher.logDebugInfo) { 146 if (MacOSDirectoryWatcher.logDebugInfo) {
147 print("[$_id] canonical event for $relativePath: " 147 print("[$_id] canonical event for $relativePath: "
(...skipping 13 matching lines...) Expand all
161 _emitEvent(ChangeType.ADD, path); 161 _emitEvent(ChangeType.ADD, path);
162 _files.add(path); 162 _files.add(path);
163 continue; 163 continue;
164 } 164 }
165 165
166 if (_files.containsDir(path)) continue; 166 if (_files.containsDir(path)) continue;
167 167
168 _listen(Chain.track(new Directory(path).list(recursive: true)), 168 _listen(Chain.track(new Directory(path).list(recursive: true)),
169 (entity) { 169 (entity) {
170 if (entity is Directory) return; 170 if (entity is Directory) return;
171 if (_files.contains(path)) return;
172
171 _emitEvent(ChangeType.ADD, entity.path); 173 _emitEvent(ChangeType.ADD, entity.path);
172 _files.add(entity.path); 174 _files.add(entity.path);
173 }, onError: (e, stackTrace) { 175 }, onError: (e, stackTrace) {
174 if (MacOSDirectoryWatcher.logDebugInfo) { 176 if (MacOSDirectoryWatcher.logDebugInfo) {
175 print("[$_id] got error listing $relativePath: $e"); 177 print("[$_id] got error listing $relativePath: $e");
176 } 178 }
177 _emitError(e, stackTrace); 179 _emitError(e, stackTrace);
178 }, cancelOnError: true); 180 }, cancelOnError: true);
179 } else if (event is FileSystemModifyEvent) { 181 } else if (event is FileSystemModifyEvent) {
180 assert(!event.isDirectory); 182 assert(!event.isDirectory);
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
286 // from FSEvents reporting an add that happened prior to the watch 288 // from FSEvents reporting an add that happened prior to the watch
287 // beginning. If we also received a MODIFY event, we want to report that, 289 // beginning. If we also received a MODIFY event, we want to report that,
288 // but not the CREATE. 290 // but not the CREATE.
289 if (type == FileSystemEvent.CREATE && hadModifyEvent && 291 if (type == FileSystemEvent.CREATE && hadModifyEvent &&
290 _files.contains(batch.first.path)) { 292 _files.contains(batch.first.path)) {
291 type = FileSystemEvent.MODIFY; 293 type = FileSystemEvent.MODIFY;
292 } 294 }
293 295
294 switch (type) { 296 switch (type) {
295 case FileSystemEvent.CREATE: 297 case FileSystemEvent.CREATE:
296 return new ConstructableFileSystemCreateEvent(batch.first.path, isDir); 298 // Issue 16003 means that a CREATE event for a directory can indicate
299 // that the directory was moved and then re-created.
300 // [_eventsBasedOnFileSystem] will handle this correctly by producing a
301 // DELETE event followed by a CREATE event if the directory exists.
302 if (isDir) return null;
303 return new ConstructableFileSystemCreateEvent(batch.first.path, false);
297 case FileSystemEvent.DELETE: 304 case FileSystemEvent.DELETE:
298 return new ConstructableFileSystemCreateEvent(batch.first.path, false); 305 return new ConstructableFileSystemDeleteEvent(batch.first.path, isDir);
299 case FileSystemEvent.MODIFY: 306 case FileSystemEvent.MODIFY:
300 return new ConstructableFileSystemModifyEvent( 307 return new ConstructableFileSystemModifyEvent(
301 batch.first.path, isDir, false); 308 batch.first.path, isDir, false);
302 default: assert(false); 309 default: assert(false);
303 } 310 }
304 } 311 }
305 312
306 /// Returns one or more events that describe the change between the last known 313 /// Returns one or more events that describe the change between the last known
307 /// state of [path] and its current state on the filesystem. 314 /// state of [path] and its current state on the filesystem.
308 /// 315 ///
309 /// This returns a list whose order should be reflected in the events emitted 316 /// This returns a list whose order should be reflected in the events emitted
310 /// to the user, unlike the batched events from [Directory.watch]. The 317 /// to the user, unlike the batched events from [Directory.watch]. The
311 /// returned list may be empty, indicating that no changes occurred to [path] 318 /// returned list may be empty, indicating that no changes occurred to [path]
312 /// (probably indicating that it was created and then immediately deleted). 319 /// (probably indicating that it was created and then immediately deleted).
313 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) { 320 List<FileSystemEvent> _eventsBasedOnFileSystem(String path) {
314 var fileExisted = _files.contains(path); 321 var fileExisted = _files.contains(path);
315 var dirExisted = _files.containsDir(path); 322 var dirExisted = _files.containsDir(path);
316 var fileExists = new File(path).existsSync(); 323 var fileExists = new File(path).existsSync();
317 var dirExists = new Directory(path).existsSync(); 324 var dirExists = new Directory(path).existsSync();
318 325
319 if (MacOSDirectoryWatcher.logDebugInfo) { 326 if (MacOSDirectoryWatcher.logDebugInfo) {
320 print("[$_id] file existed: $fileExisted"); 327 print("[$_id] checking file system for "
321 print("[$_id] dir existed: $dirExisted"); 328 "${p.relative(path, from: directory)}");
322 print("[$_id] file exists: $fileExists"); 329 print("[$_id] file existed: $fileExisted");
323 print("[$_id] dir exists: $dirExists"); 330 print("[$_id] dir existed: $dirExisted");
331 print("[$_id] file exists: $fileExists");
332 print("[$_id] dir exists: $dirExists");
324 } 333 }
325 334
326 var events = []; 335 var events = [];
327 if (fileExisted) { 336 if (fileExisted) {
328 if (fileExists) { 337 if (fileExists) {
329 events.add(new ConstructableFileSystemModifyEvent(path, false, false)); 338 events.add(new ConstructableFileSystemModifyEvent(path, false, false));
330 } else { 339 } else {
331 events.add(new ConstructableFileSystemDeleteEvent(path, false)); 340 events.add(new ConstructableFileSystemDeleteEvent(path, false));
332 } 341 }
333 } else if (dirExisted) { 342 } else if (dirExisted) {
(...skipping 12 matching lines...) Expand all
346 events.add(new ConstructableFileSystemCreateEvent(path, false)); 355 events.add(new ConstructableFileSystemCreateEvent(path, false));
347 } else if (!dirExisted && dirExists) { 356 } else if (!dirExisted && dirExists) {
348 events.add(new ConstructableFileSystemCreateEvent(path, true)); 357 events.add(new ConstructableFileSystemCreateEvent(path, true));
349 } 358 }
350 359
351 return events; 360 return events;
352 } 361 }
353 362
354 /// The callback that's run when the [Directory.watch] stream is closed. 363 /// The callback that's run when the [Directory.watch] stream is closed.
355 void _onDone() { 364 void _onDone() {
365 print("[$_id] stream closed");
Bob Nystrom 2014/01/14 23:45:09 Put this in if (MacOSDirectoryWatcher.logDebugInfo
nweiz 2014/01/14 23:49:07 Good catch! Done.
366
356 _watchSubscription = null; 367 _watchSubscription = null;
357 368
358 // If the directory still exists and we haven't seen more than one batch, 369 // If the directory still exists and we haven't seen more than one batch,
359 // this is probably issue 14849 rather than a real close event. We should 370 // this is probably issue 14849 rather than a real close event. We should
360 // just restart the watcher. 371 // just restart the watcher.
361 if (batches < 2 && new Directory(directory).existsSync()) { 372 if (_batches < 2 && new Directory(directory).existsSync()) {
373 print("[$_id] fake closure (issue 14849), re-opening stream");
362 _startWatch(); 374 _startWatch();
363 return; 375 return;
364 } 376 }
365 377
366 // FSEvents can fail to report the contents of the directory being removed 378 // FSEvents can fail to report the contents of the directory being removed
367 // when the directory itself is removed, so we need to manually mark the as 379 // when the directory itself is removed, so we need to manually mark the
368 // removed. 380 // files as removed.
369 for (var file in _files.toSet()) { 381 for (var file in _files.toSet()) {
370 _emitEvent(ChangeType.REMOVE, file); 382 _emitEvent(ChangeType.REMOVE, file);
371 } 383 }
372 _files.clear(); 384 _files.clear();
373 close(); 385 close();
374 } 386 }
375 387
376 /// Start or restart the underlying [Directory.watch] stream. 388 /// Start or restart the underlying [Directory.watch] stream.
377 void _startWatch() { 389 void _startWatch() {
378 // Batch the FSEvent changes together so that we can dedup events. 390 // Batch the FSEvent changes together so that we can dedup events.
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
425 } else if (event is FileSystemDeleteEvent) { 437 } else if (event is FileSystemDeleteEvent) {
426 return "delete $type $path"; 438 return "delete $type $path";
427 } else if (event is FileSystemModifyEvent) { 439 } else if (event is FileSystemModifyEvent) {
428 return "modify $type $path"; 440 return "modify $type $path";
429 } else if (event is FileSystemMoveEvent) { 441 } else if (event is FileSystemMoveEvent) {
430 return "move $type $path to " 442 return "move $type $path to "
431 "${p.relative(event.destination, from: directory)}"; 443 "${p.relative(event.destination, from: directory)}";
432 } 444 }
433 } 445 }
434 } 446 }
OLDNEW
« no previous file with comments | « no previous file | pkg/watcher/test/directory_watcher/mac_os_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698