1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | Object.defineProperty(exports, "__esModule", { value: true });
|
19 | exports.InternalChannel = void 0;
|
20 | const channel_credentials_1 = require("./channel-credentials");
|
21 | const resolving_load_balancer_1 = require("./resolving-load-balancer");
|
22 | const subchannel_pool_1 = require("./subchannel-pool");
|
23 | const picker_1 = require("./picker");
|
24 | const constants_1 = require("./constants");
|
25 | const filter_stack_1 = require("./filter-stack");
|
26 | const compression_filter_1 = require("./compression-filter");
|
27 | const resolver_1 = require("./resolver");
|
28 | const logging_1 = require("./logging");
|
29 | const max_message_size_filter_1 = require("./max-message-size-filter");
|
30 | const http_proxy_1 = require("./http_proxy");
|
31 | const uri_parser_1 = require("./uri-parser");
|
32 | const connectivity_state_1 = require("./connectivity-state");
|
33 | const channelz_1 = require("./channelz");
|
34 | const load_balancing_call_1 = require("./load-balancing-call");
|
35 | const resolving_call_1 = require("./resolving-call");
|
36 | const call_number_1 = require("./call-number");
|
37 | const control_plane_status_1 = require("./control-plane-status");
|
38 | const retrying_call_1 = require("./retrying-call");
|
39 |
|
40 |
|
41 |
|
42 | const MAX_TIMEOUT_TIME = 2147483647;
|
43 | const RETRY_THROTTLER_MAP = new Map();
|
44 | const DEFAULT_RETRY_BUFFER_SIZE_BYTES = 1 << 24;
|
45 | const DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES = 1 << 20;
|
46 | class InternalChannel {
|
47 | constructor(target, credentials, options) {
|
48 | var _a, _b, _c, _d, _e, _f;
|
49 | this.credentials = credentials;
|
50 | this.options = options;
|
51 | this.connectivityState = connectivity_state_1.ConnectivityState.IDLE;
|
52 | this.currentPicker = new picker_1.UnavailablePicker();
|
53 | |
54 |
|
55 |
|
56 |
|
57 | this.configSelectionQueue = [];
|
58 | this.pickQueue = [];
|
59 | this.connectivityStateWatchers = [];
|
60 | this.configSelector = null;
|
61 | |
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | this.currentResolutionError = null;
|
69 |
|
70 | this.channelzEnabled = true;
|
71 | this.callTracker = new channelz_1.ChannelzCallTracker();
|
72 | this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
|
73 | if (typeof target !== 'string') {
|
74 | throw new TypeError('Channel target must be a string');
|
75 | }
|
76 | if (!(credentials instanceof channel_credentials_1.ChannelCredentials)) {
|
77 | throw new TypeError('Channel credentials must be a ChannelCredentials object');
|
78 | }
|
79 | if (options) {
|
80 | if (typeof options !== 'object') {
|
81 | throw new TypeError('Channel options must be an object');
|
82 | }
|
83 | }
|
84 | this.originalTarget = target;
|
85 | const originalTargetUri = uri_parser_1.parseUri(target);
|
86 | if (originalTargetUri === null) {
|
87 | throw new Error(`Could not parse target name "${target}"`);
|
88 | }
|
89 | |
90 |
|
91 | const defaultSchemeMapResult = resolver_1.mapUriDefaultScheme(originalTargetUri);
|
92 | if (defaultSchemeMapResult === null) {
|
93 | throw new Error(`Could not find a default scheme for target name "${target}"`);
|
94 | }
|
95 | this.callRefTimer = setInterval(() => { }, MAX_TIMEOUT_TIME);
|
96 | (_b = (_a = this.callRefTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
97 | if (this.options['grpc.enable_channelz'] === 0) {
|
98 | this.channelzEnabled = false;
|
99 | }
|
100 | this.channelzTrace = new channelz_1.ChannelzTrace();
|
101 | this.channelzRef = channelz_1.registerChannelzChannel(target, () => this.getChannelzInfo(), this.channelzEnabled);
|
102 | if (this.channelzEnabled) {
|
103 | this.channelzTrace.addTrace('CT_INFO', 'Channel created');
|
104 | }
|
105 | if (this.options['grpc.default_authority']) {
|
106 | this.defaultAuthority = this.options['grpc.default_authority'];
|
107 | }
|
108 | else {
|
109 | this.defaultAuthority = resolver_1.getDefaultAuthority(defaultSchemeMapResult);
|
110 | }
|
111 | const proxyMapResult = http_proxy_1.mapProxyName(defaultSchemeMapResult, options);
|
112 | this.target = proxyMapResult.target;
|
113 | this.options = Object.assign({}, this.options, proxyMapResult.extraOptions);
|
114 | |
115 |
|
116 | this.subchannelPool = subchannel_pool_1.getSubchannelPool(((_c = options['grpc.use_local_subchannel_pool']) !== null && _c !== void 0 ? _c : 0) === 0);
|
117 | this.retryBufferTracker = new retrying_call_1.MessageBufferTracker((_d = options['grpc.retry_buffer_size']) !== null && _d !== void 0 ? _d : DEFAULT_RETRY_BUFFER_SIZE_BYTES, (_e = options['grpc.per_rpc_retry_buffer_size']) !== null && _e !== void 0 ? _e : DEFAULT_PER_RPC_RETRY_BUFFER_SIZE_BYTES);
|
118 | const channelControlHelper = {
|
119 | createSubchannel: (subchannelAddress, subchannelArgs) => {
|
120 | const subchannel = this.subchannelPool.getOrCreateSubchannel(this.target, subchannelAddress, Object.assign({}, this.options, subchannelArgs), this.credentials);
|
121 | if (this.channelzEnabled) {
|
122 | this.channelzTrace.addTrace('CT_INFO', 'Created subchannel or used existing subchannel', subchannel.getChannelzRef());
|
123 | }
|
124 | return subchannel;
|
125 | },
|
126 | updateState: (connectivityState, picker) => {
|
127 | this.currentPicker = picker;
|
128 | const queueCopy = this.pickQueue.slice();
|
129 | this.pickQueue = [];
|
130 | this.callRefTimerUnref();
|
131 | for (const call of queueCopy) {
|
132 | call.doPick();
|
133 | }
|
134 | this.updateState(connectivityState);
|
135 | },
|
136 | requestReresolution: () => {
|
137 |
|
138 | throw new Error('Resolving load balancer should never call requestReresolution');
|
139 | },
|
140 | addChannelzChild: (child) => {
|
141 | if (this.channelzEnabled) {
|
142 | this.childrenTracker.refChild(child);
|
143 | }
|
144 | },
|
145 | removeChannelzChild: (child) => {
|
146 | if (this.channelzEnabled) {
|
147 | this.childrenTracker.unrefChild(child);
|
148 | }
|
149 | }
|
150 | };
|
151 | this.resolvingLoadBalancer = new resolving_load_balancer_1.ResolvingLoadBalancer(this.target, channelControlHelper, options, (serviceConfig, configSelector) => {
|
152 | if (serviceConfig.retryThrottling) {
|
153 | RETRY_THROTTLER_MAP.set(this.getTarget(), new retrying_call_1.RetryThrottler(serviceConfig.retryThrottling.maxTokens, serviceConfig.retryThrottling.tokenRatio, RETRY_THROTTLER_MAP.get(this.getTarget())));
|
154 | }
|
155 | else {
|
156 | RETRY_THROTTLER_MAP.delete(this.getTarget());
|
157 | }
|
158 | if (this.channelzEnabled) {
|
159 | this.channelzTrace.addTrace('CT_INFO', 'Address resolution succeeded');
|
160 | }
|
161 | this.configSelector = configSelector;
|
162 | this.currentResolutionError = null;
|
163 | |
164 |
|
165 | process.nextTick(() => {
|
166 | const localQueue = this.configSelectionQueue;
|
167 | this.configSelectionQueue = [];
|
168 | this.callRefTimerUnref();
|
169 | for (const call of localQueue) {
|
170 | call.getConfig();
|
171 | }
|
172 | this.configSelectionQueue = [];
|
173 | });
|
174 | }, (status) => {
|
175 | if (this.channelzEnabled) {
|
176 | this.channelzTrace.addTrace('CT_WARNING', 'Address resolution failed with code ' + status.code + ' and details "' + status.details + '"');
|
177 | }
|
178 | if (this.configSelectionQueue.length > 0) {
|
179 | this.trace('Name resolution failed with calls queued for config selection');
|
180 | }
|
181 | if (this.configSelector === null) {
|
182 | this.currentResolutionError = Object.assign(Object.assign({}, control_plane_status_1.restrictControlPlaneStatusCode(status.code, status.details)), { metadata: status.metadata });
|
183 | }
|
184 | const localQueue = this.configSelectionQueue;
|
185 | this.configSelectionQueue = [];
|
186 | this.callRefTimerUnref();
|
187 | for (const call of localQueue) {
|
188 | call.reportResolverError(status);
|
189 | }
|
190 | });
|
191 | this.filterStackFactory = new filter_stack_1.FilterStackFactory([
|
192 | new max_message_size_filter_1.MaxMessageSizeFilterFactory(this.options),
|
193 | new compression_filter_1.CompressionFilterFactory(this, this.options),
|
194 | ]);
|
195 | this.trace('Channel constructed with options ' + JSON.stringify(options, undefined, 2));
|
196 | const error = new Error();
|
197 | logging_1.trace(constants_1.LogVerbosity.DEBUG, 'channel_stacktrace', '(' + this.channelzRef.id + ') ' + 'Channel constructed \n' + ((_f = error.stack) === null || _f === void 0 ? void 0 : _f.substring(error.stack.indexOf('\n') + 1)));
|
198 | }
|
199 | getChannelzInfo() {
|
200 | return {
|
201 | target: this.originalTarget,
|
202 | state: this.connectivityState,
|
203 | trace: this.channelzTrace,
|
204 | callTracker: this.callTracker,
|
205 | children: this.childrenTracker.getChildLists()
|
206 | };
|
207 | }
|
208 | trace(text, verbosityOverride) {
|
209 | logging_1.trace(verbosityOverride !== null && verbosityOverride !== void 0 ? verbosityOverride : constants_1.LogVerbosity.DEBUG, 'channel', '(' + this.channelzRef.id + ') ' + uri_parser_1.uriToString(this.target) + ' ' + text);
|
210 | }
|
211 | callRefTimerRef() {
|
212 | var _a, _b, _c, _d;
|
213 |
|
214 | if (!((_b = (_a = this.callRefTimer).hasRef) === null || _b === void 0 ? void 0 : _b.call(_a))) {
|
215 | this.trace('callRefTimer.ref | configSelectionQueue.length=' +
|
216 | this.configSelectionQueue.length +
|
217 | ' pickQueue.length=' +
|
218 | this.pickQueue.length);
|
219 | (_d = (_c = this.callRefTimer).ref) === null || _d === void 0 ? void 0 : _d.call(_c);
|
220 | }
|
221 | }
|
222 | callRefTimerUnref() {
|
223 | var _a, _b;
|
224 |
|
225 | if (!this.callRefTimer.hasRef || this.callRefTimer.hasRef()) {
|
226 | this.trace('callRefTimer.unref | configSelectionQueue.length=' +
|
227 | this.configSelectionQueue.length +
|
228 | ' pickQueue.length=' +
|
229 | this.pickQueue.length);
|
230 | (_b = (_a = this.callRefTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
231 | }
|
232 | }
|
233 | removeConnectivityStateWatcher(watcherObject) {
|
234 | const watcherIndex = this.connectivityStateWatchers.findIndex((value) => value === watcherObject);
|
235 | if (watcherIndex >= 0) {
|
236 | this.connectivityStateWatchers.splice(watcherIndex, 1);
|
237 | }
|
238 | }
|
239 | updateState(newState) {
|
240 | logging_1.trace(constants_1.LogVerbosity.DEBUG, 'connectivity_state', '(' + this.channelzRef.id + ') ' +
|
241 | uri_parser_1.uriToString(this.target) +
|
242 | ' ' +
|
243 | connectivity_state_1.ConnectivityState[this.connectivityState] +
|
244 | ' -> ' +
|
245 | connectivity_state_1.ConnectivityState[newState]);
|
246 | if (this.channelzEnabled) {
|
247 | this.channelzTrace.addTrace('CT_INFO', connectivity_state_1.ConnectivityState[this.connectivityState] + ' -> ' + connectivity_state_1.ConnectivityState[newState]);
|
248 | }
|
249 | this.connectivityState = newState;
|
250 | const watchersCopy = this.connectivityStateWatchers.slice();
|
251 | for (const watcherObject of watchersCopy) {
|
252 | if (newState !== watcherObject.currentState) {
|
253 | if (watcherObject.timer) {
|
254 | clearTimeout(watcherObject.timer);
|
255 | }
|
256 | this.removeConnectivityStateWatcher(watcherObject);
|
257 | watcherObject.callback();
|
258 | }
|
259 | }
|
260 | if (newState !== connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
|
261 | this.currentResolutionError = null;
|
262 | }
|
263 | }
|
264 | doPick(metadata, extraPickInfo) {
|
265 | return this.currentPicker.pick({ metadata: metadata, extraPickInfo: extraPickInfo });
|
266 | }
|
267 | queueCallForPick(call) {
|
268 | this.pickQueue.push(call);
|
269 | this.callRefTimerRef();
|
270 | }
|
271 | getConfig(method, metadata) {
|
272 | this.resolvingLoadBalancer.exitIdle();
|
273 | if (this.configSelector) {
|
274 | return {
|
275 | type: 'SUCCESS',
|
276 | config: this.configSelector(method, metadata)
|
277 | };
|
278 | }
|
279 | else {
|
280 | if (this.currentResolutionError) {
|
281 | return {
|
282 | type: 'ERROR',
|
283 | error: this.currentResolutionError
|
284 | };
|
285 | }
|
286 | else {
|
287 | return {
|
288 | type: 'NONE'
|
289 | };
|
290 | }
|
291 | }
|
292 | }
|
293 | queueCallForConfig(call) {
|
294 | this.configSelectionQueue.push(call);
|
295 | this.callRefTimerRef();
|
296 | }
|
297 | createLoadBalancingCall(callConfig, method, host, credentials, deadline) {
|
298 | const callNumber = call_number_1.getNextCallNumber();
|
299 | this.trace('createLoadBalancingCall [' +
|
300 | callNumber +
|
301 | '] method="' +
|
302 | method +
|
303 | '"');
|
304 | return new load_balancing_call_1.LoadBalancingCall(this, callConfig, method, host, credentials, deadline, callNumber);
|
305 | }
|
306 | createRetryingCall(callConfig, method, host, credentials, deadline) {
|
307 | const callNumber = call_number_1.getNextCallNumber();
|
308 | this.trace('createRetryingCall [' +
|
309 | callNumber +
|
310 | '] method="' +
|
311 | method +
|
312 | '"');
|
313 | return new retrying_call_1.RetryingCall(this, callConfig, method, host, credentials, deadline, callNumber, this.retryBufferTracker, RETRY_THROTTLER_MAP.get(this.getTarget()));
|
314 | }
|
315 | createInnerCall(callConfig, method, host, credentials, deadline) {
|
316 |
|
317 | if (this.options['grpc.enable_retries'] === 0) {
|
318 | return this.createLoadBalancingCall(callConfig, method, host, credentials, deadline);
|
319 | }
|
320 | else {
|
321 | return this.createRetryingCall(callConfig, method, host, credentials, deadline);
|
322 | }
|
323 | }
|
324 | createResolvingCall(method, deadline, host, parentCall, propagateFlags) {
|
325 | const callNumber = call_number_1.getNextCallNumber();
|
326 | this.trace('createResolvingCall [' +
|
327 | callNumber +
|
328 | '] method="' +
|
329 | method +
|
330 | '", deadline=' +
|
331 | deadline);
|
332 | const finalOptions = {
|
333 | deadline: deadline,
|
334 | flags: propagateFlags !== null && propagateFlags !== void 0 ? propagateFlags : constants_1.Propagate.DEFAULTS,
|
335 | host: host !== null && host !== void 0 ? host : this.defaultAuthority,
|
336 | parentCall: parentCall,
|
337 | };
|
338 | const call = new resolving_call_1.ResolvingCall(this, method, finalOptions, this.filterStackFactory, this.credentials._getCallCredentials(), callNumber);
|
339 | if (this.channelzEnabled) {
|
340 | this.callTracker.addCallStarted();
|
341 | call.addStatusWatcher(status => {
|
342 | if (status.code === constants_1.Status.OK) {
|
343 | this.callTracker.addCallSucceeded();
|
344 | }
|
345 | else {
|
346 | this.callTracker.addCallFailed();
|
347 | }
|
348 | });
|
349 | }
|
350 | return call;
|
351 | }
|
352 | close() {
|
353 | this.resolvingLoadBalancer.destroy();
|
354 | this.updateState(connectivity_state_1.ConnectivityState.SHUTDOWN);
|
355 | clearInterval(this.callRefTimer);
|
356 | if (this.channelzEnabled) {
|
357 | channelz_1.unregisterChannelzRef(this.channelzRef);
|
358 | }
|
359 | this.subchannelPool.unrefUnusedSubchannels();
|
360 | }
|
361 | getTarget() {
|
362 | return uri_parser_1.uriToString(this.target);
|
363 | }
|
364 | getConnectivityState(tryToConnect) {
|
365 | const connectivityState = this.connectivityState;
|
366 | if (tryToConnect) {
|
367 | this.resolvingLoadBalancer.exitIdle();
|
368 | }
|
369 | return connectivityState;
|
370 | }
|
371 | watchConnectivityState(currentState, deadline, callback) {
|
372 | if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
|
373 | throw new Error('Channel has been shut down');
|
374 | }
|
375 | let timer = null;
|
376 | if (deadline !== Infinity) {
|
377 | const deadlineDate = deadline instanceof Date ? deadline : new Date(deadline);
|
378 | const now = new Date();
|
379 | if (deadline === -Infinity || deadlineDate <= now) {
|
380 | process.nextTick(callback, new Error('Deadline passed without connectivity state change'));
|
381 | return;
|
382 | }
|
383 | timer = setTimeout(() => {
|
384 | this.removeConnectivityStateWatcher(watcherObject);
|
385 | callback(new Error('Deadline passed without connectivity state change'));
|
386 | }, deadlineDate.getTime() - now.getTime());
|
387 | }
|
388 | const watcherObject = {
|
389 | currentState,
|
390 | callback,
|
391 | timer,
|
392 | };
|
393 | this.connectivityStateWatchers.push(watcherObject);
|
394 | }
|
395 | |
396 |
|
397 |
|
398 |
|
399 |
|
400 | getChannelzRef() {
|
401 | return this.channelzRef;
|
402 | }
|
403 | createCall(method, deadline, host, parentCall, propagateFlags) {
|
404 | if (typeof method !== 'string') {
|
405 | throw new TypeError('Channel#createCall: method must be a string');
|
406 | }
|
407 | if (!(typeof deadline === 'number' || deadline instanceof Date)) {
|
408 | throw new TypeError('Channel#createCall: deadline must be a number or Date');
|
409 | }
|
410 | if (this.connectivityState === connectivity_state_1.ConnectivityState.SHUTDOWN) {
|
411 | throw new Error('Channel has been shut down');
|
412 | }
|
413 | return this.createResolvingCall(method, deadline, host, parentCall, propagateFlags);
|
414 | }
|
415 | }
|
416 | exports.InternalChannel = InternalChannel;
|
417 |
|
\ | No newline at end of file |