[flutter_tools] Allows adding multiple signal handlers (#41304)

diff --git a/packages/flutter_tools/lib/src/base/signals.dart b/packages/flutter_tools/lib/src/base/signals.dart
new file mode 100644
index 0000000..b5b403b
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/signals.dart
@@ -0,0 +1,130 @@
+// Copyright 2019 The Chromium Authors. 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 'async_guard.dart';
+import 'context.dart';
+import 'io.dart';
+
+typedef SignalHandler = FutureOr<void> Function(ProcessSignal signal);
+
+Signals get signals => Signals.instance;
+
+/// A class that manages signal handlers
+///
+/// Signal handlers are run in the order that they were added.
+abstract class Signals {
+  factory Signals() => _DefaultSignals._();
+
+  static Signals get instance => context.get<Signals>();
+
+  /// Adds a signal handler to run on receipt of signal.
+  ///
+  /// The handler will run after all handlers that were previously added for the
+  /// signal. The function returns an abstract token that should be provided to
+  /// removeHandler to remove the handler.
+  Object addHandler(ProcessSignal signal, SignalHandler handler);
+
+  /// Removes a signal handler.
+  ///
+  /// Removes the signal handler for the signal identified by the abstract
+  /// token parameter. Returns true if the handler was removed and false
+  /// otherwise.
+  Future<bool> removeHandler(ProcessSignal signal, Object token);
+
+  /// If a [SignalHandler] throws an error, either synchronously or
+  /// asynchronously, it will be added to this stream instead of propagated.
+  Stream<Object> get errors;
+}
+
+class _DefaultSignals implements Signals {
+  _DefaultSignals._();
+
+  // A table mapping (signal, token) -> signal handler.
+  final Map<ProcessSignal, Map<Object, SignalHandler>> _handlersTable =
+      <ProcessSignal, Map<Object, SignalHandler>>{};
+
+  // A table mapping (signal) -> signal handler list. The list is in the order
+  // that the signal handlers should be run.
+  final Map<ProcessSignal, List<SignalHandler>> _handlersList =
+      <ProcessSignal, List<SignalHandler>>{};
+
+  // A table mapping (signal) -> low-level signal event stream.
+  final Map<ProcessSignal, StreamSubscription<ProcessSignal>> _streamSubscriptions =
+    <ProcessSignal, StreamSubscription<ProcessSignal>>{};
+
+  // The stream controller for errors coming from signal handlers.
+  final StreamController<Object> _errorStreamController = StreamController<Object>.broadcast();
+
+  @override
+  Stream<Object> get errors => _errorStreamController.stream;
+
+  @override
+  Object addHandler(ProcessSignal signal, SignalHandler handler) {
+    final Object token = Object();
+    _handlersTable.putIfAbsent(signal, () => <Object, SignalHandler>{});
+    _handlersTable[signal][token] = handler;
+
+    _handlersList.putIfAbsent(signal, () => <SignalHandler>[]);
+    _handlersList[signal].add(handler);
+
+    // If we added the first one, then call signal.watch(), listen, and cache
+    // the stream controller.
+    if (_handlersList[signal].length == 1) {
+      _streamSubscriptions[signal] = signal.watch().listen(_handleSignal);
+    }
+    return token;
+  }
+
+  @override
+  Future<bool> removeHandler(ProcessSignal signal, Object token) async {
+    // We don't know about this signal.
+    if (!_handlersTable.containsKey(signal)) {
+      return false;
+    }
+    // We don't know about this token.
+    if (!_handlersTable[signal].containsKey(token)) {
+      return false;
+    }
+    final SignalHandler handler = _handlersTable[signal][token];
+    final bool removed = _handlersList[signal].remove(handler);
+    if (!removed) {
+      return false;
+    }
+
+    // If _handlersList[signal] is empty, then lookup the cached stream
+    // controller and unsubscribe from the stream.
+    if (_handlersList.isEmpty) {
+      await _streamSubscriptions[signal].cancel();
+    }
+    return true;
+  }
+
+  Future<void> _handleSignal(ProcessSignal s) async {
+    for (SignalHandler handler in _handlersList[s]) {
+      try {
+        await asyncGuard<void>(() => handler(s));
+      } catch (e) {
+        if (_errorStreamController.hasListener) {
+          _errorStreamController.add(e);
+        }
+      }
+    }
+    // If this was a signal that should cause the process to go down, then
+    // call exit();
+    if (_shouldExitFor(s)) {
+      exit(0);
+    }
+  }
+
+  // The list of signals that should cause the process to exit.
+  static const List<ProcessSignal> _exitingSignals = <ProcessSignal>[
+    ProcessSignal.SIGTERM,
+    ProcessSignal.SIGINT,
+    ProcessSignal.SIGKILL,
+  ];
+
+  bool _shouldExitFor(ProcessSignal signal) => _exitingSignals.contains(signal);
+}