"use strict"; /* * 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. * */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ResolvingCall = void 0; const call_credentials_1 = require("./call-credentials"); const constants_1 = require("./constants"); const deadline_1 = require("./deadline"); const metadata_1 = require("./metadata"); const logging = require("./logging"); const control_plane_status_1 = require("./control-plane-status"); const TRACER_NAME = 'resolving_call'; class ResolvingCall { constructor(channel, method, options, filterStackFactory, callNumber) { this.channel = channel; this.method = method; this.filterStackFactory = filterStackFactory; this.callNumber = callNumber; this.child = null; this.readPending = false; this.pendingMessage = null; this.pendingHalfClose = false; this.ended = false; this.readFilterPending = false; this.writeFilterPending = false; this.pendingChildStatus = null; this.metadata = null; this.listener = null; this.statusWatchers = []; this.deadlineTimer = setTimeout(() => { }, 0); this.filterStack = null; this.deadlineStartTime = null; this.configReceivedTime = null; this.childStartTime = null; /** * Credentials configured for this specific call. Does not include * call credentials associated with the channel credentials used to create * the channel. */ this.credentials = call_credentials_1.CallCredentials.createEmpty(); this.deadline = options.deadline; this.host = options.host; if (options.parentCall) { if (options.flags & constants_1.Propagate.CANCELLATION) { options.parentCall.on('cancelled', () => { this.cancelWithStatus(constants_1.Status.CANCELLED, 'Cancelled by parent call'); }); } if (options.flags & constants_1.Propagate.DEADLINE) { this.trace('Propagating deadline from parent: ' + options.parentCall.getDeadline()); this.deadline = (0, deadline_1.minDeadline)(this.deadline, options.parentCall.getDeadline()); } } this.trace('Created'); this.runDeadlineTimer(); } trace(text) { logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text); } runDeadlineTimer() { clearTimeout(this.deadlineTimer); this.deadlineStartTime = new Date(); this.trace('Deadline: ' + (0, deadline_1.deadlineToString)(this.deadline)); const timeout = (0, deadline_1.getRelativeTimeout)(this.deadline); if (timeout !== Infinity) { this.trace('Deadline will be reached in ' + timeout + 'ms'); const handleDeadline = () => { if (!this.deadlineStartTime) { this.cancelWithStatus(constants_1.Status.DEADLINE_EXCEEDED, 'Deadline exceeded'); return; } const deadlineInfo = []; const deadlineEndTime = new Date(); deadlineInfo.push(`Deadline exceeded after ${(0, deadline_1.formatDateDifference)(this.deadlineStartTime, deadlineEndTime)}`); if (this.configReceivedTime) { if (this.configReceivedTime > this.deadlineStartTime) { deadlineInfo.push(`name resolution: ${(0, deadline_1.formatDateDifference)(this.deadlineStartTime, this.configReceivedTime)}`); } if (this.childStartTime) { if (this.childStartTime > this.configReceivedTime) { deadlineInfo.push(`metadata filters: ${(0, deadline_1.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(constants_1.Status.DEADLINE_EXCEEDED, deadlineInfo.join(',')); }; if (timeout <= 0) { process.nextTick(handleDeadline); } else { this.deadlineTimer = setTimeout(handleDeadline, timeout); } } } outputStatus(status) { 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(() => { var _a; (_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus(filteredStatus); }); } } sendMessageOnChild(context, message) { 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) => { this.cancelWithStatus(status.code, status.details); }); } getConfig() { 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' this.configReceivedTime = new Date(); const config = configResult.config; if (config.status !== constants_1.Status.OK) { const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(config.status, 'Failed to route call to method ' + this.method); this.outputStatus({ code: code, details: details, metadata: new metadata_1.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 / 1000000); this.deadline = (0, deadline_1.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.createRetryingCall(config, this.method, this.host, this.credentials, this.deadline); this.trace('Created child [' + this.child.getCallNumber() + ']'); this.childStartTime = new Date(); 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) => { 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) => { this.outputStatus(status); }); } reportResolverError(status) { var _a; if ((_a = this.metadata) === null || _a === void 0 ? void 0 : _a.getOptions().waitForReady) { this.channel.queueCallForConfig(this); } else { this.outputStatus(status); } } cancelWithStatus(status, details) { var _a; this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"'); (_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details); this.outputStatus({ code: status, details: details, metadata: new metadata_1.Metadata(), }); } getPeer() { var _a, _b; return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget(); } start(metadata, listener) { this.trace('start called'); this.metadata = metadata.clone(); this.listener = listener; this.getConfig(); } sendMessageWithContext(context, message) { this.trace('write() called with message of length ' + message.length); if (this.child) { this.sendMessageOnChild(context, message); } else { this.pendingMessage = { context, message }; } } startRead() { this.trace('startRead called'); if (this.child) { this.child.startRead(); } else { this.readPending = true; } } halfClose() { this.trace('halfClose called'); if (this.child && !this.writeFilterPending) { this.child.halfClose(); } else { this.pendingHalfClose = true; } } setCredentials(credentials) { this.credentials = credentials; } addStatusWatcher(watcher) { this.statusWatchers.push(watcher); } getCallNumber() { return this.callNumber; } } exports.ResolvingCall = ResolvingCall; //# sourceMappingURL=resolving-call.js.map