Skip to content

Commit a1b8972

Browse files
authored
Merge pull request #2806 from murgatroid99/grpc-js_connectivity_management_fixes
grpc-js: Simplify pick_first behavior
2 parents 0c5ab98 + 3d43b1a commit a1b8972

File tree

1 file changed

+33
-62
lines changed

1 file changed

+33
-62
lines changed

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

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

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

226224
private reportHealthStatus: boolean;
227225

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

254+
private resetChildrenReportedTF() {
255+
this.children.every(child => child.hasReportedTransientFailure = false);
256+
}
257+
262258
private calculateAndReportNewState() {
263259
if (this.currentPick) {
264260
if (this.reportHealthStatus && !this.currentPick.isHealthy()) {
@@ -291,23 +287,15 @@ export class PickFirstLoadBalancer implements LoadBalancer {
291287
}
292288

293289
private requestReresolution() {
294-
this.requestedResolutionSinceLastUpdate = true;
295290
this.channelControlHelper.requestReresolution();
296291
}
297292

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

321309
private removeCurrentPick() {
322310
if (this.currentPick !== null) {
323-
/* Unref can cause a state change, which can cause a change in the value
324-
* of this.currentPick, so we hold a local reference to make sure that
325-
* does not impact this function. */
326-
const currentPick = this.currentPick;
327-
this.currentPick = null;
328-
currentPick.unref();
329-
currentPick.removeConnectivityStateListener(this.subchannelStateListener);
311+
this.currentPick.removeConnectivityStateListener(this.subchannelStateListener);
330312
this.channelControlHelper.removeChannelzChild(
331-
currentPick.getChannelzRef()
313+
this.currentPick.getChannelzRef()
332314
);
333-
if (this.reportHealthStatus) {
334-
currentPick.removeHealthStateWatcher(
335-
this.pickedSubchannelHealthListener
336-
);
337-
}
315+
this.currentPick.removeHealthStateWatcher(
316+
this.pickedSubchannelHealthListener
317+
);
318+
// Unref last, to avoid triggering listeners
319+
this.currentPick.unref();
320+
this.currentPick = null;
338321
}
339322
}
340323

@@ -374,9 +357,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
374357

375358
private startNextSubchannelConnecting(startIndex: number) {
376359
clearTimeout(this.connectionDelayTimeout);
377-
if (this.triedAllSubchannels) {
378-
return;
379-
}
380360
for (const [index, child] of this.children.entries()) {
381361
if (index >= startIndex) {
382362
const subchannelState = child.subchannel.getConnectivityState();
@@ -389,7 +369,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
389369
}
390370
}
391371
}
392-
this.triedAllSubchannels = true;
393372
this.maybeEnterStickyTransientFailureMode();
394373
}
395374

@@ -418,20 +397,25 @@ export class PickFirstLoadBalancer implements LoadBalancer {
418397
this.connectionDelayTimeout.unref?.();
419398
}
420399

400+
/**
401+
* Declare that the specified subchannel should be used to make requests.
402+
* This functions the same independent of whether subchannel is a member of
403+
* this.children and whether it is equal to this.currentPick.
404+
* Prerequisite: subchannel.getConnectivityState() === READY.
405+
* @param subchannel
406+
*/
421407
private pickSubchannel(subchannel: SubchannelInterface) {
422-
if (this.currentPick && subchannel.realSubchannelEquals(this.currentPick)) {
423-
return;
424-
}
425408
trace('Pick subchannel with address ' + subchannel.getAddress());
426409
this.stickyTransientFailureMode = false;
427-
this.removeCurrentPick();
428-
this.currentPick = subchannel;
410+
/* Ref before removeCurrentPick and resetSubchannelList to avoid the
411+
* refcount dropping to 0 during this process. */
429412
subchannel.ref();
430-
if (this.reportHealthStatus) {
431-
subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener);
432-
}
433413
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
414+
this.removeCurrentPick();
434415
this.resetSubchannelList();
416+
subchannel.addConnectivityStateListener(this.subchannelStateListener);
417+
subchannel.addHealthStateWatcher(this.pickedSubchannelHealthListener);
418+
this.currentPick = subchannel;
435419
clearTimeout(this.connectionDelayTimeout);
436420
this.calculateAndReportNewState();
437421
}
@@ -448,20 +432,11 @@ export class PickFirstLoadBalancer implements LoadBalancer {
448432

449433
private resetSubchannelList() {
450434
for (const child of this.children) {
451-
if (
452-
!(
453-
this.currentPick &&
454-
child.subchannel.realSubchannelEquals(this.currentPick)
455-
)
456-
) {
457-
/* The connectivity state listener is the same whether the subchannel
458-
* is in the list of children or it is the currentPick, so if it is in
459-
* both, removing it here would cause problems. In particular, that
460-
* always happens immediately after the subchannel is picked. */
461-
child.subchannel.removeConnectivityStateListener(
462-
this.subchannelStateListener
463-
);
464-
}
435+
/* Always remoev the connectivity state listener. If the subchannel is
436+
getting picked, it will be re-added then. */
437+
child.subchannel.removeConnectivityStateListener(
438+
this.subchannelStateListener
439+
);
465440
/* Refs are counted independently for the children list and the
466441
* currentPick, so we call unref whether or not the child is the
467442
* currentPick. Channelz child references are also refcounted, so
@@ -473,20 +448,16 @@ export class PickFirstLoadBalancer implements LoadBalancer {
473448
}
474449
this.currentSubchannelIndex = 0;
475450
this.children = [];
476-
this.triedAllSubchannels = false;
477-
this.requestedResolutionSinceLastUpdate = false;
478451
}
479452

480453
private connectToAddressList(addressList: SubchannelAddress[]) {
454+
trace('connectToAddressList([' + addressList.map(address => subchannelAddressToString(address)) + '])');
481455
const newChildrenList = addressList.map(address => ({
482456
subchannel: this.channelControlHelper.createSubchannel(address, {}),
483457
hasReportedTransientFailure: false,
484458
}));
485-
trace('connectToAddressList([' + addressList.map(address => subchannelAddressToString(address)) + '])');
486459
for (const { subchannel } of newChildrenList) {
487460
if (subchannel.getConnectivityState() === ConnectivityState.READY) {
488-
this.channelControlHelper.addChannelzChild(subchannel.getChannelzRef());
489-
subchannel.addConnectivityStateListener(this.subchannelStateListener);
490461
this.pickSubchannel(subchannel);
491462
return;
492463
}

0 commit comments

Comments
 (0)