Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/grpc-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grpc/grpc-js",
"version": "1.9.5",
"version": "1.9.6",
"description": "gRPC Library for Node - pure JS implementation",
"homepage": "https://grpc.io/",
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
Expand Down
20 changes: 16 additions & 4 deletions packages/grpc-js/src/load-balancer-pick-first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,11 @@ export class PickFirstLoadBalancer implements LoadBalancer {
private subchannelStateListener: ConnectivityStateListener = (
subchannel,
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
Expand All @@ -172,6 +174,12 @@ export class PickFirstLoadBalancer implements LoadBalancer {
*/
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
* in order, and picks the first one that connects, using it for every
Expand Down Expand Up @@ -200,7 +208,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
if (this.stickyTransientFailureMode) {
this.updateState(
ConnectivityState.TRANSIENT_FAILURE,
new UnavailablePicker()
new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`})
);
} else {
this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
Expand Down Expand Up @@ -241,7 +249,8 @@ export class PickFirstLoadBalancer implements LoadBalancer {
private onSubchannelStateUpdate(
subchannel: SubchannelInterface,
previousState: ConnectivityState,
newState: ConnectivityState
newState: ConnectivityState,
errorMessage?: string
) {
if (this.currentPick?.realSubchannelEquals(subchannel)) {
if (newState !== ConnectivityState.READY) {
Expand All @@ -258,6 +267,9 @@ export class PickFirstLoadBalancer implements LoadBalancer {
}
if (newState === ConnectivityState.TRANSIENT_FAILURE) {
child.hasReportedTransientFailure = true;
if (errorMessage) {
this.lastError = errorMessage;
}
this.maybeEnterStickyTransientFailureMode();
if (index === this.currentSubchannelIndex) {
this.startNextSubchannelConnecting(index + 1);
Expand Down
12 changes: 9 additions & 3 deletions packages/grpc-js/src/load-balancer-round-robin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,24 @@ export class RoundRobinLoadBalancer implements LoadBalancer {

private currentReadyPicker: RoundRobinPicker | null = null;

private lastError: string | null = null;

constructor(private readonly channelControlHelper: ChannelControlHelper) {
this.subchannelStateListener = (
subchannel: SubchannelInterface,
previousState: ConnectivityState,
newState: ConnectivityState
newState: ConnectivityState,
keepaliveTime: number,
errorMessage?: string
) => {
this.calculateAndUpdateState();

if (
newState === ConnectivityState.TRANSIENT_FAILURE ||
newState === ConnectivityState.IDLE
) {
if (errorMessage) {
this.lastError = errorMessage;
}
this.channelControlHelper.requestReresolution();
subchannel.startConnecting();
}
Expand Down Expand Up @@ -157,7 +163,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
) {
this.updateState(
ConnectivityState.TRANSIENT_FAILURE,
new UnavailablePicker()
new UnavailablePicker({details: `No connection established. Last error: ${this.lastError}`})
);
} else {
this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
Expand Down
17 changes: 7 additions & 10 deletions packages/grpc-js/src/picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,13 @@ export interface Picker {
*/
export class UnavailablePicker implements Picker {
private status: StatusObject;
constructor(status?: StatusObject) {
if (status !== undefined) {
this.status = status;
} else {
this.status = {
code: Status.UNAVAILABLE,
details: 'No connection established',
metadata: new Metadata(),
};
}
constructor(status?: Partial<StatusObject>) {
this.status = {
code: Status.UNAVAILABLE,
details: 'No connection established',
metadata: new Metadata(),
...status,
};
}
pick(pickArgs: PickArgs): TransientFailurePickResult {
return {
Expand Down
3 changes: 2 additions & 1 deletion packages/grpc-js/src/subchannel-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export type ConnectivityStateListener = (
subchannel: SubchannelInterface,
previousState: ConnectivityState,
newState: ConnectivityState,
keepaliveTime: number
keepaliveTime: number,
errorMessage?: string
) => void;

/**
Expand Down
8 changes: 5 additions & 3 deletions packages/grpc-js/src/subchannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ export class Subchannel {
error => {
this.transitionToState(
[ConnectivityState.CONNECTING],
ConnectivityState.TRANSIENT_FAILURE
ConnectivityState.TRANSIENT_FAILURE,
`${error}`
);
}
);
Expand All @@ -265,7 +266,8 @@ export class Subchannel {
*/
private transitionToState(
oldStates: ConnectivityState[],
newState: ConnectivityState
newState: ConnectivityState,
errorMessage?: string
): boolean {
if (oldStates.indexOf(this.connectivityState) === -1) {
return false;
Expand Down Expand Up @@ -318,7 +320,7 @@ export class Subchannel {
throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
}
for (const listener of this.stateListeners) {
listener(this, previousState, newState, this.keepaliveTime);
listener(this, previousState, newState, this.keepaliveTime, errorMessage);
}
return true;
}
Expand Down
9 changes: 7 additions & 2 deletions packages/grpc-js/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ export class Http2SubchannelConnector implements SubchannelConnector {
connectionOptions
);
this.session = session;
let errorMessage = 'Failed to connect';
session.unref();
session.once('connect', () => {
session.removeAllListeners();
Expand All @@ -749,10 +750,14 @@ export class Http2SubchannelConnector implements SubchannelConnector {
});
session.once('close', () => {
this.session = null;
reject();
// Leave time for error event to happen before rejecting
setImmediate(() => {
reject(`${errorMessage} (${new Date().toISOString()})`);
});
});
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);
});
});
}
Expand Down