| // 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); |
| } |