UNPKG

30.2 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2019 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.ChannelImplementation = void 0;
20const call_stream_1 = require("./call-stream");
21const channel_credentials_1 = require("./channel-credentials");
22const resolving_load_balancer_1 = require("./resolving-load-balancer");
23const subchannel_pool_1 = require("./subchannel-pool");
24const picker_1 = require("./picker");
25const constants_1 = require("./constants");
26const filter_stack_1 = require("./filter-stack");
27const call_credentials_filter_1 = require("./call-credentials-filter");
28const deadline_filter_1 = require("./deadline-filter");
29const compression_filter_1 = require("./compression-filter");
30const resolver_1 = require("./resolver");
31const logging_1 = require("./logging");
32const max_message_size_filter_1 = require("./max-message-size-filter");
33const http_proxy_1 = require("./http_proxy");
34const uri_parser_1 = require("./uri-parser");
35const connectivity_state_1 = require("./connectivity-state");
36const channelz_1 = require("./channelz");
37/**
38 * See https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_args
39 */
40const MAX_TIMEOUT_TIME = 2147483647;
41let nextCallNumber = 0;
42function getNewCallNumber() {
43 const callNumber = nextCallNumber;
44 nextCallNumber += 1;
45 if (nextCallNumber >= Number.MAX_SAFE_INTEGER) {
46 nextCallNumber = 0;
47 }
48 return callNumber;
49}
50const INAPPROPRIATE_CONTROL_PLANE_CODES = [
51 constants_1.Status.OK,
52 constants_1.Status.INVALID_ARGUMENT,
53 constants_1.Status.NOT_FOUND,
54 constants_1.Status.ALREADY_EXISTS,
55 constants_1.Status.FAILED_PRECONDITION,
56 constants_1.Status.ABORTED,
57 constants_1.Status.OUT_OF_RANGE,
58 constants_1.Status.DATA_LOSS
59];
60function restrictControlPlaneStatusCode(code, details) {
61 if (INAPPROPRIATE_CONTROL_PLANE_CODES.includes(code)) {
62 return {
63 code: constants_1.Status.INTERNAL,
64 details: `Invalid status from control plane: ${code} ${constants_1.Status[code]} ${details}`
65 };
66 }
67 else {
68 return { code, details };
69 }
70}
71class ChannelImplementation {
72 constructor(target, credentials, options) {
73 var _a, _b, _c, _d;
74 this.credentials = credentials;
75 this.options = options;
76 this.connectivityState = connectivity_state_1.ConnectivityState.IDLE;
77 this.currentPicker = new picker_1.UnavailablePicker();
78 /**
79 * Calls queued up to get a call config. Should only be populated before the
80 * first time the resolver returns a result, which includes the ConfigSelector.
81 */
82 this.configSelectionQueue = [];
83 this.pickQueue = [];
84 this.connectivityStateWatchers = [];
85 this.configSelector = null;
86 /**
87 * This is the error from the name resolver if it failed most recently. It
88 * is only used to end calls that start while there is no config selector
89 * and the name resolver is in backoff, so it should be nulled if
90 * configSelector becomes set or the channel state becomes anything other
91 * than TRANSIENT_FAILURE.
92 */
93 this.currentResolutionError = null;
94 // Channelz info
95 this.channelzEnabled = true;
96 this.callTracker = new channelz_1.ChannelzCallTracker();
97 this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
98 if (typeof target !== 'string') {
99 throw new TypeError('Channel target must be a string');
100 }
101 if (!(credentials instanceof channel_credentials_1.ChannelCredentials)) {
102 throw new TypeError('Channel credentials must be a ChannelCredentials object');
103 }
104 if (options) {
105 if (typeof options !== 'object') {
106 throw new TypeError('Channel options must be an object');
107 }
108 }
109 this.originalTarget = target;
110 const originalTargetUri = uri_parser_1.parseUri(target);
111 if (originalTargetUri === null) {
112 throw new Error(`Could not parse target name "${target}"`);
113 }
114 /* This ensures that the target has a scheme that is registered with the
115 * resolver */
116 const defaultSchemeMapResult = resolver_1.mapUriDefaultScheme(originalTargetUri);
117 if (defaultSchemeMapResult === null) {
118 throw new Error(`Could not find a default scheme for target name "${target}"`);
119 }
120 this.callRefTimer = setInterval(() => { }, MAX_TIMEOUT_TIME);
121 (_b = (_a = this.callRefTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
122 if (this.options['grpc.enable_channelz'] === 0) {
123 this.channelzEnabled = false;
124 }
125 this.channelzTrace = new channelz_1.ChannelzTrace();
126 this.channelzRef = channelz_1.registerChannelzChannel(target, () => this.getChannelzInfo(), this.channelzEnabled);
127 if (this.channelzEnabled) {
128 this.channelzTrace.addTrace('CT_INFO', 'Channel created');
129 }
130 if (this.options['grpc.default_authority']) {
131 this.defaultAuthority = this.options['grpc.default_authority'];
132 }
133 else {
134 this.defaultAuthority = resolver_1.getDefaultAuthority(defaultSchemeMapResult);
135 }
136 const proxyMapResult = http_proxy_1.mapProxyName(defaultSchemeMapResult, options);
137 this.target = proxyMapResult.target;
138 this.options = Object.assign({}, this.options, proxyMapResult.extraOptions);
139 /* The global boolean parameter to getSubchannelPool has the inverse meaning to what
140 * the grpc.use_local_subchannel_pool channel option means. */
141 this.subchannelPool = subchannel_pool_1.getSubchannelPool(((_c = options['grpc.use_local_subchannel_pool']) !== null && _c !== void 0 ? _c : 0) === 0);
142 const channelControlHelper = {
143 createSubchannel: (subchannelAddress, subchannelArgs) => {
144 const subchannel = this.subchannelPool.getOrCreateSubchannel(this.target, subchannelAddress, Object.assign({}, this.options, subchannelArgs), this.credentials);
145 if (this.channelzEnabled) {
146 this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef());
147 }
148 return subchannel;
149 },
150 updateState: (connectivityState, picker) => {
151 this.currentPicker = picker;
152 const queueCopy = this.pickQueue.slice();
153 this.pickQueue = [];
154 this.callRefTimerUnref();
155 for (const { callStream, callMetadata, callConfig, dynamicFilters } of queueCopy) {
156 this.tryPick(callStream, callMetadata, callConfig, dynamicFilters);
157 }
158 this.updateState(connectivityState);
159 },
160 requestReresolution: () => {
161 // This should never be called.
162 throw new Error('Resolving load balancer should never call requestReresolution');
163 },
164 addChannelzChild: (child) => {
165 if (this.channelzEnabled) {
166 this.childrenTracker.refChild(child);
167 }
168 },
169 removeChannelzChild: (child) => {
170 if (this.channelzEnabled) {
171 this.childrenTracker.unrefChild(child);
172 }
173 }
174 };
175 this.resolvingLoadBalancer = new resolving_load_balancer_1.ResolvingLoadBalancer(this.target, channelControlHelper, options, (configSelector) => {
176 if (this.channelzEnabled) {
177 this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded');
178 }
179 this.configSelector = configSelector;
180 this.currentResolutionError = null;
181 /* We process the queue asynchronously to ensure that the corresponding
182 * load balancer update has completed. */
183 process.nextTick(() => {
184 const localQueue = this.configSelectionQueue;
185 this.configSelectionQueue = [];
186 this.callRefTimerUnref();
187 for (const { callStream, callMetadata } of localQueue) {
188 this.tryGetConfig(callStream, callMetadata);
189 }
190 this.configSelectionQueue = [];
191 });
192 }, (status) => {
193 if (this.channelzEnabled) {
194 this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"');
195 }
196 if (this.configSelectionQueue.length > 0) {
197 this.trace('Name resolution failed with calls queued for config selection');
198 }
199 if (this.configSelector === null) {
200 this.currentResolutionError = Object.assign(Object.assign({}, restrictControlPlaneStatusCode(status.code, status.details)), { metadata: status.metadata });
201 }
202 const localQueue = this.configSelectionQueue;
203 this.configSelectionQueue = [];
204 this.callRefTimerUnref();
205 for (const { callStream, callMetadata } of localQueue) {
206 if (callMetadata.getOptions().waitForReady) {
207 this.callRefTimerRef();
208 this.configSelectionQueue.push({ callStream, callMetadata });
209 }
210 else {
211 callStream.cancelWithStatus(status.code, status.details);
212 }
213 }
214 });
215 this.filterStackFactory = new filter_stack_1.FilterStackFactory([
216 new call_credentials_filter_1.CallCredentialsFilterFactory(this),
217 new deadline_filter_1.DeadlineFilterFactory(this),
218 new max_message_size_filter_1.MaxMessageSizeFilterFactory(this.options),
219 new compression_filter_1.CompressionFilterFactory(this, this.options),
220 ]);
221 this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2));
222 const error = new Error();
223 logging_1.trace(constants_1.LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + ((_d = error.stack) === null || _d === void 0 ? void 0 : _d.substring(error.stack.indexOf('\n') + 1)));
224 }
225 getChannelzInfo() {
226 return {
227 target: this.originalTarget,
228 state: this.connectivityState,
229 trace: this.channelzTrace,
230 callTracker: this.callTracker,
231 children: this.childrenTracker.getChildLists()
232 };
233 }
234 trace(text, verbosityOverride) {
235 logging_1.trace(verbosityOverride !== null && verbosityOverride !== void 0 ? verbosityOverride : constants_1.LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uri_parser_1.uriToString(this.target) + ' ' + text);
236 }
237 callRefTimerRef() {
238 var _a, _b, _c, _d;
239 // If the hasRef function does not exist, always run the code
240 if (!((_b = (_a = this.callRefTimer).hasRef) === null || _b === void 0 ? void 0 : _b.call(_a))) {
241 this.trace('callRefTimer.ref | configSelectionQueue.length=' +
242 this.configSelectionQueue.length +
243 ' pickQueue.length=' +
244 this.pickQueue.length);
245 (_d = (_c = this.callRefTimer).ref) === null || _d === void 0 ? void 0 : _d.call(_c);
246 }
247 }
248 callRefTimerUnref() {
249 var _a, _b;
250 // If the hasRef function does not exist, always run the code
251 if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) {
252 this.trace('callRefTimer.unref | configSelectionQueue.length=' +
253 this.configSelectionQueue.length +
254 ' pickQueue.length=' +
255 this.pickQueue.length);
256 (_b = (_a = this.callRefTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
257 }
258 }
259 pushPick(callStream, callMetadata, callConfig, dynamicFilters) {
260 this.pickQueue.push({ callStream, callMetadata, callConfig, dynamicFilters });
261 this.callRefTimerRef();
262 }
263 /**
264 * Check the picker output for the given call and corresponding metadata,
265 * and take any relevant actions. Should not be called while iterating
266 * over pickQueue.
267 * @param callStream
268 * @param callMetadata
269 */
270 tryPick(callStream, callMetadata, callConfig, dynamicFilters) {
271 var _a, _b;
272 const pickResult = this.currentPicker.pick({
273 metadata: callMetadata,
274 extraPickInfo: callConfig.pickInformation,
275 });
276 const subchannelString = pickResult.subchannel ?
277 '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() :
278 '' + pickResult.subchannel;
279 this.trace('Pick result for call [' +
280 callStream.getCallNumber() +
281 ']: ' +
282 picker_1.PickResultType[pickResult.pickResultType] +
283 ' subchannel: ' +
284 subchannelString +
285 ' status: ' + ((_a = pickResult.status) === null || _a === void 0 ? void 0 : _a.code) +
286 ' ' + ((_b = pickResult.status) === null || _b === void 0 ? void 0 : _b.details));
287 switch (pickResult.pickResultType) {
288 case picker_1.PickResultType.COMPLETE:
289 if (pickResult.subchannel === null) {
290 callStream.cancelWithStatus(constants_1.Status.UNAVAILABLE, 'Request dropped by load balancing policy');
291 // End the call with an error
292 }
293 else {
294 /* If the subchannel is not in the READY state, that indicates a bug
295 * somewhere in the load balancer or picker. So, we log an error and
296 * queue the pick to be tried again later. */
297 if (pickResult.subchannel.getConnectivityState() !==
298 connectivity_state_1.ConnectivityState.READY) {
299 logging_1.log(constants_1.LogVerbosity.ERROR, 'Error: COMPLETE pick result subchannel ' +
300 subchannelString +
301 ' has state ' +
302 connectivity_state_1.ConnectivityState[pickResult.subchannel.getConnectivityState()]);
303 this.pushPick(callStream, callMetadata, callConfig, dynamicFilters);
304 break;
305 }
306 /* We need to clone the callMetadata here because the transparent
307 * retry code in the promise resolution handler use the same
308 * callMetadata object, so it needs to stay unmodified */
309 callStream.filterStack
310 .sendMetadata(Promise.resolve(callMetadata.clone()))
311 .then((finalMetadata) => {
312 var _a, _b, _c;
313 const subchannelState = pickResult.subchannel.getConnectivityState();
314 if (subchannelState === connectivity_state_1.ConnectivityState.READY) {
315 try {
316 const pickExtraFilters = pickResult.extraFilterFactories.map(factory => factory.createFilter(callStream));
317 (_a = pickResult.subchannel) === null || _a === void 0 ? void 0 : _a.getRealSubchannel().startCallStream(finalMetadata, callStream, [...dynamicFilters, ...pickExtraFilters]);
318 /* If we reach this point, the call stream has started
319 * successfully */
320 (_b = callConfig.onCommitted) === null || _b === void 0 ? void 0 : _b.call(callConfig);
321 (_c = pickResult.onCallStarted) === null || _c === void 0 ? void 0 : _c.call(pickResult);
322 }
323 catch (error) {
324 const errorCode = error.code;
325 if (errorCode === 'ERR_HTTP2_GOAWAY_SESSION' ||
326 errorCode === 'ERR_HTTP2_INVALID_SESSION') {
327 /* An error here indicates that something went wrong with
328 * the picked subchannel's http2 stream right before we
329 * tried to start the stream. We are handling a promise
330 * result here, so this is asynchronous with respect to the
331 * original tryPick call, so calling it again is not
332 * recursive. We call tryPick immediately instead of
333 * queueing this pick again because handling the queue is
334 * triggered by state changes, and we want to immediately
335 * check if the state has already changed since the
336 * previous tryPick call. We do this instead of cancelling
337 * the stream because the correct behavior may be
338 * re-queueing instead, based on the logic in the rest of
339 * tryPick */
340 this.trace('Failed to start call on picked subchannel ' +
341 subchannelString +
342 ' with error ' +
343 error.message +
344 '. Retrying pick', constants_1.LogVerbosity.INFO);
345 this.tryPick(callStream, callMetadata, callConfig, dynamicFilters);
346 }
347 else {
348 this.trace('Failed to start call on picked subchanel ' +
349 subchannelString +
350 ' with error ' +
351 error.message +
352 '. Ending call', constants_1.LogVerbosity.INFO);
353 callStream.cancelWithStatus(constants_1.Status.INTERNAL, `Failed to start HTTP/2 stream with error: ${error.message}`);
354 }
355 }
356 }
357 else {
358 /* The logic for doing this here is the same as in the catch
359 * block above */
360 this.trace('Picked subchannel ' +
361 subchannelString +
362 ' has state ' +
363 connectivity_state_1.ConnectivityState[subchannelState] +
364 ' after metadata filters. Retrying pick', constants_1.LogVerbosity.INFO);
365 this.tryPick(callStream, callMetadata, callConfig, dynamicFilters);
366 }
367 }, (error) => {
368 // We assume the error code isn't 0 (Status.OK)
369 const { code, details } = restrictControlPlaneStatusCode(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
370 callStream.cancelWithStatus(code, details);
371 });
372 }
373 break;
374 case picker_1.PickResultType.QUEUE:
375 this.pushPick(callStream, callMetadata, callConfig, dynamicFilters);
376 break;
377 case picker_1.PickResultType.TRANSIENT_FAILURE:
378 if (callMetadata.getOptions().waitForReady) {
379 this.pushPick(callStream, callMetadata, callConfig, dynamicFilters);
380 }
381 else {
382 const { code, details } = restrictControlPlaneStatusCode(pickResult.status.code, pickResult.status.details);
383 callStream.cancelWithStatus(code, details);
384 }
385 break;
386 case picker_1.PickResultType.DROP:
387 const { code, details } = restrictControlPlaneStatusCode(pickResult.status.code, pickResult.status.details);
388 callStream.cancelWithStatus(code, details);
389 break;
390 default:
391 throw new Error(`Invalid state: unknown pickResultType ${pickResult.pickResultType}`);
392 }
393 }
394 removeConnectivityStateWatcher(watcherObject) {
395 const watcherIndex = this.connectivityStateWatchers.findIndex((value) => value === watcherObject);
396 if (watcherIndex >= 0) {
397 this.connectivityStateWatchers.splice(watcherIndex, 1);
398 }
399 }
400 updateState(newState) {
401 logging_1.trace(constants_1.LogVerbosity.DEBUG, 'connectivity_state', '(' + this.channelzRef.id + ') ' +
402 uri_parser_1.uriToString(this.target) +
403 ' ' +
404 connectivity_state_1.ConnectivityState[this.connectivityState] +
405 ' -> ' +
406 connectivity_state_1.ConnectivityState[newState]);
407 if (this.channelzEnabled) {
408 this.channelzTrace.addTrace('CT_INFO', connectivity_state_1.ConnectivityState[this.connectivityState] + ' -> ' + connectivity_state_1.ConnectivityState[newState]);
409 }
410 this.connectivityState = newState;
411 const watchersCopy = this.connectivityStateWatchers.slice();
412 for (const watcherObject of watchersCopy) {
413 if (newState !== watcherObject.currentState) {
414 if (watcherObject.timer) {
415 clearTimeout(watcherObject.timer);
416 }
417 this.removeConnectivityStateWatcher(watcherObject);
418 watcherObject.callback();
419 }
420 }
421 if (newState !== connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
422 this.currentResolutionError = null;
423 }
424 }
425 tryGetConfig(stream, metadata) {
426 if (stream.getStatus() !== null) {
427 /* If the stream has a status, it has already finished and we don't need
428 * to take any more actions on it. */
429 return;
430 }
431 if (this.configSelector === null) {
432 /* This branch will only be taken at the beginning of the channel's life,
433 * before the resolver ever returns a result. So, the
434 * ResolvingLoadBalancer may be idle and if so it needs to be kicked
435 * because it now has a pending request. */
436 this.resolvingLoadBalancer.exitIdle();
437 if (this.currentResolutionError && !metadata.getOptions().waitForReady) {
438 stream.cancelWithStatus(this.currentResolutionError.code, this.currentResolutionError.details);
439 }
440 else {
441 this.configSelectionQueue.push({
442 callStream: stream,
443 callMetadata: metadata,
444 });
445 this.callRefTimerRef();
446 }
447 }
448 else {
449 const callConfig = this.configSelector(stream.getMethod(), metadata);
450 if (callConfig.status === constants_1.Status.OK) {
451 if (callConfig.methodConfig.timeout) {
452 const deadline = new Date();
453 deadline.setSeconds(deadline.getSeconds() + callConfig.methodConfig.timeout.seconds);
454 deadline.setMilliseconds(deadline.getMilliseconds() +
455 callConfig.methodConfig.timeout.nanos / 1000000);
456 stream.setConfigDeadline(deadline);
457 // Refreshing the filters makes the deadline filter pick up the new deadline
458 stream.filterStack.refresh();
459 }
460 if (callConfig.dynamicFilterFactories.length > 0) {
461 /* These dynamicFilters are the mechanism for implementing gRFC A39:
462 * https://github.com/grpc/proposal/blob/master/A39-xds-http-filters.md
463 * We run them here instead of with the rest of the filters because
464 * that spec says "the xDS HTTP filters will run in between name
465 * resolution and load balancing".
466 *
467 * We use the filter stack here to simplify the multi-filter async
468 * waterfall logic, but we pass along the underlying list of filters
469 * to avoid having nested filter stacks when combining it with the
470 * original filter stack. We do not pass along the original filter
471 * factory list because these filters may need to persist data
472 * between sending headers and other operations. */
473 const dynamicFilterStackFactory = new filter_stack_1.FilterStackFactory(callConfig.dynamicFilterFactories);
474 const dynamicFilterStack = dynamicFilterStackFactory.createFilter(stream);
475 dynamicFilterStack.sendMetadata(Promise.resolve(metadata)).then(filteredMetadata => {
476 this.tryPick(stream, filteredMetadata, callConfig, dynamicFilterStack.getFilters());
477 });
478 }
479 else {
480 this.tryPick(stream, metadata, callConfig, []);
481 }
482 }
483 else {
484 const { code, details } = restrictControlPlaneStatusCode(callConfig.status, 'Failed to route call to method ' + stream.getMethod());
485 stream.cancelWithStatus(code, details);
486 }
487 }
488 }
489 _startCallStream(stream, metadata) {
490 this.tryGetConfig(stream, metadata.clone());
491 }
492 close() {
493 this.resolvingLoadBalancer.destroy();
494 this.updateState(connectivity_state_1.ConnectivityState.SHUTDOWN);
495 clearInterval(this.callRefTimer);
496 if (this.channelzEnabled) {
497 channelz_1.unregisterChannelzRef(this.channelzRef);
498 }
499 this.subchannelPool.unrefUnusedSubchannels();
500 }
501 getTarget() {
502 return uri_parser_1.uriToString(this.target);
503 }
504 getConnectivityState(tryToConnect) {
505 const connectivityState = this.connectivityState;
506 if (tryToConnect) {
507 this.resolvingLoadBalancer.exitIdle();
508 }
509 return connectivityState;
510 }
511 watchConnectivityState(currentState, deadline, callback) {
512 if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
513 throw new Error('Channel has been shut down');
514 }
515 let timer = null;
516 if (deadline !== Infinity) {
517 const deadlineDate = deadline instanceof Date ? deadline : new Date(deadline);
518 const now = new Date();
519 if (deadline === -Infinity || deadlineDate <= now) {
520 process.nextTick(callback, new Error('Deadline passed without connectivity state change'));
521 return;
522 }
523 timer = setTimeout(() => {
524 this.removeConnectivityStateWatcher(watcherObject);
525 callback(new Error('Deadline passed without connectivity state change'));
526 }, deadlineDate.getTime() - now.getTime());
527 }
528 const watcherObject = {
529 currentState,
530 callback,
531 timer,
532 };
533 this.connectivityStateWatchers.push(watcherObject);
534 }
535 /**
536 * Get the channelz reference object for this channel. The returned value is
537 * garbage if channelz is disabled for this channel.
538 * @returns
539 */
540 getChannelzRef() {
541 return this.channelzRef;
542 }
543 createCall(method, deadline, host, parentCall, propagateFlags) {
544 if (typeof method !== 'string') {
545 throw new TypeError('Channel#createCall: method must be a string');
546 }
547 if (!(typeof deadline === 'number' || deadline instanceof Date)) {
548 throw new TypeError('Channel#createCall: deadline must be a number or Date');
549 }
550 if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
551 throw new Error('Channel has been shut down');
552 }
553 const callNumber = getNewCallNumber();
554 this.trace('createCall [' +
555 callNumber +
556 '] method="' +
557 method +
558 '", deadline=' +
559 deadline);
560 const finalOptions = {
561 deadline: deadline,
562 flags: propagateFlags !== null && propagateFlags !== void 0 ? propagateFlags : constants_1.Propagate.DEFAULTS,
563 host: host !== null && host !== void 0 ? host : this.defaultAuthority,
564 parentCall: parentCall,
565 };
566 const stream = new call_stream_1.Http2CallStream(method, this, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), callNumber);
567 if (this.channelzEnabled) {
568 this.callTracker.addCallStarted();
569 stream.addStatusWatcher(status => {
570 if (status.code === constants_1.Status.OK) {
571 this.callTracker.addCallSucceeded();
572 }
573 else {
574 this.callTracker.addCallFailed();
575 }
576 });
577 }
578 return stream;
579 }
580}
581exports.ChannelImplementation = ChannelImplementation;
582//# sourceMappingURL=channel.js.map
\No newline at end of file