grpc-js: Some fixes for how idleness and reresolution are handled

This commit is contained in:
murgatroid99 2019-10-11 12:49:39 -07:00
parent 607def892e
commit d362ccb3f6
3 changed files with 85 additions and 25 deletions

View File

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

View File

@ -152,6 +152,12 @@ export class PickFirstLoadBalancer implements LoadBalancer {
this.pickSubchannel(subchannel); this.pickSubchannel(subchannel);
return; return;
} else { } else {
if (this.triedAllSubchannels && this.subchannelStateCounts[ConnectivityState.IDLE] === this.subchannels.length) {
/* If all of the subchannels are IDLE we should go back to a
* basic IDLE state where there is no subchannel list to avoid
* holding unused resources */
this.resetSubchannelList();
}
if (this.currentPick === null) { if (this.currentPick === null) {
if (this.triedAllSubchannels) { if (this.triedAllSubchannels) {
let newLBState: ConnectivityState; let newLBState: ConnectivityState;
@ -190,18 +196,25 @@ export class PickFirstLoadBalancer implements LoadBalancer {
this.pickedSubchannelStateListener this.pickedSubchannelStateListener
); );
if (this.subchannels.length > 0) { if (this.subchannels.length > 0) {
let newLBState: ConnectivityState; if (this.triedAllSubchannels) {
if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) { let newLBState: ConnectivityState;
newLBState = ConnectivityState.CONNECTING; if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
} else if (this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0) { newLBState = ConnectivityState.CONNECTING;
newLBState = ConnectivityState.TRANSIENT_FAILURE; } else if (this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0) {
newLBState = ConnectivityState.TRANSIENT_FAILURE;
} else {
newLBState = ConnectivityState.IDLE;
}
if (newLBState === ConnectivityState.TRANSIENT_FAILURE) {
this.updateState(newLBState, new UnavailablePicker());
} else {
this.updateState(newLBState, new QueuePicker(this));
}
} else { } else {
newLBState = ConnectivityState.IDLE; this.updateState(
} ConnectivityState.CONNECTING,
if (newLBState === ConnectivityState.TRANSIENT_FAILURE) { new QueuePicker(this)
this.updateState(newLBState, new UnavailablePicker()); );
} else {
this.updateState(newLBState, new QueuePicker(this));
} }
} else { } else {
/* We don't need to backoff here because this only happens if a /* We don't need to backoff here because this only happens if a

View File

@ -77,6 +77,8 @@ export class ResolvingLoadBalancer implements LoadBalancer {
*/ */
private innerBalancerState: ConnectivityState = ConnectivityState.IDLE; private innerBalancerState: ConnectivityState = ConnectivityState.IDLE;
private innerBalancerPicker: Picker = new UnavailablePicker();
/** /**
* The most recent reported state of the pendingReplacementLoadBalancer. * The most recent reported state of the pendingReplacementLoadBalancer.
* Starts at IDLE for type simplicity. This should get updated as soon as the * Starts at IDLE for type simplicity. This should get updated as soon as the
@ -104,6 +106,12 @@ export class ResolvingLoadBalancer implements LoadBalancer {
*/ */
private readonly backoffTimeout: BackoffTimeout; private readonly backoffTimeout: BackoffTimeout;
/**
* Indicates whether we should attempt to resolve again after the backoff
* timer runs out.
*/
private continueResolving = false;
/** /**
* Wrapper class that behaves like a `LoadBalancer` and also handles name * Wrapper class that behaves like a `LoadBalancer` and also handles name
* resolution internally. * resolution internally.
@ -245,12 +253,22 @@ export class ResolvingLoadBalancer implements LoadBalancer {
}, },
updateState: (connectivityState: ConnectivityState, picker: Picker) => { updateState: (connectivityState: ConnectivityState, picker: Picker) => {
this.innerBalancerState = connectivityState; this.innerBalancerState = connectivityState;
if (connectivityState === ConnectivityState.IDLE) {
picker = new QueuePicker(this);
}
this.innerBalancerPicker = picker;
if ( if (
connectivityState !== ConnectivityState.READY && connectivityState !== ConnectivityState.READY &&
this.pendingReplacementLoadBalancer !== null this.pendingReplacementLoadBalancer !== null
) { ) {
this.switchOverReplacementBalancer(); this.switchOverReplacementBalancer();
} else { } else {
if (connectivityState === ConnectivityState.IDLE) {
if (this.innerLoadBalancer) {
this.innerLoadBalancer.destroy();
this.innerLoadBalancer = null;
}
}
this.updateState(connectivityState, picker); this.updateState(connectivityState, picker);
} }
}, },
@ -260,8 +278,10 @@ export class ResolvingLoadBalancer implements LoadBalancer {
* making resolve requests, so we shouldn't make another one here. * making resolve requests, so we shouldn't make another one here.
* In that case, the backoff timer callback will call * In that case, the backoff timer callback will call
* updateResolution */ * updateResolution */
if (!this.backoffTimeout.isRunning()) { if (this.backoffTimeout.isRunning()) {
this.innerResolver.updateResolution(); this.continueResolving = true;
} else {
this.updateResolution();
} }
} }
}, },
@ -278,32 +298,54 @@ export class ResolvingLoadBalancer implements LoadBalancer {
); );
}, },
updateState: (connectivityState: ConnectivityState, picker: Picker) => { updateState: (connectivityState: ConnectivityState, picker: Picker) => {
if (connectivityState === ConnectivityState.IDLE) {
picker = new QueuePicker(this);
}
this.replacementBalancerState = connectivityState; this.replacementBalancerState = connectivityState;
this.replacementBalancerPicker = picker; this.replacementBalancerPicker = picker;
if (connectivityState === ConnectivityState.READY) { if (connectivityState === ConnectivityState.READY) {
this.switchOverReplacementBalancer(); this.switchOverReplacementBalancer();
} else if (connectivityState === ConnectivityState.IDLE) {
if (this.pendingReplacementLoadBalancer) {
this.pendingReplacementLoadBalancer.destroy();
this.pendingReplacementLoadBalancer = null;
}
} }
}, },
requestReresolution: () => { requestReresolution: () => {
if (!this.backoffTimeout.isRunning()) { /* If the backoffTimeout is running, we're still backing off from
/* If the backoffTimeout is running, we're still backing off from * making resolve requests, so we shouldn't make another one here.
* making resolve requests, so we shouldn't make another one here. * In that case, the backoff timer callback will call
* In that case, the backoff timer callback will call * updateResolution */
* updateResolution */ if (this.backoffTimeout.isRunning()) {
this.innerResolver.updateResolution(); this.continueResolving = true;
} else {
this.updateResolution();
} }
}, },
}; };
this.backoffTimeout = new BackoffTimeout(() => { this.backoffTimeout = new BackoffTimeout(() => {
if (this.innerLoadBalancer === null) { if (this.continueResolving) {
this.updateState(ConnectivityState.IDLE, new QueuePicker(this)); this.updateResolution();
this.continueResolving = false;
} else { } else {
this.innerResolver.updateResolution(); if (this.innerLoadBalancer === null) {
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
} else {
this.updateState(this.innerBalancerState, this.innerBalancerPicker);
}
} }
}); });
} }
private updateResolution() {
this.innerResolver.updateResolution();
if (this.innerLoadBalancer === null || this.innerBalancerState === ConnectivityState.IDLE) {
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
}
}
private updateState(connectivitystate: ConnectivityState, picker: Picker) { private updateState(connectivitystate: ConnectivityState, picker: Picker) {
trace(this.target + ' ' + ConnectivityState[this.currentState] + ' -> ' + ConnectivityState[connectivitystate]); trace(this.target + ' ' + ConnectivityState[this.currentState] + ' -> ' + ConnectivityState[connectivitystate]);
this.currentState = connectivitystate; this.currentState = connectivitystate;
@ -323,6 +365,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
); );
this.pendingReplacementLoadBalancer = null; this.pendingReplacementLoadBalancer = null;
this.innerBalancerState = this.replacementBalancerState; this.innerBalancerState = this.replacementBalancerState;
this.innerBalancerPicker = this.replacementBalancerPicker;
this.updateState( this.updateState(
this.replacementBalancerState, this.replacementBalancerState,
this.replacementBalancerPicker this.replacementBalancerPicker
@ -330,7 +373,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
} }
private handleResolutionFailure(error: StatusObject) { private handleResolutionFailure(error: StatusObject) {
if (this.innerLoadBalancer === null) { if (this.innerLoadBalancer === null || this.innerBalancerState === ConnectivityState.IDLE) {
this.updateState( this.updateState(
ConnectivityState.TRANSIENT_FAILURE, ConnectivityState.TRANSIENT_FAILURE,
new UnavailablePicker(error) new UnavailablePicker(error)
@ -344,7 +387,11 @@ export class ResolvingLoadBalancer implements LoadBalancer {
this.innerLoadBalancer.exitIdle(); this.innerLoadBalancer.exitIdle();
} }
if (this.currentState === ConnectivityState.IDLE) { if (this.currentState === ConnectivityState.IDLE) {
this.innerResolver.updateResolution(); if (this.backoffTimeout.isRunning()) {
this.continueResolving = true;
} else {
this.updateResolution();
}
this.updateState( this.updateState(
ConnectivityState.CONNECTING, ConnectivityState.CONNECTING,
new QueuePicker(this) new QueuePicker(this)