diff options
Diffstat (limited to 'frontend-old/node_modules/@grpc/grpc-js/src/resolving-call.ts')
| -rw-r--r-- | frontend-old/node_modules/@grpc/grpc-js/src/resolving-call.ts | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/frontend-old/node_modules/@grpc/grpc-js/src/resolving-call.ts b/frontend-old/node_modules/@grpc/grpc-js/src/resolving-call.ts new file mode 100644 index 0000000..723533d --- /dev/null +++ b/frontend-old/node_modules/@grpc/grpc-js/src/resolving-call.ts @@ -0,0 +1,331 @@ +/* + * Copyright 2022 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { CallCredentials } from './call-credentials'; +import { + Call, + CallStreamOptions, + InterceptingListener, + MessageContext, + StatusObject, +} from './call-interface'; +import { LogVerbosity, Propagate, Status } from './constants'; +import { + Deadline, + deadlineToString, + getRelativeTimeout, + minDeadline, +} from './deadline'; +import { FilterStack, FilterStackFactory } from './filter-stack'; +import { InternalChannel } from './internal-channel'; +import { Metadata } from './metadata'; +import * as logging from './logging'; +import { restrictControlPlaneStatusCode } from './control-plane-status'; + +const TRACER_NAME = 'resolving_call'; + +export class ResolvingCall implements Call { + private child: Call | null = null; + private readPending = false; + private pendingMessage: { context: MessageContext; message: Buffer } | null = + null; + private pendingHalfClose = false; + private ended = false; + private readFilterPending = false; + private writeFilterPending = false; + private pendingChildStatus: StatusObject | null = null; + private metadata: Metadata | null = null; + private listener: InterceptingListener | null = null; + private deadline: Deadline; + private host: string; + private statusWatchers: ((status: StatusObject) => void)[] = []; + private deadlineTimer: NodeJS.Timeout = setTimeout(() => {}, 0); + private filterStack: FilterStack | null = null; + + constructor( + private readonly channel: InternalChannel, + private readonly method: string, + options: CallStreamOptions, + private readonly filterStackFactory: FilterStackFactory, + private credentials: CallCredentials, + private callNumber: number + ) { + this.deadline = options.deadline; + this.host = options.host; + if (options.parentCall) { + if (options.flags & Propagate.CANCELLATION) { + options.parentCall.on('cancelled', () => { + this.cancelWithStatus(Status.CANCELLED, 'Cancelled by parent call'); + }); + } + if (options.flags & Propagate.DEADLINE) { + this.trace( + 'Propagating deadline from parent: ' + + options.parentCall.getDeadline() + ); + this.deadline = minDeadline( + this.deadline, + options.parentCall.getDeadline() + ); + } + } + this.trace('Created'); + this.runDeadlineTimer(); + } + + private trace(text: string): void { + logging.trace( + LogVerbosity.DEBUG, + TRACER_NAME, + '[' + this.callNumber + '] ' + text + ); + } + + private runDeadlineTimer() { + clearTimeout(this.deadlineTimer); + 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(Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); + }; + if (timeout <= 0) { + process.nextTick(handleDeadline); + } else { + this.deadlineTimer = setTimeout(handleDeadline, timeout); + } + } + } + + private outputStatus(status: StatusObject) { + if (!this.ended) { + this.ended = true; + if (!this.filterStack) { + this.filterStack = this.filterStackFactory.createFilter(); + } + clearTimeout(this.deadlineTimer); + const filteredStatus = this.filterStack.receiveTrailers(status); + this.trace( + 'ended with status: code=' + + filteredStatus.code + + ' details="' + + filteredStatus.details + + '"' + ); + this.statusWatchers.forEach(watcher => watcher(filteredStatus)); + process.nextTick(() => { + this.listener?.onReceiveStatus(filteredStatus); + }); + } + } + + private sendMessageOnChild(context: MessageContext, message: Buffer): void { + if (!this.child) { + throw new Error('sendMessageonChild called with child not populated'); + } + const child = this.child; + this.writeFilterPending = true; + this.filterStack!.sendMessage( + Promise.resolve({ message: message, flags: context.flags }) + ).then( + filteredMessage => { + this.writeFilterPending = false; + child.sendMessageWithContext(context, filteredMessage.message); + if (this.pendingHalfClose) { + child.halfClose(); + } + }, + (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + } + ); + } + + getConfig(): void { + if (this.ended) { + return; + } + if (!this.metadata || !this.listener) { + throw new Error('getConfig called before start'); + } + const configResult = this.channel.getConfig(this.method, this.metadata); + if (configResult.type === 'NONE') { + this.channel.queueCallForConfig(this); + return; + } else if (configResult.type === 'ERROR') { + if (this.metadata.getOptions().waitForReady) { + this.channel.queueCallForConfig(this); + } else { + this.outputStatus(configResult.error); + } + return; + } + // configResult.type === 'SUCCESS' + const config = configResult.config; + if (config.status !== Status.OK) { + const { code, details } = restrictControlPlaneStatusCode( + config.status, + 'Failed to route call to method ' + this.method + ); + this.outputStatus({ + code: code, + details: details, + metadata: new Metadata(), + }); + return; + } + + if (config.methodConfig.timeout) { + const configDeadline = new Date(); + configDeadline.setSeconds( + configDeadline.getSeconds() + config.methodConfig.timeout.seconds + ); + configDeadline.setMilliseconds( + configDeadline.getMilliseconds() + + config.methodConfig.timeout.nanos / 1_000_000 + ); + this.deadline = minDeadline(this.deadline, configDeadline); + this.runDeadlineTimer(); + } + + this.filterStackFactory.push(config.dynamicFilterFactories); + this.filterStack = this.filterStackFactory.createFilter(); + this.filterStack.sendMetadata(Promise.resolve(this.metadata)).then( + filteredMetadata => { + this.child = this.channel.createInnerCall( + config, + this.method, + this.host, + this.credentials, + this.deadline + ); + this.trace('Created child [' + this.child.getCallNumber() + ']'); + this.child.start(filteredMetadata, { + onReceiveMetadata: metadata => { + this.trace('Received metadata'); + this.listener!.onReceiveMetadata( + this.filterStack!.receiveMetadata(metadata) + ); + }, + onReceiveMessage: message => { + this.trace('Received message'); + this.readFilterPending = true; + this.filterStack!.receiveMessage(message).then( + filteredMesssage => { + this.trace('Finished filtering received message'); + this.readFilterPending = false; + this.listener!.onReceiveMessage(filteredMesssage); + if (this.pendingChildStatus) { + this.outputStatus(this.pendingChildStatus); + } + }, + (status: StatusObject) => { + this.cancelWithStatus(status.code, status.details); + } + ); + }, + onReceiveStatus: status => { + this.trace('Received status'); + if (this.readFilterPending) { + this.pendingChildStatus = status; + } else { + this.outputStatus(status); + } + }, + }); + if (this.readPending) { + this.child.startRead(); + } + if (this.pendingMessage) { + this.sendMessageOnChild( + this.pendingMessage.context, + this.pendingMessage.message + ); + } else if (this.pendingHalfClose) { + this.child.halfClose(); + } + }, + (status: StatusObject) => { + this.outputStatus(status); + } + ); + } + + reportResolverError(status: StatusObject) { + if (this.metadata?.getOptions().waitForReady) { + this.channel.queueCallForConfig(this); + } else { + this.outputStatus(status); + } + } + cancelWithStatus(status: Status, details: string): void { + this.trace( + 'cancelWithStatus code: ' + status + ' details: "' + details + '"' + ); + this.child?.cancelWithStatus(status, details); + this.outputStatus({ + code: status, + details: details, + metadata: new Metadata(), + }); + } + getPeer(): string { + return this.child?.getPeer() ?? this.channel.getTarget(); + } + start(metadata: Metadata, listener: InterceptingListener): void { + this.trace('start called'); + this.metadata = metadata.clone(); + this.listener = listener; + this.getConfig(); + } + sendMessageWithContext(context: MessageContext, message: Buffer): void { + this.trace('write() called with message of length ' + message.length); + if (this.child) { + this.sendMessageOnChild(context, message); + } else { + this.pendingMessage = { context, message }; + } + } + startRead(): void { + this.trace('startRead called'); + if (this.child) { + this.child.startRead(); + } else { + this.readPending = true; + } + } + halfClose(): void { + this.trace('halfClose called'); + if (this.child && !this.writeFilterPending) { + this.child.halfClose(); + } else { + this.pendingHalfClose = true; + } + } + setCredentials(credentials: CallCredentials): void { + this.credentials = this.credentials.compose(credentials); + } + + addStatusWatcher(watcher: (status: StatusObject) => void) { + this.statusWatchers.push(watcher); + } + + getCallNumber(): number { + return this.callNumber; + } +} |
