mirror of https://github.com/grpc/grpc-node.git
Merge pull request #2692 from murgatroid99/grpc-js_deadline_info
grpc-js: Add more info to deadline exceeded errors
This commit is contained in:
commit
cc44d785c9
|
@ -171,3 +171,7 @@ export interface Call {
|
||||||
getCallNumber(): number;
|
getCallNumber(): number;
|
||||||
setCredentials(credentials: CallCredentials): void;
|
setCredentials(credentials: CallCredentials): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeadlineInfoProvider {
|
||||||
|
getDeadlineInfo(): string[];
|
||||||
|
}
|
||||||
|
|
|
@ -93,3 +93,14 @@ export function deadlineToString(deadline: Deadline): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the difference between two dates as a number of seconds and format
|
||||||
|
* it as a string.
|
||||||
|
* @param startDate
|
||||||
|
* @param endDate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function formatDateDifference(startDate: Date, endDate: Date): string {
|
||||||
|
return ((endDate.getTime() - startDate.getTime()) / 1000).toFixed(3) + 's';
|
||||||
|
}
|
||||||
|
|
|
@ -684,7 +684,7 @@ export class InternalChannel {
|
||||||
host: string,
|
host: string,
|
||||||
credentials: CallCredentials,
|
credentials: CallCredentials,
|
||||||
deadline: Deadline
|
deadline: Deadline
|
||||||
): Call {
|
): LoadBalancingCall | RetryingCall {
|
||||||
// Create a RetryingCall if retries are enabled
|
// Create a RetryingCall if retries are enabled
|
||||||
if (this.options['grpc.enable_retries'] === 0) {
|
if (this.options['grpc.enable_retries'] === 0) {
|
||||||
return this.createLoadBalancingCall(
|
return this.createLoadBalancingCall(
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import { CallCredentials } from './call-credentials';
|
import { CallCredentials } from './call-credentials';
|
||||||
import {
|
import {
|
||||||
Call,
|
Call,
|
||||||
|
DeadlineInfoProvider,
|
||||||
InterceptingListener,
|
InterceptingListener,
|
||||||
MessageContext,
|
MessageContext,
|
||||||
StatusObject,
|
StatusObject,
|
||||||
|
@ -25,7 +26,7 @@ import {
|
||||||
import { SubchannelCall } from './subchannel-call';
|
import { SubchannelCall } from './subchannel-call';
|
||||||
import { ConnectivityState } from './connectivity-state';
|
import { ConnectivityState } from './connectivity-state';
|
||||||
import { LogVerbosity, Status } from './constants';
|
import { LogVerbosity, Status } from './constants';
|
||||||
import { Deadline, getDeadlineTimeoutString } from './deadline';
|
import { Deadline, formatDateDifference, getDeadlineTimeoutString } from './deadline';
|
||||||
import { InternalChannel } from './internal-channel';
|
import { InternalChannel } from './internal-channel';
|
||||||
import { Metadata } from './metadata';
|
import { Metadata } from './metadata';
|
||||||
import { PickResultType } from './picker';
|
import { PickResultType } from './picker';
|
||||||
|
@ -48,7 +49,7 @@ export interface LoadBalancingCallInterceptingListener
|
||||||
onReceiveStatus(status: StatusObjectWithProgress): void;
|
onReceiveStatus(status: StatusObjectWithProgress): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LoadBalancingCall implements Call {
|
export class LoadBalancingCall implements Call, DeadlineInfoProvider {
|
||||||
private child: SubchannelCall | null = null;
|
private child: SubchannelCall | null = null;
|
||||||
private readPending = false;
|
private readPending = false;
|
||||||
private pendingMessage: { context: MessageContext; message: Buffer } | null =
|
private pendingMessage: { context: MessageContext; message: Buffer } | null =
|
||||||
|
@ -59,6 +60,8 @@ export class LoadBalancingCall implements Call {
|
||||||
private metadata: Metadata | null = null;
|
private metadata: Metadata | null = null;
|
||||||
private listener: InterceptingListener | null = null;
|
private listener: InterceptingListener | null = null;
|
||||||
private onCallEnded: ((statusCode: Status) => void) | null = null;
|
private onCallEnded: ((statusCode: Status) => void) | null = null;
|
||||||
|
private startTime: Date;
|
||||||
|
private childStartTime: Date | null = null;
|
||||||
constructor(
|
constructor(
|
||||||
private readonly channel: InternalChannel,
|
private readonly channel: InternalChannel,
|
||||||
private readonly callConfig: CallConfig,
|
private readonly callConfig: CallConfig,
|
||||||
|
@ -80,6 +83,26 @@ export class LoadBalancingCall implements Call {
|
||||||
/* Currently, call credentials are only allowed on HTTPS connections, so we
|
/* Currently, call credentials are only allowed on HTTPS connections, so we
|
||||||
* can assume that the scheme is "https" */
|
* can assume that the scheme is "https" */
|
||||||
this.serviceUrl = `https://${hostname}/${serviceName}`;
|
this.serviceUrl = `https://${hostname}/${serviceName}`;
|
||||||
|
this.startTime = new Date();
|
||||||
|
}
|
||||||
|
getDeadlineInfo(): string[] {
|
||||||
|
const deadlineInfo: string[] = [];
|
||||||
|
if (this.childStartTime) {
|
||||||
|
if (this.childStartTime > this.startTime) {
|
||||||
|
if (this.metadata?.getOptions().waitForReady) {
|
||||||
|
deadlineInfo.push('wait_for_ready');
|
||||||
|
}
|
||||||
|
deadlineInfo.push(`LB pick: ${formatDateDifference(this.startTime, this.childStartTime)}`);
|
||||||
|
}
|
||||||
|
deadlineInfo.push(...this.child!.getDeadlineInfo());
|
||||||
|
return deadlineInfo;
|
||||||
|
} else {
|
||||||
|
if (this.metadata?.getOptions().waitForReady) {
|
||||||
|
deadlineInfo.push('wait_for_ready');
|
||||||
|
}
|
||||||
|
deadlineInfo.push('Waiting for LB pick');
|
||||||
|
}
|
||||||
|
return deadlineInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private trace(text: string): void {
|
private trace(text: string): void {
|
||||||
|
@ -98,7 +121,8 @@ export class LoadBalancingCall implements Call {
|
||||||
status.code +
|
status.code +
|
||||||
' details="' +
|
' details="' +
|
||||||
status.details +
|
status.details +
|
||||||
'"'
|
'" start time=' +
|
||||||
|
this.startTime.toISOString()
|
||||||
);
|
);
|
||||||
const finalStatus = { ...status, progress };
|
const finalStatus = { ...status, progress };
|
||||||
this.listener?.onReceiveStatus(finalStatus);
|
this.listener?.onReceiveStatus(finalStatus);
|
||||||
|
@ -209,6 +233,7 @@ export class LoadBalancingCall implements Call {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
this.childStartTime = new Date();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.trace(
|
this.trace(
|
||||||
'Failed to start call on picked subchannel ' +
|
'Failed to start call on picked subchannel ' +
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { CallCredentials } from './call-credentials';
|
||||||
import {
|
import {
|
||||||
Call,
|
Call,
|
||||||
CallStreamOptions,
|
CallStreamOptions,
|
||||||
|
DeadlineInfoProvider,
|
||||||
InterceptingListener,
|
InterceptingListener,
|
||||||
MessageContext,
|
MessageContext,
|
||||||
StatusObject,
|
StatusObject,
|
||||||
|
@ -27,6 +28,7 @@ import { LogVerbosity, Propagate, Status } from './constants';
|
||||||
import {
|
import {
|
||||||
Deadline,
|
Deadline,
|
||||||
deadlineToString,
|
deadlineToString,
|
||||||
|
formatDateDifference,
|
||||||
getRelativeTimeout,
|
getRelativeTimeout,
|
||||||
minDeadline,
|
minDeadline,
|
||||||
} from './deadline';
|
} from './deadline';
|
||||||
|
@ -39,7 +41,7 @@ import { restrictControlPlaneStatusCode } from './control-plane-status';
|
||||||
const TRACER_NAME = 'resolving_call';
|
const TRACER_NAME = 'resolving_call';
|
||||||
|
|
||||||
export class ResolvingCall implements Call {
|
export class ResolvingCall implements Call {
|
||||||
private child: Call | null = null;
|
private child: (Call & DeadlineInfoProvider) | null = null;
|
||||||
private readPending = false;
|
private readPending = false;
|
||||||
private pendingMessage: { context: MessageContext; message: Buffer } | null =
|
private pendingMessage: { context: MessageContext; message: Buffer } | null =
|
||||||
null;
|
null;
|
||||||
|
@ -56,6 +58,10 @@ export class ResolvingCall implements Call {
|
||||||
private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0);
|
private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0);
|
||||||
private filterStack: FilterStack | null = null;
|
private filterStack: FilterStack | null = null;
|
||||||
|
|
||||||
|
private deadlineStartTime: Date | null = null;
|
||||||
|
private configReceivedTime: Date | null = null;
|
||||||
|
private childStartTime: Date | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly channel: InternalChannel,
|
private readonly channel: InternalChannel,
|
||||||
private readonly method: string,
|
private readonly method: string,
|
||||||
|
@ -97,12 +103,37 @@ export class ResolvingCall implements Call {
|
||||||
|
|
||||||
private runDeadlineTimer() {
|
private runDeadlineTimer() {
|
||||||
clearTimeout(this.deadlineTimer);
|
clearTimeout(this.deadlineTimer);
|
||||||
|
this.deadlineStartTime = new Date();
|
||||||
this.trace('Deadline: ' + deadlineToString(this.deadline));
|
this.trace('Deadline: ' + deadlineToString(this.deadline));
|
||||||
const timeout = getRelativeTimeout(this.deadline);
|
const timeout = getRelativeTimeout(this.deadline);
|
||||||
if (timeout !== Infinity) {
|
if (timeout !== Infinity) {
|
||||||
this.trace('Deadline will be reached in ' + timeout + 'ms');
|
this.trace('Deadline will be reached in ' + timeout + 'ms');
|
||||||
const handleDeadline = () => {
|
const handleDeadline = () => {
|
||||||
this.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
|
if (!this.deadlineStartTime) {
|
||||||
|
this.cancelWithStatus(Status.DEADLINE_EXCEEDED, 'Deadline exceeded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deadlineInfo: string[] = [];
|
||||||
|
const deadlineEndTime = new Date();
|
||||||
|
deadlineInfo.push(`Deadline exceeded after ${formatDateDifference(this.deadlineStartTime, deadlineEndTime)}`);
|
||||||
|
if (this.configReceivedTime) {
|
||||||
|
if (this.configReceivedTime > this.deadlineStartTime) {
|
||||||
|
deadlineInfo.push(`name resolution: ${formatDateDifference(this.deadlineStartTime, this.configReceivedTime)}`);
|
||||||
|
}
|
||||||
|
if (this.childStartTime) {
|
||||||
|
if (this.childStartTime > this.configReceivedTime) {
|
||||||
|
deadlineInfo.push(`metadata filters: ${formatDateDifference(this.configReceivedTime, this.childStartTime)}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deadlineInfo.push('waiting for metadata filters');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deadlineInfo.push('waiting for name resolution');
|
||||||
|
}
|
||||||
|
if (this.child) {
|
||||||
|
deadlineInfo.push(...this.child.getDeadlineInfo());
|
||||||
|
}
|
||||||
|
this.cancelWithStatus(Status.DEADLINE_EXCEEDED, deadlineInfo.join(','));
|
||||||
};
|
};
|
||||||
if (timeout <= 0) {
|
if (timeout <= 0) {
|
||||||
process.nextTick(handleDeadline);
|
process.nextTick(handleDeadline);
|
||||||
|
@ -176,6 +207,7 @@ export class ResolvingCall implements Call {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// configResult.type === 'SUCCESS'
|
// configResult.type === 'SUCCESS'
|
||||||
|
this.configReceivedTime = new Date();
|
||||||
const config = configResult.config;
|
const config = configResult.config;
|
||||||
if (config.status !== Status.OK) {
|
if (config.status !== Status.OK) {
|
||||||
const { code, details } = restrictControlPlaneStatusCode(
|
const { code, details } = restrictControlPlaneStatusCode(
|
||||||
|
@ -215,6 +247,7 @@ export class ResolvingCall implements Call {
|
||||||
this.deadline
|
this.deadline
|
||||||
);
|
);
|
||||||
this.trace('Created child [' + this.child.getCallNumber() + ']');
|
this.trace('Created child [' + this.child.getCallNumber() + ']');
|
||||||
|
this.childStartTime = new Date();
|
||||||
this.child.start(filteredMetadata, {
|
this.child.start(filteredMetadata, {
|
||||||
onReceiveMetadata: metadata => {
|
onReceiveMetadata: metadata => {
|
||||||
this.trace('Received metadata');
|
this.trace('Received metadata');
|
||||||
|
|
|
@ -17,12 +17,13 @@
|
||||||
|
|
||||||
import { CallCredentials } from './call-credentials';
|
import { CallCredentials } from './call-credentials';
|
||||||
import { LogVerbosity, Status } from './constants';
|
import { LogVerbosity, Status } from './constants';
|
||||||
import { Deadline } from './deadline';
|
import { Deadline, formatDateDifference } from './deadline';
|
||||||
import { Metadata } from './metadata';
|
import { Metadata } from './metadata';
|
||||||
import { CallConfig } from './resolver';
|
import { CallConfig } from './resolver';
|
||||||
import * as logging from './logging';
|
import * as logging from './logging';
|
||||||
import {
|
import {
|
||||||
Call,
|
Call,
|
||||||
|
DeadlineInfoProvider,
|
||||||
InterceptingListener,
|
InterceptingListener,
|
||||||
MessageContext,
|
MessageContext,
|
||||||
StatusObject,
|
StatusObject,
|
||||||
|
@ -121,6 +122,7 @@ interface UnderlyingCall {
|
||||||
state: UnderlyingCallState;
|
state: UnderlyingCallState;
|
||||||
call: LoadBalancingCall;
|
call: LoadBalancingCall;
|
||||||
nextMessageToSend: number;
|
nextMessageToSend: number;
|
||||||
|
startTime: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,7 +172,7 @@ interface WriteBufferEntry {
|
||||||
|
|
||||||
const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts';
|
const PREVIONS_RPC_ATTEMPTS_METADATA_KEY = 'grpc-previous-rpc-attempts';
|
||||||
|
|
||||||
export class RetryingCall implements Call {
|
export class RetryingCall implements Call, DeadlineInfoProvider {
|
||||||
private state: RetryingCallState;
|
private state: RetryingCallState;
|
||||||
private listener: InterceptingListener | null = null;
|
private listener: InterceptingListener | null = null;
|
||||||
private initialMetadata: Metadata | null = null;
|
private initialMetadata: Metadata | null = null;
|
||||||
|
@ -198,6 +200,7 @@ export class RetryingCall implements Call {
|
||||||
private committedCallIndex: number | null = null;
|
private committedCallIndex: number | null = null;
|
||||||
private initialRetryBackoffSec = 0;
|
private initialRetryBackoffSec = 0;
|
||||||
private nextRetryBackoffSec = 0;
|
private nextRetryBackoffSec = 0;
|
||||||
|
private startTime: Date;
|
||||||
constructor(
|
constructor(
|
||||||
private readonly channel: InternalChannel,
|
private readonly channel: InternalChannel,
|
||||||
private readonly callConfig: CallConfig,
|
private readonly callConfig: CallConfig,
|
||||||
|
@ -223,6 +226,22 @@ export class RetryingCall implements Call {
|
||||||
} else {
|
} else {
|
||||||
this.state = 'TRANSPARENT_ONLY';
|
this.state = 'TRANSPARENT_ONLY';
|
||||||
}
|
}
|
||||||
|
this.startTime = new Date();
|
||||||
|
}
|
||||||
|
getDeadlineInfo(): string[] {
|
||||||
|
if (this.underlyingCalls.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const deadlineInfo: string[] = [];
|
||||||
|
const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1];
|
||||||
|
if (this.underlyingCalls.length > 1) {
|
||||||
|
deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`);
|
||||||
|
}
|
||||||
|
if (latestCall.startTime > this.startTime) {
|
||||||
|
deadlineInfo.push(`time to current attempt start: ${formatDateDifference(this.startTime, latestCall.startTime)}`);
|
||||||
|
}
|
||||||
|
deadlineInfo.push(...latestCall.call.getDeadlineInfo());
|
||||||
|
return deadlineInfo;
|
||||||
}
|
}
|
||||||
getCallNumber(): number {
|
getCallNumber(): number {
|
||||||
return this.callNumber;
|
return this.callNumber;
|
||||||
|
@ -242,7 +261,8 @@ export class RetryingCall implements Call {
|
||||||
statusObject.code +
|
statusObject.code +
|
||||||
' details="' +
|
' details="' +
|
||||||
statusObject.details +
|
statusObject.details +
|
||||||
'"'
|
'" start time=' +
|
||||||
|
this.startTime.toISOString()
|
||||||
);
|
);
|
||||||
this.bufferTracker.freeAll(this.callNumber);
|
this.bufferTracker.freeAll(this.callNumber);
|
||||||
this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length;
|
this.writeBufferOffset = this.writeBufferOffset + this.writeBuffer.length;
|
||||||
|
@ -628,6 +648,7 @@ export class RetryingCall implements Call {
|
||||||
state: 'ACTIVE',
|
state: 'ACTIVE',
|
||||||
call: child,
|
call: child,
|
||||||
nextMessageToSend: 0,
|
nextMessageToSend: 0,
|
||||||
|
startTime: new Date()
|
||||||
});
|
});
|
||||||
const previousAttempts = this.attempts - 1;
|
const previousAttempts = this.attempts - 1;
|
||||||
const initialMetadata = this.initialMetadata!.clone();
|
const initialMetadata = this.initialMetadata!.clone();
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isIP } from 'net';
|
import { isIP, isIPv6 } from 'net';
|
||||||
|
|
||||||
export interface TcpSubchannelAddress {
|
export interface TcpSubchannelAddress {
|
||||||
port: number;
|
port: number;
|
||||||
|
@ -63,7 +63,11 @@ export function subchannelAddressEqual(
|
||||||
|
|
||||||
export function subchannelAddressToString(address: SubchannelAddress): string {
|
export function subchannelAddressToString(address: SubchannelAddress): string {
|
||||||
if (isTcpSubchannelAddress(address)) {
|
if (isTcpSubchannelAddress(address)) {
|
||||||
return address.host + ':' + address.port;
|
if (isIPv6(address.host)) {
|
||||||
|
return '[' + address.host + ']:' + address.port;
|
||||||
|
} else {
|
||||||
|
return address.host + ':' + address.port;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return address.path;
|
return address.path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ export interface SubchannelCall {
|
||||||
startRead(): void;
|
startRead(): void;
|
||||||
halfClose(): void;
|
halfClose(): void;
|
||||||
getCallNumber(): number;
|
getCallNumber(): number;
|
||||||
|
getDeadlineInfo(): string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusObjectWithRstCode extends StatusObject {
|
export interface StatusObjectWithRstCode extends StatusObject {
|
||||||
|
@ -291,6 +292,9 @@ export class Http2SubchannelCall implements SubchannelCall {
|
||||||
this.callEventTracker.onStreamEnd(false);
|
this.callEventTracker.onStreamEnd(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
getDeadlineInfo(): string[] {
|
||||||
|
return [`remote_addr=${this.getPeer()}`];
|
||||||
|
}
|
||||||
|
|
||||||
public onDisconnect() {
|
public onDisconnect() {
|
||||||
this.endCall({
|
this.endCall({
|
||||||
|
|
Loading…
Reference in New Issue