grpc-js: shutdown improvements

This commit maintains a Set of all active sessions. This allows
tryShutdown() to gracefully stop the server properly (as
recommended in the Node HTTP2 documentation). The same Set of
sessions also allows forceShutdown() to be implemented.
This commit is contained in:
cjihrig 2019-06-21 15:05:58 -04:00
parent 0a306fa983
commit 00b091a1b1
No known key found for this signature in database
GPG Key ID: 7434390BDBE9B9C5
3 changed files with 45 additions and 25 deletions

View File

@ -91,6 +91,7 @@ export class Server {
string,
UntypedHandler
>();
private sessions = new Set<http2.ServerHttp2Session>();
private started = false;
constructor(options?: object) {}
@ -223,7 +224,22 @@ export class Server {
}
forceShutdown(): void {
throw new Error('Not yet implemented');
// Close the server if it is still running.
if (this.http2Server && this.http2Server.listening) {
this.http2Server.close();
}
this.started = false;
// Always destroy any available sessions. It's possible that one or more
// tryShutdown() calls are in progress. Don't wait on them to finish.
this.sessions.forEach(session => {
// Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to
// recognize destroy(code) as a valid signature.
// tslint:disable-next-line:no-any
session.destroy(http2.constants.NGHTTP2_CANCEL as any);
});
this.sessions.clear();
}
register<RequestType, ResponseType>(
@ -259,17 +275,34 @@ export class Server {
}
tryShutdown(callback: (error?: Error) => void): void {
callback = typeof callback === 'function' ? callback : noop;
let pendingChecks = 0;
if (this.http2Server === null) {
callback(new Error('server is not running'));
return;
function maybeCallback(): void {
pendingChecks--;
if (pendingChecks === 0) {
callback();
}
}
this.http2Server.close((err?: Error) => {
this.started = false;
callback(err);
// Close the server if necessary.
this.started = false;
if (this.http2Server && this.http2Server.listening) {
pendingChecks++;
this.http2Server.close(maybeCallback);
}
// If any sessions are active, close them gracefully.
pendingChecks += this.sessions.size;
this.sessions.forEach(session => {
session.close(maybeCallback);
});
// If the server is closed and there are no active sessions, just call back.
if (pendingChecks === 0) {
callback();
}
}
addHttp2Port(): void {
@ -352,6 +385,8 @@ export class Server {
session.destroy();
return;
}
this.sessions.add(session);
});
}
}

View File

@ -386,9 +386,9 @@ describe('Other conditions', () => {
});
});
after(done => {
after(() => {
client.close();
server.tryShutdown(done);
server.forceShutdown();
});
describe('Server receiving bad input', () => {

View File

@ -127,17 +127,6 @@ describe('Server', () => {
});
});
describe('tryShutdown', () => {
it('calls back with an error if the server is not running', done => {
const server = new Server();
server.tryShutdown(err => {
assert(err !== undefined && err.message === 'server is not running');
done();
});
});
});
describe('start', () => {
let server: Server;
@ -237,10 +226,6 @@ describe('Server', () => {
server.addProtoService();
}, /Not implemented. Use addService\(\) instead/);
assert.throws(() => {
server.forceShutdown();
}, /Not yet implemented/);
assert.throws(() => {
server.addHttp2Port();
}, /Not yet implemented/);