Ignore Path{NotFound,Access}Exception in more places. (#2183)
Directory.list() can fail with these errors due to concurrent file system modifications.
Suppress these errors uniformly across the code base.diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md
index fd65b4a..01755b4 100644
--- a/pkgs/watcher/CHANGELOG.md
+++ b/pkgs/watcher/CHANGELOG.md
@@ -1,8 +1,8 @@
## 1.1.4-wip
-- Improve handling of subdirectories on Linux: ignore `PathNotFoundException`
- due to subdirectory deletion during watch setup, instead of raising it on the
- event stream.
+- Improve handling of subdirectories: ignore `PathNotFoundException` due to
+ subdirectory deletion racing with watcher internals, instead of raising
+ it on the event stream.
## 1.1.3
diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart
index f696a89..99e2cf5 100644
--- a/pkgs/watcher/lib/src/directory_watcher/linux.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart
@@ -92,7 +92,7 @@
});
_listen(
- Directory(path).list(recursive: true),
+ Directory(path).listRecursivelyIgnoringErrors(),
(FileSystemEntity entity) {
if (entity is Directory) {
_watchSubdir(entity.path);
@@ -136,17 +136,10 @@
// top-level clients such as barback as well, and could be implemented with
// a wrapper similar to how listening/canceling works now.
- var stream = Directory(path).watch().transform(
- StreamTransformer<FileSystemEvent, FileSystemEvent>.fromHandlers(
- handleError: (error, st, sink) {
- // Directory might no longer exist at the point where we try to
- // start the watcher. Simply ignore this error and let the stream
- // close.
- if (error is! PathNotFoundException) {
- sink.addError(error, st);
- }
- },
- ));
+ // Directory might no longer exist at the point where we try to
+ // start the watcher. Simply ignore this error and let the stream
+ // close.
+ var stream = Directory(path).watch().ignoring<PathNotFoundException>();
_subdirStreams[path] = stream;
_nativeEvents.add(stream);
}
diff --git a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
index b461383..509cf6f 100644
--- a/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/mac_os.dart
@@ -148,7 +148,9 @@
if (_files.containsDir(path)) continue;
- var stream = Directory(path).list(recursive: true);
+ var stream = Directory(path)
+ .list(recursive: true)
+ .ignoring<PathNotFoundException>();
var subscription = stream.listen((entity) {
if (entity is Directory) return;
if (_files.contains(path)) return;
@@ -373,7 +375,7 @@
_files.clear();
var completer = Completer<void>();
- var stream = Directory(path).list(recursive: true);
+ var stream = Directory(path).listRecursivelyIgnoringErrors();
_initialListSubscription = stream.listen((entity) {
if (entity is! Directory) _files.add(entity.path);
}, onError: _emitError, onDone: completer.complete, cancelOnError: true);
diff --git a/pkgs/watcher/lib/src/directory_watcher/polling.dart b/pkgs/watcher/lib/src/directory_watcher/polling.dart
index 207679b..d3fa6eb 100644
--- a/pkgs/watcher/lib/src/directory_watcher/polling.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/polling.dart
@@ -112,7 +112,7 @@
_filesToProcess.add(null);
}
- var stream = Directory(path).list(recursive: true);
+ var stream = Directory(path).listRecursivelyIgnoringErrors();
_listSubscription = stream.listen((entity) {
assert(!_events.isClosed);
diff --git a/pkgs/watcher/lib/src/directory_watcher/windows.dart b/pkgs/watcher/lib/src/directory_watcher/windows.dart
index 8f21268..9b17f8d 100644
--- a/pkgs/watcher/lib/src/directory_watcher/windows.dart
+++ b/pkgs/watcher/lib/src/directory_watcher/windows.dart
@@ -123,8 +123,15 @@
void _startParentWatcher() {
var absoluteDir = p.absolute(path);
var parent = p.dirname(absoluteDir);
- // Check if [path] is already the root directory.
- if (FileSystemEntity.identicalSync(parent, path)) return;
+ try {
+ // Check if [path] is already the root directory.
+ if (FileSystemEntity.identicalSync(parent, path)) return;
+ } on FileSystemException catch (_) {
+ // Either parent or path or both might be gone due to concurrently
+ // occurring changes. Just ignore and continue. If we fail to
+ // watch path we will report an error from _startWatch.
+ return;
+ }
var parentStream = Directory(parent).watch(recursive: false);
_parentWatchSubscription = parentStream.listen(
(event) {
@@ -185,7 +192,14 @@
if (_files.containsDir(path)) continue;
- var stream = Directory(path).list(recursive: true);
+ // "Path not found" can be caused by creating then quickly removing
+ // a directory: continue without reporting an error. Nested files
+ // that get removed during the `list` are already ignored by `list`
+ // itself, so there are no other types of "path not found" that
+ // might need different handling here.
+ var stream = Directory(path)
+ .list(recursive: true)
+ .ignoring<PathNotFoundException>();
var subscription = stream.listen((entity) {
if (entity is Directory) return;
if (_files.contains(entity.path)) return;
@@ -198,14 +212,7 @@
});
subscription.onError((Object e, StackTrace stackTrace) {
_listSubscriptions.remove(subscription);
- // "Path not found" can be caused by creating then quickly removing
- // a directory: continue without reporting an error. Nested files
- // that get removed during the `list` are already ignored by `list`
- // itself, so there are no other types of "path not found" that
- // might need different handling here.
- if (e is! PathNotFoundException) {
- _emitError(e, stackTrace);
- }
+ _emitError(e, stackTrace);
});
_listSubscriptions.add(subscription);
} else if (event is FileSystemModifyEvent) {
@@ -435,7 +442,7 @@
_files.clear();
var completer = Completer<void>();
- var stream = Directory(path).list(recursive: true);
+ var stream = Directory(path).listRecursivelyIgnoringErrors();
void handleEntity(FileSystemEntity entity) {
if (entity is! Directory) _files.add(entity.path);
}
diff --git a/pkgs/watcher/lib/src/utils.dart b/pkgs/watcher/lib/src/utils.dart
index c2e71b3..e5ef54c 100644
--- a/pkgs/watcher/lib/src/utils.dart
+++ b/pkgs/watcher/lib/src/utils.dart
@@ -50,3 +50,30 @@
}).bind(this);
}
}
+
+extension IgnoringError<T> on Stream<T> {
+ /// Ignore all errors of type [E] emitted by the given stream.
+ ///
+ /// Everything else gets forwarded through as-is.
+ Stream<T> ignoring<E>() {
+ return transform(StreamTransformer<T, T>.fromHandlers(
+ handleError: (error, st, sink) {
+ if (error is! E) {
+ sink.addError(error, st);
+ }
+ },
+ ));
+ }
+}
+
+extension DirectoryRobustRecursiveListing on Directory {
+ /// List the given directory recursively but ignore not-found or access
+ /// errors.
+ ///
+ /// Theses can arise from concurrent file-system modification.
+ Stream<FileSystemEntity> listRecursivelyIgnoringErrors() {
+ return list(recursive: true)
+ .ignoring<PathNotFoundException>()
+ .ignoring<PathAccessException>();
+ }
+}
diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart
index a1d2239..816d2a2 100644
--- a/pkgs/watcher/test/directory_watcher/shared.dart
+++ b/pkgs/watcher/test/directory_watcher/shared.dart
@@ -347,7 +347,7 @@
test('subdirectory watching is robust against races', () async {
// Make sandboxPath accessible to child isolates created by Isolate.run.
final sandboxPath = d.sandbox;
- final dirNames = [for (var i = 0; i < 50; i++) 'dir$i'];
+ final dirNames = [for (var i = 0; i < 500; i++) 'dir$i'];
await startWatcher();
// Repeatedly create and delete subdirectories in attempt to trigger