|  | // Copyright 2014 The Flutter 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 'package:meta/meta.dart'; | 
|  |  | 
|  | import 'async_guard.dart'; | 
|  | import 'io.dart'; | 
|  |  | 
|  | typedef SignalHandler = FutureOr<void> Function(ProcessSignal signal); | 
|  |  | 
|  | /// A class that manages signal handlers. | 
|  | /// | 
|  | /// Signal handlers are run in the order that they were added. | 
|  | abstract class Signals { | 
|  | @visibleForTesting | 
|  | factory Signals.test({ | 
|  | List<ProcessSignal> exitSignals = defaultExitSignals, | 
|  | }) => LocalSignals._(exitSignals); | 
|  |  | 
|  | // The default list of signals that should cause the process to exit. | 
|  | static const List<ProcessSignal> defaultExitSignals = <ProcessSignal>[ | 
|  | ProcessSignal.sigterm, | 
|  | ProcessSignal.sigint, | 
|  | ]; | 
|  |  | 
|  | /// 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; | 
|  | } | 
|  |  | 
|  | /// A class that manages the real dart:io signal handlers. | 
|  | /// | 
|  | /// We use a singleton instance of this class to ensure that all handlers for | 
|  | /// fatal signals run before this class calls exit(). | 
|  | class LocalSignals implements Signals { | 
|  | LocalSignals._(this.exitSignals); | 
|  |  | 
|  | static LocalSignals instance = LocalSignals._( | 
|  | Signals.defaultExitSignals, | 
|  | ); | 
|  |  | 
|  | final List<ProcessSignal> exitSignals; | 
|  |  | 
|  | // 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, | 
|  | onError: (Object e) { | 
|  | _handlersTable[signal]?.remove(token); | 
|  | _handlersList[signal]?.remove(handler); | 
|  | }, | 
|  | ); | 
|  | } | 
|  | 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]!.remove(token); | 
|  | if (handler == null) { | 
|  | return false; | 
|  | } | 
|  | 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 { | 
|  | final List<SignalHandler>? handlers = _handlersList[s]; | 
|  | if (handlers != null) { | 
|  | final List<SignalHandler> handlersCopy = handlers.toList(); | 
|  | for (final SignalHandler handler in handlersCopy) { | 
|  | try { | 
|  | await asyncGuard<void>(() async => handler(s)); | 
|  | } on Exception 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool _shouldExitFor(ProcessSignal signal) => exitSignals.contains(signal); | 
|  | } |