From 1d7d1e96d2e5963c57d6108c5445b47fc95ccbdd Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 30 Sep 2025 09:19:05 +0200 Subject: [PATCH 1/3] Ignore PathNotFoundException when watching subdirs Watching subdirectories is inherently racy with file-system modifications so watcher must be prepared for `Directory.watch` to fail with `PathNotFoundException`. --- pkgs/watcher/CHANGELOG.md | 8 +++++++- pkgs/watcher/lib/src/directory_watcher/linux.dart | 15 +++++++++++---- pkgs/watcher/pubspec.yaml | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index 30a19756e..be7fd824d 100644 --- a/pkgs/watcher/CHANGELOG.md +++ b/pkgs/watcher/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.1.4-wip + +- Improve handling of subdirectories on Linux: watching subdirectories is + inherently racy with file-system modifications so watcher must be prepared + for `Directory.watch` to fail with `PathNotFoundException`. + ## 1.1.3 - Improve handling of @@ -6,7 +12,7 @@ events. But, the restart would sometimes silently fail. Now, it is more reliable. - Improving handling of directories that are created then immediately deleted on - Windows. Previously, that could cause a `PathNotfoundException` to be thrown. + Windows. Previously, that could cause a `PathNotFoundException` to be thrown. ## 1.1.2 diff --git a/pkgs/watcher/lib/src/directory_watcher/linux.dart b/pkgs/watcher/lib/src/directory_watcher/linux.dart index cb1d07781..f696a89b5 100644 --- a/pkgs/watcher/lib/src/directory_watcher/linux.dart +++ b/pkgs/watcher/lib/src/directory_watcher/linux.dart @@ -136,10 +136,17 @@ class _LinuxDirectoryWatcher // top-level clients such as barback as well, and could be implemented with // a wrapper similar to how listening/canceling works now. - // TODO(nweiz): Catch any errors here that indicate that the directory in - // question doesn't exist and silently stop watching it instead of - // propagating the errors. - var stream = Directory(path).watch(); + var stream = Directory(path).watch().transform( + StreamTransformer.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); + } + }, + )); _subdirStreams[path] = stream; _nativeEvents.add(stream); } diff --git a/pkgs/watcher/pubspec.yaml b/pkgs/watcher/pubspec.yaml index 16af27bc2..80a8abbfd 100644 --- a/pkgs/watcher/pubspec.yaml +++ b/pkgs/watcher/pubspec.yaml @@ -1,5 +1,5 @@ name: watcher -version: 1.1.3 +version: 1.1.4-wip description: >- A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified. From 8b63ecb029681631554b2aeba66f92862da7cca1 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 30 Sep 2025 10:14:37 +0200 Subject: [PATCH 2/3] Add a test --- pkgs/watcher/CHANGELOG.md | 6 ++-- .../test/directory_watcher/shared.dart | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pkgs/watcher/CHANGELOG.md b/pkgs/watcher/CHANGELOG.md index be7fd824d..fd65b4abd 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: watching subdirectories is - inherently racy with file-system modifications so watcher must be prepared - for `Directory.watch` to fail with `PathNotFoundException`. +- Improve handling of subdirectories on Linux: ignore `PathNotFoundException` + due to subdirectory deletion during watch setup, instead of raising it on the + event stream. ## 1.1.3 diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index 1ebc78d4b..c8435bc5a 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -1,8 +1,11 @@ // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:isolate'; +import 'dart:io' as io; import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; import 'package:watcher/src/utils.dart'; import '../utils.dart'; @@ -340,5 +343,31 @@ void sharedTests() { events.add(isRemoveEvent('dir/sub')); await inAnyOrder(events); }); + + 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']; + await startWatcher(); + + // Repeatedly create and delete subdirectories in attempt to trigger + // a race. + for (var i = 0; i < 10; i++) { + for (var dir in dirNames) { + createDir(dir); + } + await Isolate.run(() async { + await Future.wait([ + for (var dir in dirNames) + io.Directory('$sandboxPath/$dir').delete(), + ]); + }); + } + + writeFile('a/b/c/d/file.txt'); + await inAnyOrder([ + isAddEvent('a/b/c/d/file.txt'), + ]); + }); }); } From c39a2377061c90cefd246d30ed2dee5706c5a696 Mon Sep 17 00:00:00 2001 From: Vyacheslav Egorov Date: Tue, 30 Sep 2025 10:16:13 +0200 Subject: [PATCH 3/3] Fix import order --- pkgs/watcher/test/directory_watcher/shared.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/watcher/test/directory_watcher/shared.dart b/pkgs/watcher/test/directory_watcher/shared.dart index c8435bc5a..a1d2239c6 100644 --- a/pkgs/watcher/test/directory_watcher/shared.dart +++ b/pkgs/watcher/test/directory_watcher/shared.dart @@ -1,8 +1,8 @@ // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:isolate'; import 'dart:io' as io; +import 'dart:isolate'; import 'package:test/test.dart'; import 'package:test_descriptor/test_descriptor.dart' as d;