Skip to content

Commit 98816bd

Browse files
committed
Run Windows directory watcher in an isolate.
1 parent f0467d7 commit 98816bd

File tree

10 files changed

+851
-522
lines changed

10 files changed

+851
-522
lines changed

pkgs/watcher/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
- `DirectoryWatcher` on Windows performance: reduce 100ms buffering of events
77
before reporting to 5ms, the larger buffer isn't needed for correctness after
88
the various fixes.
9+
- `DirectoryWatcher` on Windows watches in a separate Isolate to make buffer
10+
exhaustion, "Directory watcher closed unexpectedly", much less likely. The old
11+
implementation which does not use a separate Isolate is available as
12+
`DirectoryWatcher(path, runInIsolateOnWindows: false)`.
913
- Bug fix: while listing directories skip symlinks that lead to a directory
1014
that has already been listed. This prevents a severe performance regression on
1115
MacOS and Linux when there are more than a few symlink loops.

pkgs/watcher/lib/src/directory_watcher.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import 'directory_watcher/windows.dart';
2020
/// the message "Directory watcher closed unexpectedly" on the event stream. The
2121
/// code using the watcher needs to do additional work to account for the
2222
/// dropped events, for example by recomputing interesting files from scratch.
23+
/// By default, the watcher is started in a separate isolate to make this less
24+
/// likely. Pass `runInIsolateOnWindows = false` to not launch an isolate.
2325
///
2426
/// On Linux, the underlying SDK `Directory.watch` fails if the system limit on
2527
/// watchers has been reached. If this happens the SDK exception is thrown, it
@@ -40,7 +42,11 @@ abstract class DirectoryWatcher implements Watcher {
4042
/// shorter will give more immediate feedback at the expense of doing more IO
4143
/// and higher CPU usage. Defaults to one second. Ignored for non-polling
4244
/// watchers.
43-
factory DirectoryWatcher(String directory, {Duration? pollingDelay}) {
45+
///
46+
/// On Windows, pass [runInIsolateOnWindows] `false` to not run the watcher
47+
/// in a separate isolate to reduce buffer exhaustion failures.
48+
factory DirectoryWatcher(String directory,
49+
{Duration? pollingDelay, bool runInIsolateOnWindows = true}) {
4450
if (FileSystemEntity.isWatchSupported) {
4551
var customWatcher = createCustomDirectoryWatcher(
4652
directory,
@@ -49,7 +55,10 @@ abstract class DirectoryWatcher implements Watcher {
4955
if (customWatcher != null) return customWatcher;
5056
if (Platform.isLinux) return LinuxDirectoryWatcher(directory);
5157
if (Platform.isMacOS) return MacOSDirectoryWatcher(directory);
52-
if (Platform.isWindows) return WindowsDirectoryWatcher(directory);
58+
if (Platform.isWindows) {
59+
return WindowsDirectoryWatcher(directory,
60+
runInIsolate: runInIsolateOnWindows);
61+
}
5362
}
5463
return PollingDirectoryWatcher(directory, pollingDelay: pollingDelay);
5564
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,17 @@ class _ResolvedDirectory {
142142
} on FileSystemException catch (e, s) {
143143
// The first operation on a directory is to resolve symbolic links, which
144144
// fails with a general FileSystemException if the file is not found.
145-
// Convert that into a PathNotFoundException as that makes more sense
146-
// to the caller, who didn't ask for anything to do with symbolic links.
147-
if (e.message.contains('Cannot resolve symbolic links') &&
148-
e.osError?.errorCode == 2) {
149-
throw Error.throwWithStackTrace(
150-
PathNotFoundException(directory.path, e.osError!), s);
145+
// Convert that into a PathNotFoundException or PathAccessException
146+
// as that makes more sense to the caller, who didn't ask for anything to
147+
// do with symbolic links.
148+
if (e.message.contains('Cannot resolve symbolic links')) {
149+
if (e.osError?.errorCode == 2) {
150+
throw Error.throwWithStackTrace(
151+
PathNotFoundException(directory.path, e.osError!), s);
152+
} else if (e.osError?.errorCode == 5) {
153+
throw Error.throwWithStackTrace(
154+
PathAccessException(directory.path, e.osError!), s);
155+
}
151156
}
152157
rethrow;
153158
}

0 commit comments

Comments
 (0)