Skip to content

Commit d985a67

Browse files
committed
Fix occasional PathNotFoundException for transient directories on Windows.
1 parent eca3c3c commit d985a67

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

pkgs/watcher/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
## 1.1.3-wip
1+
## 1.1.3
22

33
- Improve handling of
44
`FileSystemException: Directory watcher closed unexpectedly` on Windows. The
55
watcher was already attempting to restart after this error and resume sending
66
events. But, the restart would sometimes silently fail. Now, it is more
77
reliable.
8+
- Improving handling of directories that are created then immediately deleted on
9+
Windows. Previously, that could cause a `PathNotfoundException` to be thrown.
810

911
## 1.1.2
1012

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class WindowsDirectoryWatcher extends ResubscribableWatcher
2121
String get directory => path;
2222

2323
WindowsDirectoryWatcher(String directory)
24-
: super(directory, () => _WindowsDirectoryWatcher(directory));
24+
: super(directory, () => _WindowsDirectoryWatcher(directory));
2525
}
2626

2727
class _EventBatcher {
@@ -169,9 +169,10 @@ class _WindowsDirectoryWatcher
169169
void _onBatch(List<FileSystemEvent> batch) {
170170
_sortEvents(batch).forEach((path, eventSet) {
171171
var canonicalEvent = _canonicalEvent(eventSet);
172-
var events = canonicalEvent == null
173-
? _eventsBasedOnFileSystem(path)
174-
: [canonicalEvent];
172+
var events =
173+
canonicalEvent == null
174+
? _eventsBasedOnFileSystem(path)
175+
: [canonicalEvent];
175176

176177
for (var event in events) {
177178
if (event is FileSystemCreateEvent) {
@@ -198,7 +199,11 @@ class _WindowsDirectoryWatcher
198199
});
199200
subscription.onError((Object e, StackTrace stackTrace) {
200201
_listSubscriptions.remove(subscription);
201-
_emitError(e, stackTrace);
202+
// "Path not found" can be caused by creating then quickly removing
203+
// a directory: continue without reporting an error.
204+
if (e is! PathNotFoundException) {
205+
_emitError(e, stackTrace);
206+
}
202207
});
203208
_listSubscriptions.add(subscription);
204209
} else if (event is FileSystemModifyEvent) {

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-wip
2+
version: 1.1.3
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/windows_test.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,52 @@ void main() {
6666
},
6767
);
6868

69+
// Regression test for https://github.com/dart-lang/tools/issues/2152:
70+
// watcher can throws if a directory is created then quickly deleted.
71+
group('Transient directory', () {
72+
late StreamSubscription<Object> subscription;
73+
late Directory temp;
74+
late Watcher watcher;
75+
late int errorsSeen;
76+
77+
setUp(() async {
78+
temp = Directory.systemTemp.createTempSync();
79+
watcher = DirectoryWatcher(temp.path);
80+
errorsSeen = 0;
81+
subscription = watcher.events.listen(
82+
(e) {},
83+
onError: (Object e, _) {
84+
print('Event stream error: $e');
85+
++errorsSeen;
86+
},
87+
);
88+
await watcher.ready;
89+
});
90+
91+
tearDown(() {
92+
subscription.cancel();
93+
});
94+
95+
test('does not break watching', () async {
96+
// Iterate creating 10 directories and deleting 1-10 of them. This means
97+
// the directories will exist for different lengths of times, exploring
98+
// possible race conditions in directory handling.
99+
for (var i = 0; i != 50; ++i) {
100+
for (var j = 0; j != 10; ++j) {
101+
File('${temp.path}\\$j\\file').createSync(recursive: true);
102+
}
103+
await Future<void>.delayed(const Duration(milliseconds: 1));
104+
for (var j = 0; j != i % 10 + 1; ++j) {
105+
final d = Directory('${temp.path}\\$j');
106+
d.deleteSync(recursive: true);
107+
}
108+
await Future<void>.delayed(const Duration(milliseconds: 1));
109+
}
110+
111+
expect(errorsSeen, 0);
112+
});
113+
});
114+
69115
// The Windows native watcher has a buffer that gets exhausted if events are
70116
// not handled quickly enough. Then, it throws an error and stops watching.
71117
// The exhaustion is reliably triggered if enough events arrive during a sync

0 commit comments

Comments
 (0)