Skip to content

Commit 124dc20

Browse files
committed
Recover from watcher restart.
1 parent bcbbcb8 commit 124dc20

File tree

7 files changed

+85
-31
lines changed

7 files changed

+85
-31
lines changed

build_runner/lib/src/daemon/daemon_builder.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,9 @@ class BuildRunnerDaemonBuilder implements DaemonBuilder {
277277
Stream<List<WatchEvent>> graphEvents() =>
278278
PackageGraphWatcher(packageGraph, watch: PackageNodeWatcher.new)
279279
.watch()
280+
// TODO(davidmorgan): do something with the nulls, which indicate
281+
// "watcher dropped events and restarted".
282+
.whereNotNull()
280283
.asyncWhere(
281284
(change) => shouldProcess(
282285
change,

build_runner/lib/src/entrypoint/watch.dart

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,25 +49,30 @@ class WatchCommand extends BuildRunnerCommand {
4949
}
5050

5151
Future<int> _run(WatchOptions options) async {
52-
var handler = await watch(
53-
builderApplications,
54-
enableLowResourcesMode: options.enableLowResourcesMode,
55-
configKey: options.configKey,
56-
buildDirs: options.buildDirs,
57-
outputSymlinksOnly: options.outputSymlinksOnly,
58-
packageGraph: packageGraph,
59-
trackPerformance: options.trackPerformance,
60-
skipBuildScriptCheck: options.skipBuildScriptCheck,
61-
verbose: options.verbose,
62-
builderConfigOverrides: options.builderConfigOverrides,
63-
isReleaseBuild: options.isReleaseBuild,
64-
logPerformanceDir: options.logPerformanceDir,
65-
buildFilters: options.buildFilters,
66-
);
52+
while (true) {
53+
final handler = await watch(
54+
builderApplications,
55+
enableLowResourcesMode: options.enableLowResourcesMode,
56+
configKey: options.configKey,
57+
buildDirs: options.buildDirs,
58+
outputSymlinksOnly: options.outputSymlinksOnly,
59+
packageGraph: packageGraph,
60+
trackPerformance: options.trackPerformance,
61+
skipBuildScriptCheck: options.skipBuildScriptCheck,
62+
verbose: options.verbose,
63+
builderConfigOverrides: options.builderConfigOverrides,
64+
isReleaseBuild: options.isReleaseBuild,
65+
logPerformanceDir: options.logPerformanceDir,
66+
buildFilters: options.buildFilters,
67+
);
6768

68-
final completer = Completer<int>();
69-
handleBuildResultsStream(handler.buildResults, completer);
70-
return completer.future;
69+
final completer = Completer<int>();
70+
handleBuildResultsStream(handler.buildResults, completer);
71+
final result = await completer.future;
72+
if (result != ExitCode.tempFail.code) {
73+
return result;
74+
}
75+
}
7176
}
7277

7378
/// Listens to [buildResults], handling certain types of errors and completing
@@ -83,6 +88,9 @@ class WatchCommand extends BuildRunnerCommand {
8388
completer.completeError(const BuildScriptChangedException());
8489
} else if (result.failureType == FailureType.buildConfigChanged) {
8590
completer.completeError(const BuildConfigChangedException());
91+
} else if (result.failureType == FailureType.watcherRestarted) {
92+
// TODO(davidmorgan): don't communicate using errors.
93+
completer.complete(ExitCode.tempFail.code);
8694
}
8795
}
8896
});

