Skip to content

Commit 6732205

Browse files
authored
Merge pull request #2835 from murgatroid99/grpc-js_1.12_1.11_bugfix_merge
grpc-js: Port bugfixes from 1.11.x into 1.12.x
2 parents 707f6cb + 65cd9b6 commit 6732205

9 files changed

+86
-80
lines changed

packages/grpc-js-xds/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grpc/grpc-js-xds",
3-
"version": "1.11.0",
3+
"version": "1.12.0",
44
"description": "Plugin for @grpc/grpc-js. Adds the xds:// URL scheme and associated features.",
55
"main": "build/src/index.js",
66
"scripts": {
@@ -52,7 +52,7 @@
5252
"xxhash-wasm": "^1.0.2"
5353
},
5454
"peerDependencies": {
55-
"@grpc/grpc-js": "~1.11.0"
55+
"@grpc/grpc-js": "~1.12.0"
5656
},
5757
"engines": {
5858
"node": ">=10.10.0"

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.11.0",
3+
"version": "1.12.1",
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/internal-channel.ts

+12-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
getDefaultAuthority,
3232
mapUriDefaultScheme,
3333
} from './resolver';
34-
import { trace } from './logging';
34+
import { trace, isTracerEnabled } from './logging';
3535
import { SubchannelAddress } from './subchannel-address';
3636
import { mapProxyName } from './http_proxy';
3737
import { GrpcUri, parseUri, uriToString } from './uri-parser';
@@ -426,15 +426,17 @@ export class InternalChannel {
426426
JSON.stringify(options, undefined, 2)
427427
);
428428
const error = new Error();
429-
trace(
430-
LogVerbosity.DEBUG,
431-
'channel_stacktrace',
432-
'(' +
433-
this.channelzRef.id +
434-
') ' +
435-
'Channel constructed \n' +
436-
error.stack?.substring(error.stack.indexOf('\n') + 1)
437-
);
429+
if (isTracerEnabled('channel_stacktrace')){
430+
trace(
431+
LogVerbosity.DEBUG,
432+
'channel_stacktrace',
433+
'(' +
434+
this.channelzRef.id +
435+
') ' +
436+
'Channel constructed \n' +
437+
error.stack?.substring(error.stack.indexOf('\n') + 1)
438+
);
439+
}
438440
this.lastActivityTimestamp = new Date();
439441
}
440442

packages/grpc-js/src/load-balancer-pick-first.ts

+33-62
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
214214
*/
215215
private connectionDelayTimeout: NodeJS.Timeout;
216216

