Skip to content

Commit de93f01

Browse files
authored
Ignore PathNotFoundException when watching subdirs (#2181)
Watching subdirectories is inherently racy with file-system modifications thus watcher must be prepared for `Directory.watch` to fail with `PathNotFoundException`. This is revealed when running tests against a [CL][1] which changes timing of certain events slightly: `onDone` for `FileSystemEntity.watch()` stream is now delayed by few turns of the event loop while underlying watcher is being destroyed, previously it used to fire immediately which hid away some of the inherent races in the code. [1]: https://dart-review.googlesource.com/c/sdk/+/450921
1 parent 9b705d1 commit de93f01

File tree

4 files changed

+48
-6
lines changed

4 files changed

+48
-6
lines changed

pkgs/watcher/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.1.4-wip
2+
3+
- Improve handling of subdirectories on Linux: ignore `PathNotFoundException`
4+
due to subdirectory deletion during watch setup, instead of raising it on the
5+
event stream.
6+
17
## 1.1.3
28

39
- Improve handling of
@@ -6,7 +12,7 @@
612
events. But, the restart would sometimes silently fail. Now, it is more
713
reliable.
814
- Improving handling of directories that are created then immediately deleted on
9-
Windows. Previously, that could cause a `PathNotfoundException` to be thrown.
15+
Windows. Previously, that could cause a `PathNotFoundException` to be thrown.
1016

1117
## 1.1.2
1218

pkgs/watcher/lib/src/directory_watcher/linux.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,17 @@ class _LinuxDirectoryWatcher
136136
// top-level clients such as barback as well, and could be implemented with
137137
// a wrapper similar to how listening/canceling works now.
138138

139-
// TODO(nweiz): Catch any errors here that indicate that the directory in
140-
// question doesn't exist and silently stop watching it instead of
141-
// propagating the errors.
142-
var stream = Directory(path).watch();
139+
var stream = Directory(path).watch().transform(
140+
StreamTransformer<FileSystemEvent, FileSystemEvent>.fromHandlers(
141+
handleError: (error, st, sink) {
142+
// Directory might no longer exist at the point where we try to
143+
// start the watcher. Simply ignore this error and let the stream
144+
// close.
145+
if (error is! PathNotFoundException) {
146+
sink.addError(error, st);
147+
}
148+
},
149+
));
143150
_subdirStreams[path] = stream;
144151
_nativeEvents.add(stream);
145152
}

pkgs/watcher/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: watcher
2-
version: 1.1.3
2+
version: 1.1.4-wip
33
description: >-
44
A file system watcher. It monitors changes to contents of directories and
55
sends notifications when files have been added, removed, or modified.

pkgs/watcher/test/directory_watcher/shared.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
4+
import 'dart:io' as io;
5+
import 'dart:isolate';
46

57
import 'package:test/test.dart';
8+
import 'package:test_descriptor/test_descriptor.dart' as d;
69
import 'package:watcher/src/utils.dart';
710

811
import '../utils.dart';
@@ -340,5 +343,31 @@ void sharedTests() {
340343
events.add(isRemoveEvent('dir/sub'));
341344
await inAnyOrder(events);
342345
});
346+
347+
test('subdirectory watching is robust against races', () async {
348+
// Make sandboxPath accessible to child isolates created by Isolate.run.
349+
final sandboxPath = d.sandbox;
350+
final dirNames = [for (var i = 0; i < 50; i++) 'dir$i'];
351+
await startWatcher();
352+
353+
// Repeatedly create and delete subdirectories in attempt to trigger
354+
// a race.
355+
for (var i = 0; i < 10; i++) {
356+
for (var dir in dirNames) {
357+
createDir(dir);
358+
}
359+
await Isolate.run(() async {
360+
await Future.wait([
361+
for (var dir in dirNames)
362+
io.Directory('$sandboxPath/$dir').delete(),
363+
]);
364+
});
365+
}
366+
367+
writeFile('a/b/c/d/file.txt');
368+
await inAnyOrder([
369+
isAddEvent('a/b/c/d/file.txt'),
370+
]);
371+
});
343372
});
344373
}

0 commit comments

Comments
 (0)