Skip to content

Commit 04d653d

Browse files
committed
even more graceful shutdown
1 parent be2703a commit 04d653d

File tree

3 files changed

+92
-40
lines changed

3 files changed

+92
-40
lines changed

src/server/services/db.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ export class DatabaseService {
108108
}
109109

110110
private _beginTransaction(conn: Connection): Observable<Connection> {
111-
return Observable.create(obs => {
111+
return new Observable(obs => {
112112
conn.beginTransaction((err) => {
113113
if (err) {
114114
return obs.error(err);
115115
}
116116
obs.next(conn);
117-
return obs.complete(conn);
117+
obs.complete();
118118
});
119119
});
120120
}

src/server/services/health.ts

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as cluster from 'cluster';
2+
import * as inspector from 'inspector';
23
import { Server } from 'net';
3-
import { BehaviorSubject, forkJoin, Observable, of } from 'rxjs';
4-
import { distinctUntilChanged, switchMap } from 'rxjs/operators';
4+
import { BehaviorSubject, forkJoin, Observable, of, race, timer } from 'rxjs';
5+
import { distinctUntilChanged, switchMap, take, tap } from 'rxjs/operators';
56
import { LoggingService } from './logger';
67

78
const EXIT_SIGNALS: string[] = [
@@ -17,8 +18,6 @@ export class HealthService {
1718

1819
private _servers: Server[] = [];
1920
private _services = [];
20-
21-
private _timeouts = {};
2221
private _listeners = [];
2322

2423
constructor(
@@ -33,6 +32,30 @@ export class HealthService {
3332
})
3433
);
3534
});
35+
36+
if (cluster.isWorker) {
37+
cluster.worker.on('message', (msg) => {
38+
if (EXIT_SIGNALS.indexOf(msg) >= 0) {
39+
if (!this._isHealthy.value) {
40+
this._logger.log(`${msg} ignored. Process is already terminating`);
41+
return;
42+
}
43+
this._isHealthy.next(false);
44+
this._logger.log(`shutting down...`);
45+
this._cleanup(msg)
46+
.subscribe(
47+
_ => {
48+
this._logger.log('Goodbye.');
49+
process.exit(0);
50+
},
51+
err => {
52+
this._logger.logError(err);
53+
this._logger.log('Goodbye.');
54+
process.exit(1);
55+
});
56+
}
57+
});
58+
}
3659
}
3760

3861
setHealthy(isHealthy: boolean) {
@@ -71,18 +94,28 @@ export class HealthService {
7194
return alive;
7295
}
7396

74-
private _cleanup(signal: string) {
97+
private _cleanup(signal) {
98+
if (inspector && inspector.url()) {
99+
this._logger.log('closing inspector');
100+
inspector.close();
101+
}
75102
if (cluster.isMaster) {
76103
return forkJoin([
77104
of(true),
78105
...(this._servers.map(s => this._closeServer(s)))
79106
]).pipe(
80107
switchMap(_ => {
81-
const workers = this.getLivingWorkers() || [];
82-
return forkJoin([
83-
of(true),
84-
...(workers.map(w => this._cleanupWorker(w, signal)))
85-
]);
108+
return race(
109+
this._cleanupWorkers(signal),
110+
timer(10 * 1000)
111+
.pipe(
112+
take(1),
113+
tap(_ => {
114+
this._logger.log('[ master ]: Workers took too long to shut down');
115+
this.getLivingWorkers().forEach(w => w.kill('SIGKILL'));
116+
})
117+
)
118+
);
86119
}),
87120
switchMap(_ => {
88121
return forkJoin([
@@ -108,24 +141,25 @@ export class HealthService {
108141
}
109142

110143
private _handleExitSignal(signal: string) {
111-
if (!this._isHealthy.value) {
112-
this._logger.log(`${signal} ignored. Process is already terminating`);
144+
if (cluster.isMaster) {
145+
if (!this._isHealthy.value) {
146+
this._logger.log(`${signal} ignored. Process is already terminating`);
147+
return;
148+
}
149+
this._isHealthy.next(false);
150+
this._logger.log(`[ master ]: caught ${signal}. shutting down...`);
151+
this._cleanup(signal)
152+
.subscribe(_ => {
153+
this._logger.log(`[ master ]: Goodbye.`);
154+
process.exit(0)
155+
}, err => {
156+
this._logger.logError(err);
157+
process.exit(1);
158+
});
159+
} else {
160+
// workers wait to die
113161
return;
114162
}
115-
this._isHealthy.next(false);
116-
this._logger.log(`${cluster.isMaster ? '[ master ]: ' : ''}caught ${signal}. shutting down...`);
117-
this._cleanup(signal)
118-
.subscribe(_ => {
119-
if (cluster.isMaster) {
120-
this._logger.log(`[ master ]: Goodbye.`);
121-
} else {
122-
this._logger.log(`Goodbye.`);
123-
}
124-
process.exit(0)
125-
}, err => {
126-
this._logger.logError(err);
127-
process.exit(1);
128-
});
129163
}
130164

131165
private _closeServer(server: Server): Observable<any> {
@@ -137,24 +171,39 @@ export class HealthService {
137171
});
138172
}
139173

140-
private _cleanupWorker(worker: cluster.Worker, signal: string): Observable<any> {
174+
private _cleanupWorkers(signal): Observable<any> {
175+
if (!cluster.isMaster) {
176+
return of(true);
177+
}
178+
this.getLivingWorkers().forEach(w => {
179+
w.send(signal);
180+
});
141181
return new Observable(obs => {
142-
worker.on('exit', (code, signal) => {
143-
if (this._timeouts[worker.id]) {
144-
clearTimeout(this._timeouts[worker.id]);
145-
}
146-
obs.next(`${worker.id} exited`);
182+
cluster.disconnect(() => {
183+
obs.next(true);
147184
obs.complete();
148185
});
149-
150-
worker.kill(signal);
151-
this._timeouts[worker.id] = setTimeout(() => {
152-
this._logger.log('worker shutdown time expired. forcefully killing worker', worker.id);
153-
worker.process.kill('SIGKILL');
154-
}, 6000); // based on maximum `keep-alive` timeout
155186
});
156187
}
157188

189+
// private _cleanupWorker(worker: cluster.Worker, signal: string): Observable<any> {
190+
// return new Observable(obs => {
191+
// worker.on('exit', (code, signal) => {
192+
// if (this._timeouts[worker.id]) {
193+
// clearTimeout(this._timeouts[worker.id]);
194+
// }
195+
// obs.next(`${worker.id} exited`);
196+
// obs.complete();
197+
// });
198+
199+
// worker.kill(signal);
200+
// this._timeouts[worker.id] = setTimeout(() => {
201+
// this._logger.log('worker shutdown time expired. forcefully killing worker', worker.id);
202+
// worker.process.kill('SIGKILL');
203+
// }, 6000); // based on maximum `keep-alive` timeout
204+
// });
205+
// }
206+
158207
private _cleanupService(service): Observable<any> {
159208
if (service && service.cleanup) {
160209
return service.cleanup();

src/server/services/logger.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export class LoggingService {
2626

2727
log(...messages: any[]): void {
2828
if (cluster.isMaster) {
29+
if (messages.length && !/^\[ (m|w)/i.test(messages[0])) {
30+
messages.unshift(`[ master ]:`);
31+
}
2932
console.log(...messages);
3033
} else {
3134
if (cluster.worker.isConnected()) {

0 commit comments

Comments
 (0)