217-
private triedAllSubchannels = false;
218-
219217
/**
220218
* The LB policy enters sticky TRANSIENT_FAILURE mode when all
221219
* subchannels have failed to connect at least once, and it stays in that
@@ -226,12 +224,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
226224

227225
private reportHealthStatus: boolean;
228226

229-
/**
230-
* Indicates whether we called channelControlHelper.requestReresolution since
231-
* the last call to updateAddressList
232-
*/
233-
private requestedResolutionSinceLastUpdate = false;
234-
235227
/**
236228
* The most recent error reported by any subchannel as it transitioned to
237229
* TRANSIENT_FAILURE.
@@ -261,6 +253,10 @@ export class PickFirstLoadBalancer implements LoadBalancer {
261253
return this.children.every(child => child.hasReportedTransientFailure);
262254
}
263255

256+
private resetChildrenReportedTF() {
257+
this.children.every(child => child.hasReportedTransientFailure = false);
258+
}
259+
264260
private calculateAndReportNewState() {
265261
if (this.currentPick) {
266262
if (this.reportHealthStatus && !this.currentPick.isHealthy()) {
@@ -293,23 +289,15 @@ export class PickFirstLoadBalancer implements LoadBalancer {
293289
}
294290

295291
private requestReresolution() {
296-
this.requestedResolutionSinceLastUpdate = true;
297292
this.channelControlHelper.requestReresolution();
298293
}
299294

300295
private maybeEnterStickyTransientFailureMode() {
301296
if (!this.allChildrenHaveReportedTF()) {
302297
return;
303298
}
304-
if (!this.requestedResolutionSinceLastUpdate) {
305-
/* Each time we get an update we reset each subchannel's
306-
* hasReportedTransientFailure flag, so the next time we get to this
307-
* point after that, each subchannel has reported TRANSIENT_FAILURE
308-
* at least once since then. That is the trigger for requesting
309-
* reresolution, whether or not the LB policy is already in sticky TF
310-
* mode. */
311-
this.requestReresolution();
312-
}
299+
this.requestReresolution();
300+
this.resetChildrenReportedTF();
313301
if (this.stickyTransientFailureMode) {
314302
this.calculateAndReportNewState();
315303
return;
@@ -323,21 +311,16 @@ export class PickFirstLoadBalancer implements LoadBalancer {
323311

324312
private removeCurrentPick() {
325313
if (this.currentPick !== null) {
326-
/* Unref can cause a state change, which can cause a change in the value
327-
* of this.currentPick, so we hold a local reference to make sure that
328-
* does not impact this function. */
329-
const currentPick = this.currentPick;
330-
this.currentPick = null;
331-
currentPick.unref();
332-
currentPick.removeConnectivityStateListener(this.subchannelStateListener);
314+
this.currentPick.removeConnectivityStateListener(this.subchannelStateListener);
333315
this.channelControlHelper.removeChannelzChild(
334-
currentPick.getChannelzRef()
316+
this.currentPick.getChannelzRef()
335317
);
336-
if (this.reportHealthStatus) {
337-
currentPick.removeHealthStateWatcher(
338-
this.pickedSubchannelHealthListener
339-
);
340-
}
318+
this.currentPick.removeHealthStateWatcher(
319+
this.pickedSubchannelHealthListener
320+
);
321+
// Unref last, to avoid triggering listeners
322+
this.currentPick.unref();
323+
this.currentPick = null;
341324
}
342325
}
343326

@@ -377,9 +360,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
377360

378361
private startNextSubchannelConnecting(startIndex: number) {
379362
clearTimeout(this.connectionDelayTimeout);
380-
if (this.triedAllSubchannels) {
381-
return;
382-
}
383363
for (const [index, child] of this.children.entries()) {
384364
if (index >= startIndex) {
385365
const subchannelState = child.subchannel.getConnectivityState();
@@ -392,7 +372,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
392372
}
393373
}
394374
}
395-
this.triedAllSubchannels = true;
396375
this.maybeEnterStickyTransientFailureMode();
397376
}
398377

@@ -421,20 +400,25 @@ export class PickFirstLoadBalancer implements LoadBalancer {
421400
this.connectionDelayTimeout.unref?.();
422401
}
423402

403+
/**
404+
* Declare that the specified subchannel should be used to make requests.
405+
* This functions the same independent of whether subchannel is a member of
406+
* this.children and whether it is equal to this.currentPick.
407+
* Prerequisite: subchannel.getConnectivityState() === READY.
408+
* @param subchannel
409+
*/
424410
private pickSubchannel(subchannel: SubchannelInterface) {
425-
if (this.currentPick && subchannel.realSubchannelEquals(this.currentPick)) {
426-
return;
427-
}
428411
trace('Pick subchannel with address ' + subchannel.getAddress());
429412
this.stickyTransientFailureMode = false;
430-
this.removeCurrentPick();
431-
this.currentPick = subchannel;
413+
/* Ref before removeCurrentPick and resetSubchannelList to avoid the
414+
* refcount dropping to 0 during this process. */
432415
subchannel.ref();
433-
if (this.reportHealthStatus) {
434-
subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener);
435-
}
436416
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
417+
this.removeCurrentPick();
437418
this.resetSubchannelList();
419+
subchannel.addConnectivityStateListener(this.subchannelStateListener);
420+
subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener);
421+
this.currentPick = subchannel;
438422
clearTimeout(this.connectionDelayTimeout);
439423
this.calculateAndReportNewState();
440424
}
@@ -451,20 +435,11 @@ export class PickFirstLoadBalancer implements LoadBalancer {
451435

452436
private resetSubchannelList() {
453437
for (const child of this.children) {
454-
if (
455-
!(
456-
this.currentPick &&
457-
child.subchannel.realSubchannelEquals(this.currentPick)
458-
)
459-
) {
460-
/* The connectivity state listener is the same whether the subchannel
461-
* is in the list of children or it is the currentPick, so if it is in
462-
* both, removing it here would cause problems. In particular, that
463-
* always happens immediately after the subchannel is picked. */
464-
child.subchannel.removeConnectivityStateListener(
465-
this.subchannelStateListener
466-
);
467-
}
438+
/* Always remoev the connectivity state listener. If the subchannel is
439+
getting picked, it will be re-added then. */
440+
child.subchannel.removeConnectivityStateListener(
441+
this.subchannelStateListener
442+
);
468443
/* Refs are counted independently for the children list and the
469444
* currentPick, so we call unref whether or not the child is the
470445
* currentPick. Channelz child references are also refcounted, so
@@ -476,20 +451,16 @@ export class PickFirstLoadBalancer implements LoadBalancer {
476451
}
477452
this.currentSubchannelIndex = 0;
478453
this.children = [];
479-
this.triedAllSubchannels = false;
480-
this.requestedResolutionSinceLastUpdate = false;
481454
}
482455

483456
private connectToAddressList(addressList: SubchannelAddress[]) {
457+
trace('connectToAddressList([' + addressList.map(address => subchannelAddressToString(address)) + '])');
484458
const newChildrenList = addressList.map(address => ({
485459
subchannel: this.channelControlHelper.createSubchannel(address, {}, null),
486460
hasReportedTransientFailure: false,
487461
}));
488-
trace('connectToAddressList([' + addressList.map(address => subchannelAddressToString(address)) + '])');
489462
for (const { subchannel } of newChildrenList) {
490463
if (subchannel.getConnectivityState() === ConnectivityState.READY) {
491-
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
492-
subchannel.addConnectivityStateListener(this.subchannelStateListener);
493464
this.pickSubchannel(subchannel);
494465
return;
495466
}

packages/grpc-js/src/load-balancer-round-robin.ts

+7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
112112
channelControlHelper,
113113
{
114114
updateState: (connectivityState, picker) => {
115+
/* Ensure that name resolution is requested again after active
116+
* connections are dropped. This is more aggressive than necessary to
117+
* accomplish that, so we are counting on resolvers to have
118+
* reasonable rate limits. */
119+
if (this.currentState === ConnectivityState.READY && connectivityState !== ConnectivityState.READY) {
120+
this.channelControlHelper.requestReresolution();
121+
}
115122
this.calculateAndUpdateState();
116123
},
117124
}

packages/grpc-js/src/resolver-dns.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
registerResolver,
2121
registerDefaultScheme,
2222
} from './resolver';
23-
import { promises as dns } from 'node:dns';
23+
import { promises as dns } from 'dns';
2424
import { extractAndSelectServiceConfig, ServiceConfig } from './service-config';
2525
import { Status } from './constants';
2626
import { StatusObject } from './call-interface';

