1 | /**
|
2 | * @license
|
3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
7 | * Code distributed by Google as part of the polymer project is also
|
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
9 | */
|
10 | ;
|
11 |
|
12 | /**
|
13 | * CleanKill hooks the interrupt handler, and provides callbacks for your code
|
14 | * to cleanly shut down before the process exits.
|
15 | */
|
16 |
|
17 | /**
|
18 | * The type of a cleankill interrupt handler.
|
19 | */
|
20 | export type Handler = () => Promise<void>;
|
21 |
|
22 | const interruptHandlers: Handler[] = [];
|
23 |
|
24 | /**
|
25 | * Register a handler to occur on SIGINT. The handler must return a promise if
|
26 | * it has any asynchronous work to do. The process will be terminated once
|
27 | * all handlers complete. If a handler throws or the promise it returns rejects
|
28 | * then the process will be terminated immediately.
|
29 | */
|
30 | export function onInterrupt(handler: Handler): void {
|
31 | interruptHandlers.push(handler);
|
32 | }
|
33 |
|
34 | /**
|
35 | * Waits for all promises to settle, then rejects with the first error, if any.
|
36 | */
|
37 | export async function promiseAllStrict(
|
38 | promises: Promise<any>[]): Promise<void> {
|
39 | let errors = await Promise.all(
|
40 | promises.map((p) => p.then(() => null, (e) => e)));
|
41 | let firstError = errors.find((e) => e != null);
|
42 | if (firstError) {
|
43 | throw firstError;
|
44 | }
|
45 | }
|
46 |
|
47 | /**
|
48 | * Call all interrupt handlers, and call the callback when they all complete.
|
49 | *
|
50 | * Clears the list of interrupt handlers.
|
51 | */
|
52 | export async function close(): Promise<void> {
|
53 | const promises = interruptHandlers.map((handler) => handler());
|
54 | // Empty the array in place. Looks weird, totally works.
|
55 | interruptHandlers.length = 0;
|
56 |
|
57 | await promiseAllStrict(promises);
|
58 | }
|
59 |
|
60 | let interrupted = false;
|
61 |
|
62 | /**
|
63 | * Calls all interrupt handlers, then exits with exit code 0.
|
64 | *
|
65 | * If called more than once it skips waiting for the interrupt handlers to
|
66 | * finish and exits with exit code 1. If there are any errors with interrupt
|
67 | * handlers, the process exits immediately with exit code 2.
|
68 | *
|
69 | * This function is called when a SIGINT is received.
|
70 | */
|
71 | export function interrupt(): void {
|
72 | if (interrupted) {
|
73 | console.log('\nGot multiple interrupts, exiting immediately!');
|
74 | return process.exit(1);
|
75 | }
|
76 | interrupted = true;
|
77 |
|
78 | close().then(() => process.exit(0), (error) => {
|
79 | error = error || 'cleankill interrupt handler failed without a message';
|
80 | console.error(error.stack || error.message || error);
|
81 | process.exit(2);
|
82 | });
|
83 | console.log('\nShutting down. Press ctrl-c again to kill immediately.');
|
84 | }
|
85 |
|
86 | process.on('SIGINT', interrupt);
|