Skip to content

Commit b51afd3

Browse files
authored
Merge pull request grpc#1580 from murgatroid99/grpc-js_event_loop_liveness
grpc-js: Add a timer that holds the event loop open while there are pending calls
2 parents 2445875 + a160081 commit b51afd3

File tree

2 files changed

+28
-4
lines changed

2 files changed

+28
-4
lines changed

packages/grpc-js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grpc/grpc-js",
3-
"version": "1.1.6",
3+
"version": "1.1.7",
44
"description": "gRPC Library for Node - pure JS implementation",
55
"homepage": "https://grpc.io/",
66
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",

packages/grpc-js/src/channel.ts

+27-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export enum ConnectivityState {
4848
SHUTDOWN,
4949
}
5050

51+
/**
52+
* See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args
53+
*/
54+
const MAX_TIMEOUT_TIME = 2147483647;
55+
5156
let nextCallNumber = 0;
5257

5358
function getNewCallNumber(): number {
@@ -137,6 +142,14 @@ export class ChannelImplementation implements Channel {
137142
private defaultAuthority: string;
138143
private filterStackFactory: FilterStackFactory;
139144
private target: GrpcUri;
145+
/**
146+
* This timer does not do anything on its own. Its purpose is to hold the
147+
* event loop open while there are any pending calls for the channel that
148+
* have not yet been assigned to specific subchannels. In other words,
149+
* the invariant is that callRefTimer is reffed if and only if pickQueue
150+
* is non-empty.
151+
*/
152+
private callRefTimer: NodeJS.Timer;
140153
constructor(
141154
target: string,
142155
private readonly credentials: ChannelCredentials,
@@ -177,6 +190,10 @@ export class ChannelImplementation implements Channel {
177190
`Could not find a default scheme for target name "${target}"`
178191
);
179192
}
193+
194+
this.callRefTimer = setInterval(() => {}, MAX_TIMEOUT_TIME);
195+
this.callRefTimer.unref?.();
196+
180197
if (this.options['grpc.default_authority']) {
181198
this.defaultAuthority = this.options['grpc.default_authority'] as string;
182199
} else {
@@ -206,6 +223,7 @@ export class ChannelImplementation implements Channel {
206223
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
207224
this.currentPicker = picker;
208225
const queueCopy = this.pickQueue.slice();
226+
this.callRefTimer.unref?.();
209227
this.pickQueue = [];
210228
for (const { callStream, callMetadata } of queueCopy) {
211229
this.tryPick(callStream, callMetadata);
@@ -232,6 +250,11 @@ export class ChannelImplementation implements Channel {
232250
]);
233251
}
234252

253+
private pushPick(callStream: Http2CallStream, callMetadata: Metadata) {
254+
this.callRefTimer.ref?.();
255+
this.pickQueue.push({ callStream, callMetadata });
256+
}
257+
235258
/**
236259
* Check the picker output for the given call and corresponding metadata,
237260
* and take any relevant actions. Should not be called while iterating
@@ -276,7 +299,7 @@ export class ChannelImplementation implements Channel {
276299
' has state ' +
277300
ConnectivityState[pickResult.subchannel!.getConnectivityState()]
278301
);
279-
this.pickQueue.push({ callStream, callMetadata });
302+
this.pushPick(callStream, callMetadata);
280303
break;
281304
}
282305
/* We need to clone the callMetadata here because the transparent
@@ -367,11 +390,11 @@ export class ChannelImplementation implements Channel {
367390
}
368391
break;
369392
case PickResultType.QUEUE:
370-
this.pickQueue.push({ callStream, callMetadata });
393+
this.pushPick(callStream, callMetadata);
371394
break;
372395
case PickResultType.TRANSIENT_FAILURE:
373396
if (callMetadata.getOptions().waitForReady) {
374-
this.pickQueue.push({ callStream, callMetadata });
397+
this.pushPick(callStream, callMetadata);
375398
} else {
376399
callStream.cancelWithStatus(
377400
pickResult.status!.code,
@@ -433,6 +456,7 @@ export class ChannelImplementation implements Channel {
433456
close() {
434457
this.resolvingLoadBalancer.destroy();
435458
this.updateState(ConnectivityState.SHUTDOWN);
459+
clearInterval(this.callRefTimer);
436460

437461
this.subchannelPool.unrefUnusedSubchannels();
438462
}

0 commit comments

Comments
 (0)