packages/grpc-js/src/retrying-call.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
398398
return list.some(
399399
value =>
400400
value === code ||
401-
value.toString().toLowerCase() === Status[code].toLowerCase()
401+
value.toString().toLowerCase() === Status[code]?.toLowerCase()
402402
);
403403
}
404404

packages/grpc-js/src/transport.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,8 @@ class Http2Transport implements Transport {
223223
);
224224

225225
session.once('error', error => {
226-
/* Do nothing here. Any error should also trigger a close event, which is
227-
* where we want to handle that. */
228226
this.trace('connection closed with error ' + (error as Error).message);
227+
this.handleDisconnect();
229228
});
230229

231230
if (logging.isTracerEnabled(TRACER_NAME)) {
@@ -383,6 +382,9 @@ class Http2Transport implements Transport {
383382
* Handle connection drops, but not GOAWAYs.
384383
*/
385384
private handleDisconnect() {
385+
if (this.disconnectHandled) {
386+
return;
387+
}
386388
this.clearKeepaliveTimeout();
387389
this.reportDisconnectToOwner(false);
388390
/* Give calls an event loop cycle to finish naturally before reporting the
@@ -773,6 +775,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
773775
);
774776
this.session = session;
775777
let errorMessage = 'Failed to connect';
778+
let reportedError = false;
776779
session.unref();
777780
session.once('connect', () => {
778781
session.removeAllListeners();
@@ -783,12 +786,19 @@ export class Http2SubchannelConnector implements SubchannelConnector {
783786
this.session = null;
784787
// Leave time for error event to happen before rejecting
785788
setImmediate(() => {
786-
reject(`${errorMessage} (${new Date().toISOString()})`);
789+
if (!reportedError) {
790+
reportedError = true;
791+
reject(`${errorMessage} (${new Date().toISOString()})`);
792+
}
787793
});
788794
});
789795
session.once('error', error => {
790796
errorMessage = (error as Error).message;
791797
this.trace('connection failed with error ' + errorMessage);
798+
if (!reportedError) {
799+
reportedError = true;
800+
reject(`${errorMessage} (${new Date().toISOString()})`);
801+
}
792802
});
793803
});
794804
}

packages/grpc-js/test/test-retry.ts

+16
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,22 @@ describe('Retries', () => {
323323
}
324324
);
325325
});
326+
327+
it('Should not retry on custom error code', done => {
328+
const metadata = new grpc.Metadata();
329+
metadata.set('succeed-on-retry-attempt', '2');
330+
metadata.set('respond-with-status', '300');
331+
client.echo(
332+
{ value: 'test value', value2: 3 },
333+
metadata,
334+
(error: grpc.ServiceError, response: any) => {
335+
assert(error);
336+
assert.strictEqual(error.code, 300);
337+
assert.strictEqual(error.details, 'Failed on retry 0');
338+
done();
339+
}
340+
);
341+
});
326342
});
327343

328344
describe('Client with hedging configured', () => {

0 commit comments

Comments
 (0)