// 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; static LocalSignals get instance => _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 { for (final SignalHandler handler in _handlersList[s]) { 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); }