build_runner/lib/src/generate/watch_impl.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,22 @@ class WatchImpl implements BuildState {
291291
);
292292
graphWatcher
293293
.watch()
294+
.where((change) {
295+
if (change == null) {
296+
controller.add(
297+
BuildResult(
298+
BuildStatus.failure,
299+
[],
300+
failureType: FailureType.watcherRestarted,
301+
),
302+
);
303+
_terminateCompleter.complete();
304+
return false;
305+
} else {
306+
return true;
307+
}
308+
})
309+
.whereNotNull()
294310
.asyncMap<AssetChange>((change) {
295311
// Delay any events until the first build is completed.
296312
if (firstBuildCompleter.isCompleted) return change;

build_runner/lib/src/watcher/graph_watcher.dart

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,42 @@ class PackageGraphWatcher {
3333
}) : _strategy = watch ?? _default;
3434

3535
/// Returns a stream of records for assets that changed in the package graph.
36-
Stream<AssetChange> watch() {
36+
///
37+
/// A `null` event indicates that the watcher failed: watching will continue
38+
/// but some events are missing.
39+
Stream<AssetChange?> watch() {
3740
assert(!_isWatching);
3841
_isWatching = true;
3942
return LazyStream(_watch);
4043
}
4144

42-
Stream<AssetChange> _watch() {
45+
Stream<AssetChange?> _watch() {
4346
final allWatchers =
4447
_graph.allPackages.values
4548
.where((node) => node.dependencyType == DependencyType.path)
4649
.map(_strategy)
4750
.toList();
51+
final restartsController = StreamController<AssetChange?>();
4852
final filteredEvents =
4953
allWatchers
5054
.map(
5155
(w) => w.watch().where(_nestedPathFilter(w.node)).handleError((
5256
Object e,
5357
StackTrace s,
5458
) {
55-
buildLog.error(
56-
buildLog.renderThrowable(
57-
'Failed to watch files in package:${w.node.name}.',
58-
e,
59-
),
60-
);
59+
if (e is FileSystemException &&
60+
e.message.startsWith(
61+
'Directory watcher closed unexpectedly',
62+
)) {
63+
restartsController.add(null);
64+
} else {
65+
buildLog.error(
66+
buildLog.renderThrowable(
67+
'Failed to watch files in package:${w.node.name}.',
68+
e,
69+
),
70+
);
71+
}
6172
}),
6273
)
6374
.toList();
@@ -69,12 +80,13 @@ class PackageGraphWatcher {
6980
);
7081
_readyCompleter.complete();
7182
}();
72-
return StreamGroup.merge(filteredEvents);
83+
return StreamGroup.merge([...filteredEvents, restartsController.stream]);
7384
}
7485

75-
bool Function(AssetChange) _nestedPathFilter(PackageNode rootNode) {
86+
bool Function(AssetChange?) _nestedPathFilter(PackageNode rootNode) {
7687
final ignorePaths = _nestedPaths(rootNode);
77-
return (change) => !ignorePaths.any(change.id.path.startsWith);
88+
return (change) =>
89+
change == null || !ignorePaths.any(change.id.path.startsWith);
7890
}
7991

8092
// Returns a set of all package paths that are "nested" within a node.

build_runner/lib/src/watcher/node_watcher.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:io';
67

78
import 'package:build_runner_core/build_runner_core.dart';
89
import 'package:watcher/watcher.dart';
@@ -29,9 +30,22 @@ class PackageNodeWatcher {
2930
: _strategy = watch ?? _default;
3031

3132
/// Returns a stream of records for assets that change recursively.
32-
Stream<AssetChange> watch() {
33+
/*Stream<AssetChange> watch() {
3334
_watcher = _strategy(node.path);
3435
final events = _watcher.events;
3536
return events.map((e) => AssetChange.fromEvent(node, e));
37+
}*/
38+
39+
Stream<AssetChange> watch() {
40+
_watcher = _strategy(node.path);
41+
final events = _watcher.events;
42+
final result = StreamController<AssetChange>();
43+
events.listen((e) => result.add(AssetChange.fromEvent(node, e)));
44+
Timer.periodic(const Duration(seconds: 20), (_) {
45+
result.addError(
46+
const FileSystemException('Directory watcher closed unexpectedly'),
47+
);
48+
});
49+
return result.stream;
3650
}
3751
}

build_runner/test/watcher/graph_watcher_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void main() {
6060
},
6161
);
6262

63-
final events = <AssetChange>[];
63+
final events = <AssetChange?>[];
6464
unawaited(watcher.watch().forEach(events.add));
6565
await watcher.ready;
6666

build_runner_core/lib/src/generate/build_result.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class FailureType {
5959
static final cantCreate = FailureType._(73);
6060
static final buildConfigChanged = FailureType._(75);
6161
static final buildScriptChanged = FailureType._(75);
62+
static final watcherRestarted = FailureType._(75);
6263
final int exitCode;
6364
FailureType._(this.exitCode);
6465
}

0 commit comments

Comments
 (0)