Skip to content
Open
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
8 changes: 4 additions & 4 deletions packages/grpc-js/src/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
import { EventEmitter } from 'events';
import { Duplex, Readable, Writable } from 'stream';

import { StatusObject, MessageContext } from './call-interface';
import { AuthContext } from './auth-context';
import { MessageContext, StatusObject } from './call-interface';
import { InterceptingCallInterface } from './client-interceptors';
import { Status } from './constants';
import { EmitterAugmentation1 } from './events';
import { Metadata } from './metadata';
import { ObjectReadable, ObjectWritable, WriteCallback } from './object-stream';
import { InterceptingCallInterface } from './client-interceptors';
import { AuthContext } from './auth-context';

/**
* A type extending the built-in Error object with additional fields.
Expand Down Expand Up @@ -83,7 +83,7 @@ export function callErrorFromStatus(
const message = `${status.code} ${Status[status.code]}: ${status.details}`;
const error = new Error(message);
const stack = `${error.stack}\nfor call at\n${callerStack}`;
return Object.assign(new Error(message), status, { stack });
return Object.assign(error, status, { stack });
}

export class ClientUnaryCallImpl
Expand Down
9 changes: 8 additions & 1 deletion packages/grpc-js/src/channel-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export interface ChannelOptions {
'grpc-node.retry_max_attempts_limit'?: number;
'grpc-node.flow_control_window'?: number;
'grpc.server_call_metric_recording'?: number;
/**
* Whether to construct an Error object on every call to capture the caller's
* stack trace. Enabled by default (1). Can be set to 0 to disable and avoid
* stack trace capture overhead for every RPC.
*/
'grpc.enable_caller_stack_traces'?: number;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with other channel options that are specific to this implementation, and have no reason to ever be shared with the C++ implementation, this option name should be grpc-node.enable_caller_stack_traces.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}
Expand Down Expand Up @@ -105,7 +111,8 @@ export const recognizedOptions = {
'grpc.lb.ring_hash.ring_size_cap': true,
'grpc-node.retry_max_attempts_limit': true,
'grpc-node.flow_control_window': true,
'grpc.server_call_metric_recording': true
'grpc.server_call_metric_recording': true,
'grpc.enable_caller_stack_traces': true,
};

