blob: 3899d4f3a9159cb1891c9a59db505070cf2d9488 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Zachary Anderson7fa5dd72019-09-26 08:12:48 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'async_guard.dart';
8import 'context.dart';
9import 'io.dart';
10
11typedef SignalHandler = FutureOr<void> Function(ProcessSignal signal);
12
13Signals get signals => Signals.instance;
14
Zachary Anderson45f3c8d2019-09-30 07:38:11 -070015// The default list of signals that should cause the process to exit.
16const List<ProcessSignal> _defaultExitSignals = <ProcessSignal>[
17 ProcessSignal.SIGTERM,
18 ProcessSignal.SIGINT,
19 ProcessSignal.SIGKILL,
20];
21
Zachary Anderson7fa5dd72019-09-26 08:12:48 -070022/// A class that manages signal handlers
23///
24/// Signal handlers are run in the order that they were added.
25abstract class Signals {
Zachary Anderson45f3c8d2019-09-30 07:38:11 -070026 factory Signals({
27 List<ProcessSignal> exitSignals = _defaultExitSignals,
28 }) => _DefaultSignals._(exitSignals);
Zachary Anderson7fa5dd72019-09-26 08:12:48 -070029
30 static Signals get instance => context.get<Signals>();
31
32 /// Adds a signal handler to run on receipt of signal.
33 ///
34 /// The handler will run after all handlers that were previously added for the
35 /// signal. The function returns an abstract token that should be provided to
36 /// removeHandler to remove the handler.
37 Object addHandler(ProcessSignal signal, SignalHandler handler);
38
39 /// Removes a signal handler.
40 ///
41 /// Removes the signal handler for the signal identified by the abstract
42 /// token parameter. Returns true if the handler was removed and false
43 /// otherwise.
44 Future<bool> removeHandler(ProcessSignal signal, Object token);
45
46 /// If a [SignalHandler] throws an error, either synchronously or
47 /// asynchronously, it will be added to this stream instead of propagated.
48 Stream<Object> get errors;
49}
50
51class _DefaultSignals implements Signals {
Zachary Anderson45f3c8d2019-09-30 07:38:11 -070052 _DefaultSignals._(this.exitSignals);
53
54 final List<ProcessSignal> exitSignals;
Zachary Anderson7fa5dd72019-09-26 08:12:48 -070055
56 // A table mapping (signal, token) -> signal handler.
57 final Map<ProcessSignal, Map<Object, SignalHandler>> _handlersTable =
58 <ProcessSignal, Map<Object, SignalHandler>>{};
59
60 // A table mapping (signal) -> signal handler list. The list is in the order
61 // that the signal handlers should be run.
62 final Map<ProcessSignal, List<SignalHandler>> _handlersList =
63 <ProcessSignal, List<SignalHandler>>{};
64
65 // A table mapping (signal) -> low-level signal event stream.
66 final Map<ProcessSignal, StreamSubscription<ProcessSignal>> _streamSubscriptions =
67 <ProcessSignal, StreamSubscription<ProcessSignal>>{};
68
69 // The stream controller for errors coming from signal handlers.
70 final StreamController<Object> _errorStreamController = StreamController<Object>.broadcast();
71
72 @override
73 Stream<Object> get errors => _errorStreamController.stream;
74
75 @override
76 Object addHandler(ProcessSignal signal, SignalHandler handler) {
77 final Object token = Object();
78 _handlersTable.putIfAbsent(signal, () => <Object, SignalHandler>{});
79 _handlersTable[signal][token] = handler;
80
81 _handlersList.putIfAbsent(signal, () => <SignalHandler>[]);
82 _handlersList[signal].add(handler);
83
84 // If we added the first one, then call signal.watch(), listen, and cache
85 // the stream controller.
86 if (_handlersList[signal].length == 1) {
87 _streamSubscriptions[signal] = signal.watch().listen(_handleSignal);
88 }
89 return token;
90 }
91
92 @override
93 Future<bool> removeHandler(ProcessSignal signal, Object token) async {
94 // We don't know about this signal.
95 if (!_handlersTable.containsKey(signal)) {
96 return false;
97 }
98 // We don't know about this token.
99 if (!_handlersTable[signal].containsKey(token)) {
100 return false;
101 }
102 final SignalHandler handler = _handlersTable[signal][token];
103 final bool removed = _handlersList[signal].remove(handler);
104 if (!removed) {
105 return false;
106 }
107
108 // If _handlersList[signal] is empty, then lookup the cached stream
109 // controller and unsubscribe from the stream.
110 if (_handlersList.isEmpty) {
111 await _streamSubscriptions[signal].cancel();
112 }
113 return true;
114 }
115
116 Future<void> _handleSignal(ProcessSignal s) async {
117 for (SignalHandler handler in _handlersList[s]) {
118 try {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100119 await asyncGuard<void>(() async => handler(s));
Zachary Anderson7fa5dd72019-09-26 08:12:48 -0700120 } catch (e) {
121 if (_errorStreamController.hasListener) {
122 _errorStreamController.add(e);
123 }
124 }
125 }
126 // If this was a signal that should cause the process to go down, then
127 // call exit();
128 if (_shouldExitFor(s)) {
129 exit(0);
130 }
131 }
132
Zachary Anderson45f3c8d2019-09-30 07:38:11 -0700133 bool _shouldExitFor(ProcessSignal signal) => exitSignals.contains(signal);
Zachary Anderson7fa5dd72019-09-26 08:12:48 -0700134}