| Index: pkg/watcher/lib/src/directory_watcher/mac_os.dart
|
| diff --git a/pkg/watcher/lib/src/directory_watcher/mac_os.dart b/pkg/watcher/lib/src/directory_watcher/mac_os.dart
|
| index bb82a4c3e1af4ed38f1a75e357d5f881913be94d..ba0ede4cb489101af78e2904d9626b3b751b1012 100644
|
| --- a/pkg/watcher/lib/src/directory_watcher/mac_os.dart
|
| +++ b/pkg/watcher/lib/src/directory_watcher/mac_os.dart
|
| @@ -50,14 +50,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| Future get ready => _readyCompleter.future;
|
| final _readyCompleter = new Completer();
|
|
|
| - /// The number of event batches that have been received from
|
| - /// [Directory.watch].
|
| - ///
|
| - /// This is used to determine if the [Directory.watch] stream was falsely
|
| - /// closed due to issue 14849. A close caused by events in the past will only
|
| - /// happen before or immediately after the first batch of events.
|
| - int _batches = 0;
|
| -
|
| /// The set of files that are known to exist recursively within the watched
|
| /// directory.
|
| ///
|
| @@ -73,11 +65,17 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| /// needs to be resubscribed in order to work around issue 14849.
|
| StreamSubscription<FileSystemEvent> _watchSubscription;
|
|
|
| - /// A set of subscriptions that this watcher subscribes to.
|
| - ///
|
| - /// These are gathered together so that they may all be canceled when the
|
| - /// watcher is closed. This does not include [_watchSubscription].
|
| - final _subscriptions = new Set<StreamSubscription>();
|
| + /// The subscription to the [Directory.list] call for the initial listing of
|
| + /// the directory to determine its initial state.
|
| + StreamSubscription<FileSystemEntity> _initialListSubscription;
|
| +
|
| + /// The subscription to the [Directory.list] call for listing the contents of
|
| + /// a subdirectory that was moved into the watched directory.
|
| + StreamSubscription<FileSystemEntity> _listSubscription;
|
| +
|
| + /// The timer for tracking how long we wait for an initial batch of bogus
|
| + /// events (see issue 14373).
|
| + Timer _bogusEventTimer;
|
|
|
| _MacOSDirectoryWatcher(String directory, int parentId)
|
| : directory = directory,
|
| @@ -85,12 +83,20 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| _id = "$parentId/${_count++}" {
|
| _startWatch();
|
|
|
| - _listen(Chain.track(new Directory(directory).list(recursive: true)),
|
| - (entity) {
|
| - if (entity is! Directory) _files.add(entity.path);
|
| - },
|
| - onError: _emitError,
|
| - onDone: () {
|
| + // Before we're ready to emit events, wait for [_listDir] to complete and
|
| + // for enough time to elapse that if bogus events (issue 14373) would be
|
| + // emitted, they will be.
|
| + //
|
| + // If we do receive a batch of events, [_onBatch] will ensure that these
|
| + // futures don't fire and that the directory is re-listed.
|
| + Future.wait([
|
| + _listDir().then((_) {
|
| + if (MacOSDirectoryWatcher.logDebugInfo) {
|
| + print("[$_id] finished initial directory list");
|
| + }
|
| + }),
|
| + _waitForBogusEvents()
|
| + ]).then((_) {
|
| if (MacOSDirectoryWatcher.logDebugInfo) {
|
| print("[$_id] watcher is ready, known files:");
|
| for (var file in _files.toSet()) {
|
| @@ -98,20 +104,19 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| }
|
| }
|
| _readyCompleter.complete();
|
| - },
|
| - cancelOnError: true);
|
| + });
|
| }
|
|
|
| void close() {
|
| if (MacOSDirectoryWatcher.logDebugInfo) {
|
| print("[$_id] watcher is closed");
|
| }
|
| - for (var subscription in _subscriptions) {
|
| - subscription.cancel();
|
| - }
|
| - _subscriptions.clear();
|
| if (_watchSubscription != null) _watchSubscription.cancel();
|
| + if (_initialListSubscription != null) _initialListSubscription.cancel();
|
| + if (_listSubscription != null) _listSubscription.cancel();
|
| _watchSubscription = null;
|
| + _initialListSubscription = null;
|
| + _listSubscription = null;
|
| _eventsController.close();
|
| }
|
|
|
| @@ -129,7 +134,28 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| }
|
| }
|
|
|
| - _batches++;
|
| + // If we get a batch of events before we're ready to begin emitting events,
|
| + // it's probable that it's a batch of pre-watcher events (see issue 14373).
|
| + // Ignore those events and re-list the directory.
|
| + if (!isReady) {
|
| + if (MacOSDirectoryWatcher.logDebugInfo) {
|
| + print("[$_id] not ready to emit events, re-listing directory");
|
| + }
|
| +
|
| + // Cancel the timer because bogus events only occur in the first batch, so
|
| + // we can fire [ready] as soon as we're done listing the directory.
|
| + _bogusEventTimer.cancel();
|
| + _listDir().then((_) {
|
| + if (MacOSDirectoryWatcher.logDebugInfo) {
|
| + print("[$_id] watcher is ready, known files:");
|
| + for (var file in _files.toSet()) {
|
| + print("[$_id] ${p.relative(file, from: directory)}");
|
| + }
|
| + }
|
| + _readyCompleter.complete();
|
| + });
|
| + return;
|
| + }
|
|
|
| _sortEvents(batch).forEach((path, events) {
|
| var relativePath = p.relative(path, from: directory);
|
| @@ -165,8 +191,8 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
|
|
| if (_files.containsDir(path)) continue;
|
|
|
| - _listen(Chain.track(new Directory(path).list(recursive: true)),
|
| - (entity) {
|
| + var stream = Chain.track(new Directory(path).list(recursive: true));
|
| + _listSubscription = stream.listen((entity) {
|
| if (entity is Directory) return;
|
| if (_files.contains(path)) return;
|
|
|
| @@ -366,10 +392,10 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
|
|
| _watchSubscription = null;
|
|
|
| - // If the directory still exists and we haven't seen more than one batch,
|
| + // If the directory still exists and we're still expecting bogus events,
|
| // this is probably issue 14849 rather than a real close event. We should
|
| // just restart the watcher.
|
| - if (_batches < 2 && new Directory(directory).existsSync()) {
|
| + if (!isReady && new Directory(directory).existsSync()) {
|
| if (MacOSDirectoryWatcher.logDebugInfo) {
|
| print("[$_id] fake closure (issue 14849), re-opening stream");
|
| }
|
| @@ -398,6 +424,37 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| onDone: _onDone);
|
| }
|
|
|
| + /// Starts or restarts listing the watched directory to get an initial picture
|
| + /// of its state.
|
| + Future _listDir() {
|
| + assert(!isReady);
|
| + if (_initialListSubscription != null) _initialListSubscription.cancel();
|
| +
|
| + _files.clear();
|
| + var completer = new Completer();
|
| + var stream = Chain.track(new Directory(directory).list(recursive: true));
|
| + _initialListSubscription = stream.listen((entity) {
|
| + if (entity is! Directory) _files.add(entity.path);
|
| + },
|
| + onError: _emitError,
|
| + onDone: completer.complete,
|
| + cancelOnError: true);
|
| + return completer.future;
|
| + }
|
| +
|
| + /// Wait 200ms for a batch of bogus events (issue 14373) to come in.
|
| + ///
|
| + /// 200ms is short in terms of human interaction, but longer than any Mac OS
|
| + /// watcher tests take on the bots, so it should be safe to assume that any
|
| + /// bogus events will be signaled in that time frame.
|
| + Future _waitForBogusEvents() {
|
| + var completer = new Completer();
|
| + _bogusEventTimer = new Timer(
|
| + new Duration(milliseconds: 200),
|
| + completer.complete);
|
| + return completer.future;
|
| + }
|
| +
|
| /// Emit an event with the given [type] and [path].
|
| void _emitEvent(ChangeType type, String path) {
|
| if (!isReady) return;
|
| @@ -415,18 +472,6 @@ class _MacOSDirectoryWatcher implements ManuallyClosedDirectoryWatcher {
|
| close();
|
| }
|
|
|
| - /// Like [Stream.listen], but automatically adds the subscription to
|
| - /// [_subscriptions] so that it can be canceled when [close] is called.
|
| - void _listen(Stream stream, void onData(event), {Function onError,
|
| - void onDone(), bool cancelOnError}) {
|
| - var subscription;
|
| - subscription = stream.listen(onData, onError: onError, onDone: () {
|
| - _subscriptions.remove(subscription);
|
| - if (onDone != null) onDone();
|
| - }, cancelOnError: cancelOnError);
|
| - _subscriptions.add(subscription);
|
| - }
|
| -
|
| // TODO(nweiz): remove this when issue 15042 is fixed.
|
| /// Return a human-friendly string representation of [event].
|
| String _formatEvent(FileSystemEvent event) {
|
|
|