export function channelOptionsEqual(
Expand Down
69 changes: 45 additions & 24 deletions packages/grpc-js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import {
callErrorFromStatus,
ClientDuplexStream,
ClientDuplexStreamImpl,
ClientReadableStream,
Expand All @@ -25,37 +26,37 @@ import {
ClientWritableStream,
ClientWritableStreamImpl,
ServiceError,
callErrorFromStatus,
SurfaceCall,
} from './call';
import { CallCredentials } from './call-credentials';
import { StatusObject } from './call-interface';
import { Channel, ChannelImplementation } from './channel';
import { ConnectivityState } from './connectivity-state';
import { ChannelCredentials } from './channel-credentials';
import { ChannelOptions } from './channel-options';
import { Status } from './constants';
import { Metadata } from './metadata';
import { ClientMethodDefinition } from './make-client';
import {
getInterceptingCall,
InterceptingCallInterface,
Interceptor,
InterceptorProvider,
InterceptorArguments,
InterceptingCallInterface,
InterceptorProvider,
} from './client-interceptors';
import { ConnectivityState } from './connectivity-state';
import { Status } from './constants';
import { Deadline } from './deadline';
import { ClientMethodDefinition } from './make-client';
import { Metadata } from './metadata';
import {
ServerUnaryCall,
ServerDuplexStream,
ServerReadableStream,
ServerUnaryCall,
ServerWritableStream,
ServerDuplexStream,
} from './server-call';
import { Deadline } from './deadline';

const CHANNEL_SYMBOL = Symbol();
const INTERCEPTOR_SYMBOL = Symbol();
const INTERCEPTOR_PROVIDER_SYMBOL = Symbol();
const CALL_INVOCATION_TRANSFORMER_SYMBOL = Symbol();
const ENABLE_CALLER_STACK_TRACES_SYMBOL = Symbol();

function isFunction<ResponseType>(
arg: Metadata | CallOptions | UnaryCallback<ResponseType> | undefined
Expand Down Expand Up @@ -109,8 +110,10 @@ export type ClientOptions = Partial<ChannelOptions> & {
callInvocationTransformer?: CallInvocationTransformer;
};

function getErrorStackString(error: Error): string {
return error.stack?.split('\n').slice(1).join('\n') || 'no stack trace available';
function getErrorStackString(error: Error | null): string {
return (
error?.stack?.split('\n').slice(1).join('\n') || 'no stack trace available'
);
}

/**
Expand All @@ -122,12 +125,16 @@ export class Client {
private readonly [INTERCEPTOR_SYMBOL]: Interceptor[];
private readonly [INTERCEPTOR_PROVIDER_SYMBOL]: InterceptorProvider[];
private readonly [CALL_INVOCATION_TRANSFORMER_SYMBOL]?: CallInvocationTransformer;
private readonly [ENABLE_CALLER_STACK_TRACES_SYMBOL]: boolean;
constructor(
address: string,
credentials: ChannelCredentials,
options: ClientOptions = {}
) {
options = Object.assign({}, options);
this[ENABLE_CALLER_STACK_TRACES_SYMBOL] =
options['grpc.enable_caller_stack_traces'] !== 0 &&
(options['grpc.enable_caller_stack_traces'] as any) !== false;
this[INTERCEPTOR_SYMBOL] = options.interceptors ?? [];
delete options.interceptors;
this[INTERCEPTOR_PROVIDER_SYMBOL] = options.interceptor_providers ?? [];
Expand Down Expand Up @@ -322,15 +329,20 @@ export class Client {
emitter.call = call;
let responseMessage: ResponseType | null = null;
let receivedStatus = false;
let callerStackError: Error | null = new Error();
let callerStackError: Error | null = this[ENABLE_CALLER_STACK_TRACES_SYMBOL]
? new Error()
: null;
call.start(callProperties.metadata, {
onReceiveMetadata: metadata => {
emitter.emit('metadata', metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message: any) {
if (responseMessage !== null) {
call.cancelWithStatus(Status.UNIMPLEMENTED, 'Too many responses received');
call.cancelWithStatus(
Status.UNIMPLEMENTED,
'Too many responses received'
);
}
responseMessage = message;
},
Expand All @@ -341,7 +353,7 @@ export class Client {
receivedStatus = true;
if (status.code === Status.OK) {
if (responseMessage === null) {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
callProperties.callback!(
callErrorFromStatus(
{
Expand All @@ -356,7 +368,7 @@ export class Client {
callProperties.callback!(null, responseMessage);
}
} else {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
callProperties.callback!(callErrorFromStatus(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
Expand Down Expand Up @@ -455,15 +467,20 @@ export class Client {
emitter.call = call;
let responseMessage: ResponseType | null = null;
let receivedStatus = false;
let callerStackError: Error | null = new Error();
let callerStackError: Error | null = this[ENABLE_CALLER_STACK_TRACES_SYMBOL]
? new Error()
: null;
call.start(callProperties.metadata, {
onReceiveMetadata: metadata => {
emitter.emit('metadata', metadata);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onReceiveMessage(message: any) {
if (responseMessage !== null) {
call.cancelWithStatus(Status.UNIMPLEMENTED, 'Too many responses received');
call.cancelWithStatus(
Status.UNIMPLEMENTED,
'Too many responses received'
);
}
responseMessage = message;
call.startRead();
Expand All @@ -475,7 +492,7 @@ export class Client {
receivedStatus = true;
if (status.code === Status.OK) {
if (responseMessage === null) {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
callProperties.callback!(
callErrorFromStatus(
{
Expand All @@ -490,7 +507,7 @@ export class Client {
callProperties.callback!(null, responseMessage);
}
} else {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
callProperties.callback!(callErrorFromStatus(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
Expand Down Expand Up @@ -592,7 +609,9 @@ export class Client {
* call after that. */
stream.call = call;
let receivedStatus = false;
let callerStackError: Error | null = new Error();
let callerStackError: Error | null = this[ENABLE_CALLER_STACK_TRACES_SYMBOL]
? new Error()
: null;
call.start(callProperties.metadata, {
onReceiveMetadata(metadata: Metadata) {
stream.emit('metadata', metadata);
Expand All @@ -608,7 +627,7 @@ export class Client {
receivedStatus = true;
stream.push(null);
if (status.code !== Status.OK) {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
stream.emit('error', callErrorFromStatus(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
Expand Down Expand Up @@ -687,7 +706,9 @@ export class Client {
* call after that. */
stream.call = call;
let receivedStatus = false;
let callerStackError: Error | null = new Error();
let callerStackError: Error | null = this[ENABLE_CALLER_STACK_TRACES_SYMBOL]
? new Error()
: null;
call.start(callProperties.metadata, {
onReceiveMetadata(metadata: Metadata) {
stream.emit('metadata', metadata);
Expand All @@ -702,7 +723,7 @@ export class Client {
receivedStatus = true;
stream.push(null);
if (status.code !== Status.OK) {
const callerStack = getErrorStackString(callerStackError!);
const callerStack = getErrorStackString(callerStackError);
stream.emit('error', callErrorFromStatus(status, callerStack));
}
/* Avoid retaining the callerStackError object in the call context of
Expand Down
Loading