mirror of https://github.com/grpc/grpc-node.git
grpc-js: Propagate connectivity error information to request errors
This commit is contained in:
parent
065ac2fef6
commit
3a9f4d2aa6
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@grpc/grpc-js",
|
"name": "@grpc/grpc-js",
|
||||||
"version": "1.9.5",
|
"version": "1.9.6",
|
||||||
"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",
|
||||||
|
|
|
@ -153,9 +153,11 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
private subchannelStateListener: ConnectivityStateListener = (
|
private subchannelStateListener: ConnectivityStateListener = (
|
||||||
subchannel,
|
subchannel,
|
||||||
previousState,
|
previousState,
|
||||||
newState
|
newState,
|
||||||
|
keepaliveTime,
|
||||||
|
errorMessage
|
||||||
) => {
|
) => {
|
||||||
this.onSubchannelStateUpdate(subchannel, previousState, newState);
|
this.onSubchannelStateUpdate(subchannel, previousState, newState, errorMessage);
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Timer reference for the timer tracking when to start
|
* Timer reference for the timer tracking when to start
|
||||||
|
@ -172,6 +174,12 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
*/
|
*/
|
||||||
private stickyTransientFailureMode = false;
|
private stickyTransientFailureMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The most recent error reported by any subchannel as it transitioned to
|
||||||
|
* TRANSIENT_FAILURE.
|
||||||
|
*/
|
||||||
|
private lastError: string | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load balancer that attempts to connect to each backend in the address list
|
* Load balancer that attempts to connect to each backend in the address list
|
||||||
* in order, and picks the first one that connects, using it for every
|
* in order, and picks the first one that connects, using it for every
|
||||||
|
@ -200,7 +208,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
if (this.stickyTransientFailureMode) {
|
if (this.stickyTransientFailureMode) {
|
||||||
this.updateState(
|
this.updateState(
|
||||||
ConnectivityState.TRANSIENT_FAILURE,
|
ConnectivityState.TRANSIENT_FAILURE,
|
||||||
new UnavailablePicker()
|
new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
|
||||||
|
@ -241,7 +249,8 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
private onSubchannelStateUpdate(
|
private onSubchannelStateUpdate(
|
||||||
subchannel: SubchannelInterface,
|
subchannel: SubchannelInterface,
|
||||||
previousState: ConnectivityState,
|
previousState: ConnectivityState,
|
||||||
newState: ConnectivityState
|
newState: ConnectivityState,
|
||||||
|
errorMessage?: string
|
||||||
) {
|
) {
|
||||||
if (this.currentPick?.realSubchannelEquals(subchannel)) {
|
if (this.currentPick?.realSubchannelEquals(subchannel)) {
|
||||||
if (newState !== ConnectivityState.READY) {
|
if (newState !== ConnectivityState.READY) {
|
||||||
|
@ -258,6 +267,9 @@ export class PickFirstLoadBalancer implements LoadBalancer {
|
||||||
}
|
}
|
||||||
if (newState === ConnectivityState.TRANSIENT_FAILURE) {
|
if (newState === ConnectivityState.TRANSIENT_FAILURE) {
|
||||||
child.hasReportedTransientFailure = true;
|
child.hasReportedTransientFailure = true;
|
||||||
|
if (errorMessage) {
|
||||||
|
this.lastError = errorMessage;
|
||||||
|
}
|
||||||
this.maybeEnterStickyTransientFailureMode();
|
this.maybeEnterStickyTransientFailureMode();
|
||||||
if (index === this.currentSubchannelIndex) {
|
if (index === this.currentSubchannelIndex) {
|
||||||
this.startNextSubchannelConnecting(index + 1);
|
this.startNextSubchannelConnecting(index + 1);
|
||||||
|
|
|
@ -105,18 +105,24 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
||||||
|
|
||||||
private currentReadyPicker: RoundRobinPicker | null = null;
|
private currentReadyPicker: RoundRobinPicker | null = null;
|
||||||
|
|
||||||
|
private lastError: string | null = null;
|
||||||
|
|
||||||
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
constructor(private readonly channelControlHelper: ChannelControlHelper) {
|
||||||
this.subchannelStateListener = (
|
this.subchannelStateListener = (
|
||||||
subchannel: SubchannelInterface,
|
subchannel: SubchannelInterface,
|
||||||
previousState: ConnectivityState,
|
previousState: ConnectivityState,
|
||||||
newState: ConnectivityState
|
newState: ConnectivityState,
|
||||||
|
keepaliveTime: number,
|
||||||
|
errorMessage?: string
|
||||||
) => {
|
) => {
|
||||||
this.calculateAndUpdateState();
|
this.calculateAndUpdateState();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
newState === ConnectivityState.TRANSIENT_FAILURE ||
|
newState === ConnectivityState.TRANSIENT_FAILURE ||
|
||||||
newState === ConnectivityState.IDLE
|
newState === ConnectivityState.IDLE
|
||||||
) {
|
) {
|
||||||
|
if (errorMessage) {
|
||||||
|
this.lastError = errorMessage;
|
||||||
|
}
|
||||||
this.channelControlHelper.requestReresolution();
|
this.channelControlHelper.requestReresolution();
|
||||||
subchannel.startConnecting();
|
subchannel.startConnecting();
|
||||||
}
|
}
|
||||||
|
@ -157,7 +163,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
|
||||||
) {
|
) {
|
||||||
this.updateState(
|
this.updateState(
|
||||||
ConnectivityState.TRANSIENT_FAILURE,
|
ConnectivityState.TRANSIENT_FAILURE,
|
||||||
new UnavailablePicker()
|
new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
|
||||||
|
|
|
@ -97,16 +97,13 @@ export interface Picker {
|
||||||
*/
|
*/
|
||||||
export class UnavailablePicker implements Picker {
|
export class UnavailablePicker implements Picker {
|
||||||
private status: StatusObject;
|
private status: StatusObject;
|
||||||
constructor(status?: StatusObject) {
|
constructor(status?: Partial<StatusObject>) {
|
||||||
if (status !== undefined) {
|
this.status = {
|
||||||
this.status = status;
|
code: Status.UNAVAILABLE,
|
||||||
} else {
|
details: 'No connection established',
|
||||||
this.status = {
|
metadata: new Metadata(),
|
||||||
code: Status.UNAVAILABLE,
|
...status,
|
||||||
details: 'No connection established',
|
};
|
||||||
metadata: new Metadata(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pick(pickArgs: PickArgs): TransientFailurePickResult {
|
pick(pickArgs: PickArgs): TransientFailurePickResult {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -23,7 +23,8 @@ export type ConnectivityStateListener = (
|
||||||
subchannel: SubchannelInterface,
|
subchannel: SubchannelInterface,
|
||||||
previousState: ConnectivityState,
|
previousState: ConnectivityState,
|
||||||
newState: ConnectivityState,
|
newState: ConnectivityState,
|
||||||
keepaliveTime: number
|
keepaliveTime: number,
|
||||||
|
errorMessage?: string
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -250,7 +250,8 @@ export class Subchannel {
|
||||||
error => {
|
error => {
|
||||||
this.transitionToState(
|
this.transitionToState(
|
||||||
[ConnectivityState.CONNECTING],
|
[ConnectivityState.CONNECTING],
|
||||||
ConnectivityState.TRANSIENT_FAILURE
|
ConnectivityState.TRANSIENT_FAILURE,
|
||||||
|
`${error}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -265,7 +266,8 @@ export class Subchannel {
|
||||||
*/
|
*/
|
||||||
private transitionToState(
|
private transitionToState(
|
||||||
oldStates: ConnectivityState[],
|
oldStates: ConnectivityState[],
|
||||||
newState: ConnectivityState
|
newState: ConnectivityState,
|
||||||
|
errorMessage?: string
|
||||||
): boolean {
|
): boolean {
|
||||||
if (oldStates.indexOf(this.connectivityState) === -1) {
|
if (oldStates.indexOf(this.connectivityState) === -1) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -318,7 +320,7 @@ export class Subchannel {
|
||||||
throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
|
throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
|
||||||
}
|
}
|
||||||
for (const listener of this.stateListeners) {
|
for (const listener of this.stateListeners) {
|
||||||
listener(this, previousState, newState, this.keepaliveTime);
|
listener(this, previousState, newState, this.keepaliveTime, errorMessage);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -741,6 +741,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
|
||||||
connectionOptions
|
connectionOptions
|
||||||
);
|
);
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
let errorMessage = 'Failed to connect';
|
||||||
session.unref();
|
session.unref();
|
||||||
session.once('connect', () => {
|
session.once('connect', () => {
|
||||||
session.removeAllListeners();
|
session.removeAllListeners();
|
||||||
|
@ -749,10 +750,14 @@ export class Http2SubchannelConnector implements SubchannelConnector {
|
||||||
});
|
});
|
||||||
session.once('close', () => {
|
session.once('close', () => {
|
||||||
this.session = null;
|
this.session = null;
|
||||||
reject();
|
// Leave time for error event to happen before rejecting
|
||||||
|
setImmediate(() => {
|
||||||
|
reject(`${errorMessage} (${new Date().toISOString()})`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
session.once('error', error => {
|
session.once('error', error => {
|
||||||
this.trace('connection failed with error ' + (error as Error).message);
|
errorMessage = (error as Error).message;
|
||||||
|
this.trace('connection failed with error ' + errorMessage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue