Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report and shutdown after file watch errors #3411

Merged
merged 2 commits into from Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions build_daemon/CHANGELOG.md
@@ -1,3 +1,8 @@
## 3.1.1-dev
jakemac53 marked this conversation as resolved.
Show resolved Hide resolved

- Report file watching errors and stop the daemon.


## 3.1.0

- Add `BuildResults.changedAssets` containing asset URIs changed during a
Expand Down
6 changes: 6 additions & 0 deletions build_daemon/lib/constants.dart
Expand Up @@ -12,6 +12,12 @@ const optionsSkew = 'DIFFERENT OPTIONS';

const buildModeFlag = 'build-mode';

/// Daemon shuts down after this timeout if there is no active connection.
const defaultIdleTimeout = Duration(seconds: 30);

const fileChangeEventErrorCode = 101;
const fileChangeStreamClosedErrorCode = 102;

enum BuildMode {
// ignore: constant_identifier_names
Manual,
Expand Down
15 changes: 8 additions & 7 deletions build_daemon/lib/daemon.dart
Expand Up @@ -25,7 +25,7 @@ import 'src/server.dart';
class Daemon {
final String _workingDirectory;
final RandomAccessFile? _lock;
final _doneCompleter = Completer();
final _doneCompleter = Completer<int>();

Server? _server;
StreamSubscription? _sub;
Expand All @@ -34,7 +34,8 @@ class Daemon {
: _workingDirectory = workingDirectory,
_lock = _tryGetLock(workingDirectory);

Future<void> get onDone => _doneCompleter.future;
/// Returns exit code.
Future<int> get onDone => _doneCompleter.future;

Future<void> stop({String message = '', int failureType = 0}) =>
_server!.stop(message: message, failureType: failureType);
Expand Down Expand Up @@ -65,7 +66,7 @@ class Daemon {
ChangeProvider changeProvider, {
Serializers? serializersOverride,
bool Function(BuildTarget, Iterable<WatchEvent>)? shouldBuild,
Duration timeout = const Duration(seconds: 30),
Duration timeout = defaultIdleTimeout,
}) async {
if (_server != null || _lock == null) return;
_handleGracefulExit();
Expand All @@ -83,12 +84,12 @@ class Daemon {
var port = await server.listen();
_createPortFile(port);

unawaited(server.onDone.then((_) async {
await _cleanUp();
unawaited(server.onDone.then((exitCode) async {
await _cleanUp(exitCode);
}));
}

Future<void> _cleanUp() async {
Future<void> _cleanUp(int exitCode) async {
await _server?.stop();
await _sub?.cancel();
// We need to close the lock prior to deleting the file.
Expand All @@ -97,7 +98,7 @@ class Daemon {
if (workspace.existsSync()) {
workspace.deleteSync(recursive: true);
}
if (!_doneCompleter.isCompleted) _doneCompleter.complete();
if (!_doneCompleter.isCompleted) _doneCompleter.complete(exitCode);
}

void _createPortFile(int port) =>
Expand Down
5 changes: 2 additions & 3 deletions build_daemon/lib/src/fakes/fake_change_provider.dart
@@ -1,16 +1,15 @@
// Copyright (c) 2019, 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:async';

import 'package:build_daemon/change_provider.dart';
import 'package:watcher/watcher.dart' show WatchEvent;

class FakeChangeProvider implements ChangeProvider {
final changeStreamController = StreamController<List<WatchEvent>>();
@override
Stream<List<WatchEvent>> get changes => Stream.empty();

Stream<List<WatchEvent>> get changes => changeStreamController.stream;
@override
Future<List<WatchEvent>> collectChanges() async => [];
}
32 changes: 14 additions & 18 deletions build_daemon/lib/src/server.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2019, 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:async';
import 'dart:convert';
import 'dart:io';
Expand All @@ -17,6 +16,7 @@ import 'package:watcher/watcher.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import '../change_provider.dart';
import '../constants.dart';
import '../daemon_builder.dart';
import '../data/build_request.dart';
import '../data/build_target.dart';
Expand All @@ -31,24 +31,19 @@ import 'managers/build_target_manager.dart';
/// Note the server will only notify clients of pertinent events.
class Server {
static final loggerName = 'BuildDaemonServer';

final _isDoneCompleter = Completer();
final _isDoneCompleter = Completer<int>();
final BuildTargetManager _buildTargetManager;
final _pool = Pool(1);
final Serializers _serializers;
final ChangeProvider _changeProvider;
late final Timer _timeout;

HttpServer? _server;
final DaemonBuilder _builder;
// Channels that are interested in the current build.
var _interestedChannels = <WebSocketChannel>{};

final _subs = <StreamSubscription>[];

final _outputStreamController = StreamController<ServerLog>();
late final Stream<ServerLog> _logs;

Server(
this._builder,
Duration timeout,
Expand All @@ -61,9 +56,7 @@ class Server {
BuildTargetManager(shouldBuildOverride: shouldBuild) {
_logs = _outputStreamController.stream;
_forwardData();

_handleChanges(changeProvider.changes);

// Stop the server if nobody connects.
_timeout = Timer(timeout, () async {
if (_buildTargetManager.isEmpty) {
Expand All @@ -72,7 +65,8 @@ class Server {
});
}

Future<void> get onDone => _isDoneCompleter.future;
/// Returns exit code.
Future<int> get onDone => _isDoneCompleter.future;

/// Starts listening for build daemon clients.
Future<int> listen() async {
Expand All @@ -98,7 +92,6 @@ class Server {
_removeChannel(channel);
});
});

var server = _server = await HttpMultiServer.loopback(0);
// Serve requests in an error zone to prevent failures
// when running from another error zone.
Expand All @@ -124,7 +117,7 @@ class Server {
await sub.cancel();
}
await _outputStreamController.close();
if (!_isDoneCompleter.isCompleted) _isDoneCompleter.complete();
if (!_isDoneCompleter.isCompleted) _isDoneCompleter.complete(failureType);
}

Future<void> _build(
Expand All @@ -134,7 +127,6 @@ class Server {
buildTargets.expand(_buildTargetManager.channels).toSet();
return _builder.build(buildTargets, changes);
});

void _forwardData() {
_subs
..add(_builder.logs.listen((log) {
Expand All @@ -147,14 +139,11 @@ class Server {
// Don't serialize or send changed assets if the client isn't interested
// in them.
String? message, messageWithoutChangedAssets;

for (var channel in _interestedChannels) {
var targets = _buildTargetManager.targetsFor(channel);
var wantsChangedAssets = targets
.any((e) => e is DefaultBuildTarget && e.reportChangedAssets);

String messageForChannel;

if (wantsChangedAssets) {
messageForChannel =
message ??= jsonEncode(_serializers.serialize(status));
Expand All @@ -163,7 +152,6 @@ class Server {
_serializers
.serialize(status.rebuild((b) => b.changedAssets = null)));
}

channel.sink.add(messageForChannel);
}
}))
Expand All @@ -183,7 +171,15 @@ class Server {
var buildTargets = _buildTargetManager.targetsForChanges(changes);
if (buildTargets.isEmpty) return;
await _build(buildTargets, changes);
}).listen((_) {}));
}).listen((_) {}, onError: (e) {
stop(
message: 'Error in file change event: $e',
failureType: fileChangeEventErrorCode);
}, onDone: () {
stop(
message: 'File change stream closed',
failureType: fileChangeStreamClosedErrorCode);
}));
}

void _removeChannel(WebSocketChannel channel) async {
Expand Down
2 changes: 1 addition & 1 deletion build_daemon/pubspec.yaml
@@ -1,5 +1,5 @@
name: build_daemon
version: 3.1.0
version: 3.1.1-dev
description: A daemon for running Dart builds.
repository: https://github.com/dart-lang/build/tree/master/build_daemon

Expand Down