grpc-js: Fix and optimize IDLE timeouts

This commit is contained in:
Michael Lumish 2024-01-16 14:18:05 -08:00
parent 442e3ea24a
commit 6da0b49dbc
2 changed files with 35 additions and 10 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@grpc/grpc-js",
"version": "1.9.13",
"version": "1.9.14",
"description": "gRPC Library for Node - pure JS implementation",
"homepage": "https://grpc.io/",
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",

View File

@ -184,6 +184,7 @@ export class InternalChannel {
private callCount = 0;
private idleTimer: NodeJS.Timeout | null = null;
private readonly idleTimeoutMs: number;
private lastActivityTimestamp: Date;
// Channelz info
private readonly channelzEnabled: boolean = true;
@ -409,6 +410,7 @@ export class InternalChannel {
'Channel constructed \n' +
error.stack?.substring(error.stack.indexOf('\n') + 1)
);
this.lastActivityTimestamp = new Date();
}
private getChannelzInfo(): ChannelInfo {
@ -556,19 +558,44 @@ export class InternalChannel {
this.resolvingLoadBalancer.destroy();
this.updateState(ConnectivityState.IDLE);
this.currentPicker = new QueuePicker(this.resolvingLoadBalancer);
if (this.idleTimer) {
clearTimeout(this.idleTimer);
this.idleTimer = null;
}
}
private maybeStartIdleTimer() {
if (this.connectivityState !== ConnectivityState.SHUTDOWN && this.callCount === 0) {
this.idleTimer = setTimeout(() => {
private startIdleTimeout(timeoutMs: number) {
this.idleTimer = setTimeout(() => {
if (this.callCount > 0) {
/* If there is currently a call, the channel will not go idle for a
* period of at least idleTimeoutMs, so check again after that time.
*/
this.startIdleTimeout(this.idleTimeoutMs);
return;
}
const now = new Date();
const timeSinceLastActivity = now.valueOf() - this.lastActivityTimestamp.valueOf();
if (timeSinceLastActivity >= this.idleTimeoutMs) {
this.trace(
'Idle timer triggered after ' +
this.idleTimeoutMs +
'ms of inactivity'
);
this.enterIdle();
}, this.idleTimeoutMs);
this.idleTimer.unref?.();
} else {
/* Whenever the timer fires with the latest activity being too recent,
* set the timer again for the time when the time since the last
* activity is equal to the timeout. This should result in the timer
* firing no more than once every idleTimeoutMs/2 on average. */
this.startIdleTimeout(this.idleTimeoutMs - timeSinceLastActivity);
}
}, timeoutMs);
this.idleTimer.unref?.();
}
private maybeStartIdleTimer() {
if (this.connectivityState !== ConnectivityState.SHUTDOWN && !this.idleTimer) {
this.startIdleTimeout(this.idleTimeoutMs);
}
}
@ -577,10 +604,6 @@ export class InternalChannel {
this.callTracker.addCallStarted();
}
this.callCount += 1;
if (this.idleTimer) {
clearTimeout(this.idleTimer);
this.idleTimer = null;
}
}
private onCallEnd(status: StatusObject) {
@ -592,6 +615,7 @@ export class InternalChannel {
}
}
this.callCount -= 1;
this.lastActivityTimestamp = new Date();
this.maybeStartIdleTimer();
}
@ -729,6 +753,7 @@ export class InternalChannel {
const connectivityState = this.connectivityState;
if (tryToConnect) {
this.resolvingLoadBalancer.exitIdle();
this.lastActivityTimestamp = new Date();
this.maybeStartIdleTimer();
}
return connectivityState;