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",
"version": "0.6.8",
"version": "0.6.9",
"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

@ -152,6 +152,12 @@ export class PickFirstLoadBalancer implements LoadBalancer {
this.pickSubchannel(subchannel);
return;
} 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.triedAllSubchannels) {
let newLBState: ConnectivityState;
@ -190,18 +196,25 @@ export class PickFirstLoadBalancer implements LoadBalancer {
this.pickedSubchannelStateListener
);
if (this.subchannels.length > 0) {
let newLBState: ConnectivityState;
if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
newLBState = ConnectivityState.CONNECTING;
} else if (this.subchannelStateCounts[ConnectivityState.TRANSIENT_FAILURE] > 0) {
newLBState = ConnectivityState.TRANSIENT_FAILURE;
if (this.triedAllSubchannels) {
let newLBState: ConnectivityState;
if (this.subchannelStateCounts[ConnectivityState.CONNECTING] > 0) {
newLBState = ConnectivityState.CONNECTING;
} 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 {
newLBState = ConnectivityState.IDLE;
}
if (newLBState === ConnectivityState.TRANSIENT_FAILURE) {
this.updateState(newLBState, new UnavailablePicker());
} else {
this.updateState(newLBState, new QueuePicker(this));
this.updateState(
ConnectivityState.CONNECTING,
new QueuePicker(this)
);
}
} else {
/* 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 innerBalancerPicker: Picker = new UnavailablePicker();
/**
* The most recent reported state of the pendingReplacementLoadBalancer.
* 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;
/**
* 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
* resolution internally.
@ -245,12 +253,22 @@ export class ResolvingLoadBalancer implements LoadBalancer {
},
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
this.innerBalancerState = connectivityState;
if (connectivityState === ConnectivityState.IDLE) {
picker = new QueuePicker(this);
}
this.innerBalancerPicker = picker;
if (
connectivityState !== ConnectivityState.READY &&
this.pendingReplacementLoadBalancer !== null
) {
this.switchOverReplacementBalancer();
} else {
if (connectivityState === ConnectivityState.IDLE) {
if (this.innerLoadBalancer) {
this.innerLoadBalancer.destroy();
this.innerLoadBalancer = null;
}
}
this.updateState(connectivityState, picker);
}
},
@ -260,8 +278,10 @@ export class ResolvingLoadBalancer implements LoadBalancer {
* making resolve requests, so we shouldn't make another one here.
* In that case, the backoff timer callback will call
* updateResolution */
if (!this.backoffTimeout.isRunning()) {
this.innerResolver.updateResolution();
if (this.backoffTimeout.isRunning()) {
this.continueResolving = true;
} else {
this.updateResolution();
}
}
},
@ -278,32 +298,54 @@ export class ResolvingLoadBalancer implements LoadBalancer {
);
},
updateState: (connectivityState: ConnectivityState, picker: Picker) => {
if (connectivityState === ConnectivityState.IDLE) {
picker = new QueuePicker(this);
}
this.replacementBalancerState = connectivityState;
this.replacementBalancerPicker = picker;
if (connectivityState === ConnectivityState.READY) {
this.switchOverReplacementBalancer();
} else if (connectivityState === ConnectivityState.IDLE) {
if (this.pendingReplacementLoadBalancer) {
this.pendingReplacementLoadBalancer.destroy();
this.pendingReplacementLoadBalancer = null;
}
}
},
requestReresolution: () => {
if (!this.backoffTimeout.isRunning()) {
/* If the backoffTimeout is running, we're still backing off from
* making resolve requests, so we shouldn't make another one here.
* In that case, the backoff timer callback will call
* updateResolution */
this.innerResolver.updateResolution();
/* If the backoffTimeout is running, we're still backing off from
* making resolve requests, so we shouldn't make another one here.
* In that case, the backoff timer callback will call
* updateResolution */
if (this.backoffTimeout.isRunning()) {
this.continueResolving = true;
} else {
this.updateResolution();
}
},
};
this.backoffTimeout = new BackoffTimeout(() => {
if (this.innerLoadBalancer === null) {
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
if (this.continueResolving) {
this.updateResolution();
this.continueResolving = false;
} 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) {
trace(this.target + ' ' + ConnectivityState[this.currentState] + ' -> ' + ConnectivityState[connectivitystate]);
this.currentState = connectivitystate;
@ -323,6 +365,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
);
this.pendingReplacementLoadBalancer = null;
this.innerBalancerState = this.replacementBalancerState;
this.innerBalancerPicker = this.replacementBalancerPicker;
this.updateState(
this.replacementBalancerState,
this.replacementBalancerPicker
@ -330,7 +373,7 @@ export class ResolvingLoadBalancer implements LoadBalancer {
}
private handleResolutionFailure(error: StatusObject) {
if (this.innerLoadBalancer === null) {
if (this.innerLoadBalancer === null || this.innerBalancerState === ConnectivityState.IDLE) {
this.updateState(
ConnectivityState.TRANSIENT_FAILURE,
new UnavailablePicker(error)
@ -344,7 +387,11 @@ export class ResolvingLoadBalancer implements LoadBalancer {
this.innerLoadBalancer.exitIdle();
}
if (this.currentState === ConnectivityState.IDLE) {
this.innerResolver.updateResolution();
if (this.backoffTimeout.isRunning()) {
this.continueResolving = true;
} else {
this.updateResolution();
}
this.updateState(
ConnectivityState.CONNECTING,
new QueuePicker(this)