signals.dart 5.03 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

7 8
import 'package:meta/meta.dart';

9 10 11 12 13
import 'async_guard.dart';
import 'io.dart';

typedef SignalHandler = FutureOr<void> Function(ProcessSignal signal);

14
/// A class that manages signal handlers.
15 16 17
///
/// Signal handlers are run in the order that they were added.
abstract class Signals {
18 19 20 21
  @visibleForTesting
  factory Signals.test({
    List<ProcessSignal> exitSignals = defaultExitSignals,
  }) => LocalSignals._(exitSignals);
22

23 24
  // The default list of signals that should cause the process to exit.
  static const List<ProcessSignal> defaultExitSignals = <ProcessSignal>[
25 26
    ProcessSignal.sigterm,
    ProcessSignal.sigint,
27
  ];
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

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

48 49 50 51 52 53 54
/// 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);

55
  static LocalSignals instance = LocalSignals._(
56 57
    Signals.defaultExitSignals,
  );
58 59

  final List<ProcessSignal> exitSignals;
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

  // 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>{});
84
    _handlersTable[signal]![token] = handler;
85 86

    _handlersList.putIfAbsent(signal, () => <SignalHandler>[]);
87
    _handlersList[signal]!.add(handler);
88 89 90

    // If we added the first one, then call signal.watch(), listen, and cache
    // the stream controller.
91
    if (_handlersList[signal]!.length == 1) {
92 93 94
      _streamSubscriptions[signal] = signal.watch().listen(
        _handleSignal,
        onError: (Object e) {
95 96
          _handlersTable[signal]?.remove(token);
          _handlersList[signal]?.remove(handler);
97 98
        },
      );
99 100 101 102 103 104 105 106 107 108 109
    }
    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.
110
    if (!_handlersTable[signal]!.containsKey(token)) {
111 112
      return false;
    }
113
    final SignalHandler? handler = _handlersTable[signal]!.remove(token);
114 115 116
    if (handler == null) {
      return false;
    }
117
    final bool removed = _handlersList[signal]!.remove(handler);
118 119 120 121 122 123 124
    if (!removed) {
      return false;
    }

    // If _handlersList[signal] is empty, then lookup the cached stream
    // controller and unsubscribe from the stream.
    if (_handlersList.isEmpty) {
125
      await _streamSubscriptions[signal]?.cancel();
126 127 128 129 130
    }
    return true;
  }

  Future<void> _handleSignal(ProcessSignal s) async {
131 132 133 134 135 136 137 138 139 140
    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);
          }
141 142 143 144 145 146 147 148 149 150
        }
      }
    }
    // If this was a signal that should cause the process to go down, then
    // call exit();
    if (_shouldExitFor(s)) {
      exit(0);
    }
  }

151
  bool _shouldExitFor(ProcessSignal signal) => exitSignals.contains(signal);
152
}