From 37eb5ed2fabb4a3831f0a506bf44cd6e1ae3da5a Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 9 Feb 2023 10:18:24 -0800 Subject: [PATCH] grpc-js: Improve timeout handling and deadline logging --- packages/grpc-js/src/deadline.ts | 39 +++++++++++++++++++++++- packages/grpc-js/src/internal-channel.ts | 4 +-- packages/grpc-js/src/resolving-call.ts | 8 ++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/grpc-js/src/deadline.ts b/packages/grpc-js/src/deadline.ts index 58ea0a80..be1f3c3b 100644 --- a/packages/grpc-js/src/deadline.ts +++ b/packages/grpc-js/src/deadline.ts @@ -51,8 +51,45 @@ export function getDeadlineTimeoutString(deadline: Deadline) { throw new Error('Deadline is too far in the future') } +/** + * See https://nodejs.org/api/timers.html#settimeoutcallback-delay-args + * In particular, "When delay is larger than 2147483647 or less than 1, the + * delay will be set to 1. Non-integer delays are truncated to an integer." + * This number of milliseconds is almost 25 days. + */ +const MAX_TIMEOUT_TIME = 2147483647; + +/** + * Get the timeout value that should be passed to setTimeout now for the timer + * to end at the deadline. For any deadline before now, the timer should end + * immediately, represented by a value of 0. For any deadline more than + * MAX_TIMEOUT_TIME milliseconds in the future, a timer cannot be set that will + * end at that time, so it is treated as infinitely far in the future. + * @param deadline + * @returns + */ export function getRelativeTimeout(deadline: Deadline) { const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline; const now = new Date().getTime(); - return deadlineMs - now; + const timeout = deadlineMs - now; + if (timeout < 0) { + return 0; + } else if (timeout > MAX_TIMEOUT_TIME) { + return Infinity + } else { + return timeout; + } +} + +export function deadlineToString(deadline: Deadline): string { + if (deadline instanceof Date) { + return deadline.toISOString(); + } else { + const dateDeadline = new Date(deadline); + if (Number.isNaN(dateDeadline.getTime())) { + return '' + deadline; + } else { + return dateDeadline.toISOString(); + } + } } \ No newline at end of file diff --git a/packages/grpc-js/src/internal-channel.ts b/packages/grpc-js/src/internal-channel.ts index 9137be27..2a1d00f5 100644 --- a/packages/grpc-js/src/internal-channel.ts +++ b/packages/grpc-js/src/internal-channel.ts @@ -46,7 +46,7 @@ import { LoadBalancingCall } from './load-balancing-call'; import { CallCredentials } from './call-credentials'; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from './call-interface'; import { SubchannelCall } from './subchannel-call'; -import { Deadline, getDeadlineTimeoutString } from './deadline'; +import { Deadline, deadlineToString, getDeadlineTimeoutString } from './deadline'; import { ResolvingCall } from './resolving-call'; import { getNextCallNumber } from './call-number'; import { restrictControlPlaneStatusCode } from './control-plane-status'; @@ -469,7 +469,7 @@ export class InternalChannel { '] method="' + method + '", deadline=' + - deadline + deadlineToString(deadline) ); const finalOptions: CallStreamOptions = { deadline: deadline, diff --git a/packages/grpc-js/src/resolving-call.ts b/packages/grpc-js/src/resolving-call.ts index f1fecd1d..b6398126 100644 --- a/packages/grpc-js/src/resolving-call.ts +++ b/packages/grpc-js/src/resolving-call.ts @@ -18,7 +18,7 @@ import { CallCredentials } from "./call-credentials"; import { Call, CallStreamOptions, InterceptingListener, MessageContext, StatusObject } from "./call-interface"; import { LogVerbosity, Propagate, Status } from "./constants"; -import { Deadline, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; +import { Deadline, deadlineToString, getDeadlineTimeoutString, getRelativeTimeout, minDeadline } from "./deadline"; import { FilterStack, FilterStackFactory } from "./filter-stack"; import { InternalChannel } from "./internal-channel"; import { Metadata } from "./metadata"; @@ -79,9 +79,9 @@ export class ResolvingCall implements Call { private runDeadlineTimer() { clearTimeout(this.deadlineTimer); - this.trace('Deadline: ' + this.deadline); - if (this.deadline !== Infinity) { - const timeout = getRelativeTimeout(this.deadline); + this.trace('Deadline: ' + deadlineToString(this.deadline)); + const timeout = getRelativeTimeout(this.deadline); + if (timeout !== Infinity) { this.trace('Deadline will be reached in ' + timeout + 'ms'); const handleDeadline = () => { this.cancelWithStatus(