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.Subchannel = void 0;
|
20 | const http2 = require("http2");
|
21 | const tls_1 = require("tls");
|
22 | const connectivity_state_1 = require("./connectivity-state");
|
23 | const backoff_timeout_1 = require("./backoff-timeout");
|
24 | const resolver_1 = require("./resolver");
|
25 | const logging = require("./logging");
|
26 | const constants_1 = require("./constants");
|
27 | const http_proxy_1 = require("./http_proxy");
|
28 | const net = require("net");
|
29 | const uri_parser_1 = require("./uri-parser");
|
30 | const subchannel_address_1 = require("./subchannel-address");
|
31 | const channelz_1 = require("./channelz");
|
32 | const clientVersion = require('../../package.json').version;
|
33 | const TRACER_NAME = 'subchannel';
|
34 | const FLOW_CONTROL_TRACER_NAME = 'subchannel_flowctrl';
|
35 | const MIN_CONNECT_TIMEOUT_MS = 20000;
|
36 | const INITIAL_BACKOFF_MS = 1000;
|
37 | const BACKOFF_MULTIPLIER = 1.6;
|
38 | const MAX_BACKOFF_MS = 120000;
|
39 | const BACKOFF_JITTER = 0.2;
|
40 |
|
41 |
|
42 |
|
43 | const KEEPALIVE_MAX_TIME_MS = ~(1 << 31);
|
44 | const KEEPALIVE_TIMEOUT_MS = 20000;
|
45 | const { HTTP2_HEADER_AUTHORITY, HTTP2_HEADER_CONTENT_TYPE, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_TE, HTTP2_HEADER_USER_AGENT, } = http2.constants;
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function uniformRandom(min, max) {
|
52 | return Math.random() * (max - min) + min;
|
53 | }
|
54 | const tooManyPingsData = Buffer.from('too_many_pings', 'ascii');
|
55 | class Subchannel {
|
56 | |
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | constructor(channelTarget, subchannelAddress, options, credentials) {
|
67 | this.channelTarget = channelTarget;
|
68 | this.subchannelAddress = subchannelAddress;
|
69 | this.options = options;
|
70 | this.credentials = credentials;
|
71 | |
72 |
|
73 |
|
74 |
|
75 | this.connectivityState = connectivity_state_1.ConnectivityState.IDLE;
|
76 | |
77 |
|
78 |
|
79 | this.session = null;
|
80 | |
81 |
|
82 |
|
83 |
|
84 | this.continueConnecting = false;
|
85 | |
86 |
|
87 |
|
88 |
|
89 |
|
90 | this.stateListeners = [];
|
91 | |
92 |
|
93 |
|
94 |
|
95 |
|
96 | this.disconnectListeners = new Set();
|
97 | |
98 |
|
99 |
|
100 | this.keepaliveTimeMs = KEEPALIVE_MAX_TIME_MS;
|
101 | |
102 |
|
103 |
|
104 | this.keepaliveTimeoutMs = KEEPALIVE_TIMEOUT_MS;
|
105 | |
106 |
|
107 |
|
108 | this.keepaliveWithoutCalls = false;
|
109 | |
110 |
|
111 |
|
112 | this.callRefcount = 0;
|
113 | |
114 |
|
115 |
|
116 | this.refcount = 0;
|
117 |
|
118 | this.channelzEnabled = true;
|
119 | this.callTracker = new channelz_1.ChannelzCallTracker();
|
120 | this.childrenTracker = new channelz_1.ChannelzChildrenTracker();
|
121 |
|
122 | this.channelzSocketRef = null;
|
123 | |
124 |
|
125 |
|
126 |
|
127 | this.remoteName = null;
|
128 | this.streamTracker = new channelz_1.ChannelzCallTracker();
|
129 | this.keepalivesSent = 0;
|
130 | this.messagesSent = 0;
|
131 | this.messagesReceived = 0;
|
132 | this.lastMessageSentTimestamp = null;
|
133 | this.lastMessageReceivedTimestamp = null;
|
134 |
|
135 | this.userAgent = [
|
136 | options['grpc.primary_user_agent'],
|
137 | `grpc-node-js/${clientVersion}`,
|
138 | options['grpc.secondary_user_agent'],
|
139 | ]
|
140 | .filter((e) => e)
|
141 | .join(' ');
|
142 | if ('grpc.keepalive_time_ms' in options) {
|
143 | this.keepaliveTimeMs = options['grpc.keepalive_time_ms'];
|
144 | }
|
145 | if ('grpc.keepalive_timeout_ms' in options) {
|
146 | this.keepaliveTimeoutMs = options['grpc.keepalive_timeout_ms'];
|
147 | }
|
148 | if ('grpc.keepalive_permit_without_calls' in options) {
|
149 | this.keepaliveWithoutCalls =
|
150 | options['grpc.keepalive_permit_without_calls'] === 1;
|
151 | }
|
152 | else {
|
153 | this.keepaliveWithoutCalls = false;
|
154 | }
|
155 | this.keepaliveIntervalId = setTimeout(() => { }, 0);
|
156 | clearTimeout(this.keepaliveIntervalId);
|
157 | this.keepaliveTimeoutId = setTimeout(() => { }, 0);
|
158 | clearTimeout(this.keepaliveTimeoutId);
|
159 | const backoffOptions = {
|
160 | initialDelay: options['grpc.initial_reconnect_backoff_ms'],
|
161 | maxDelay: options['grpc.max_reconnect_backoff_ms'],
|
162 | };
|
163 | this.backoffTimeout = new backoff_timeout_1.BackoffTimeout(() => {
|
164 | this.handleBackoffTimer();
|
165 | }, backoffOptions);
|
166 | this.subchannelAddressString = subchannel_address_1.subchannelAddressToString(subchannelAddress);
|
167 | if (options['grpc.enable_channelz'] === 0) {
|
168 | this.channelzEnabled = false;
|
169 | }
|
170 | this.channelzTrace = new channelz_1.ChannelzTrace();
|
171 | this.channelzRef = channelz_1.registerChannelzSubchannel(this.subchannelAddressString, () => this.getChannelzInfo(), this.channelzEnabled);
|
172 | if (this.channelzEnabled) {
|
173 | this.channelzTrace.addTrace('CT_INFO', 'Subchannel created');
|
174 | }
|
175 | this.trace('Subchannel constructed with options ' + JSON.stringify(options, undefined, 2));
|
176 | }
|
177 | getChannelzInfo() {
|
178 | return {
|
179 | state: this.connectivityState,
|
180 | trace: this.channelzTrace,
|
181 | callTracker: this.callTracker,
|
182 | children: this.childrenTracker.getChildLists(),
|
183 | target: this.subchannelAddressString
|
184 | };
|
185 | }
|
186 | getChannelzSocketInfo() {
|
187 | var _a, _b, _c;
|
188 | if (this.session === null) {
|
189 | return null;
|
190 | }
|
191 | const sessionSocket = this.session.socket;
|
192 | const remoteAddress = sessionSocket.remoteAddress ? subchannel_address_1.stringToSubchannelAddress(sessionSocket.remoteAddress, sessionSocket.remotePort) : null;
|
193 | const localAddress = sessionSocket.localAddress ? subchannel_address_1.stringToSubchannelAddress(sessionSocket.localAddress, sessionSocket.localPort) : null;
|
194 | let tlsInfo;
|
195 | if (this.session.encrypted) {
|
196 | const tlsSocket = sessionSocket;
|
197 | const cipherInfo = tlsSocket.getCipher();
|
198 | const certificate = tlsSocket.getCertificate();
|
199 | const peerCertificate = tlsSocket.getPeerCertificate();
|
200 | tlsInfo = {
|
201 | cipherSuiteStandardName: (_a = cipherInfo.standardName) !== null && _a !== void 0 ? _a : null,
|
202 | cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name,
|
203 | localCertificate: (certificate && 'raw' in certificate) ? certificate.raw : null,
|
204 | remoteCertificate: (peerCertificate && 'raw' in peerCertificate) ? peerCertificate.raw : null
|
205 | };
|
206 | }
|
207 | else {
|
208 | tlsInfo = null;
|
209 | }
|
210 | const socketInfo = {
|
211 | remoteAddress: remoteAddress,
|
212 | localAddress: localAddress,
|
213 | security: tlsInfo,
|
214 | remoteName: this.remoteName,
|
215 | streamsStarted: this.streamTracker.callsStarted,
|
216 | streamsSucceeded: this.streamTracker.callsSucceeded,
|
217 | streamsFailed: this.streamTracker.callsFailed,
|
218 | messagesSent: this.messagesSent,
|
219 | messagesReceived: this.messagesReceived,
|
220 | keepAlivesSent: this.keepalivesSent,
|
221 | lastLocalStreamCreatedTimestamp: this.streamTracker.lastCallStartedTimestamp,
|
222 | lastRemoteStreamCreatedTimestamp: null,
|
223 | lastMessageSentTimestamp: this.lastMessageSentTimestamp,
|
224 | lastMessageReceivedTimestamp: this.lastMessageReceivedTimestamp,
|
225 | localFlowControlWindow: (_b = this.session.state.localWindowSize) !== null && _b !== void 0 ? _b : null,
|
226 | remoteFlowControlWindow: (_c = this.session.state.remoteWindowSize) !== null && _c !== void 0 ? _c : null
|
227 | };
|
228 | return socketInfo;
|
229 | }
|
230 | resetChannelzSocketInfo() {
|
231 | if (!this.channelzEnabled) {
|
232 | return;
|
233 | }
|
234 | if (this.channelzSocketRef) {
|
235 | channelz_1.unregisterChannelzRef(this.channelzSocketRef);
|
236 | this.childrenTracker.unrefChild(this.channelzSocketRef);
|
237 | this.channelzSocketRef = null;
|
238 | }
|
239 | this.remoteName = null;
|
240 | this.streamTracker = new channelz_1.ChannelzCallTracker();
|
241 | this.keepalivesSent = 0;
|
242 | this.messagesSent = 0;
|
243 | this.messagesReceived = 0;
|
244 | this.lastMessageSentTimestamp = null;
|
245 | this.lastMessageReceivedTimestamp = null;
|
246 | }
|
247 | trace(text) {
|
248 | logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text);
|
249 | }
|
250 | refTrace(text) {
|
251 | logging.trace(constants_1.LogVerbosity.DEBUG, 'subchannel_refcount', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text);
|
252 | }
|
253 | flowControlTrace(text) {
|
254 | logging.trace(constants_1.LogVerbosity.DEBUG, FLOW_CONTROL_TRACER_NAME, '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text);
|
255 | }
|
256 | internalsTrace(text) {
|
257 | logging.trace(constants_1.LogVerbosity.DEBUG, 'subchannel_internals', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text);
|
258 | }
|
259 | keepaliveTrace(text) {
|
260 | logging.trace(constants_1.LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + this.subchannelAddressString + ' ' + text);
|
261 | }
|
262 | handleBackoffTimer() {
|
263 | if (this.continueConnecting) {
|
264 | this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.CONNECTING);
|
265 | }
|
266 | else {
|
267 | this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.IDLE);
|
268 | }
|
269 | }
|
270 | |
271 |
|
272 |
|
273 | startBackoff() {
|
274 | this.backoffTimeout.runOnce();
|
275 | }
|
276 | stopBackoff() {
|
277 | this.backoffTimeout.stop();
|
278 | this.backoffTimeout.reset();
|
279 | }
|
280 | sendPing() {
|
281 | var _a, _b;
|
282 | if (this.channelzEnabled) {
|
283 | this.keepalivesSent += 1;
|
284 | }
|
285 | this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms');
|
286 | this.keepaliveTimeoutId = setTimeout(() => {
|
287 | this.keepaliveTrace('Ping timeout passed without response');
|
288 | this.handleDisconnect();
|
289 | }, this.keepaliveTimeoutMs);
|
290 | (_b = (_a = this.keepaliveTimeoutId).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
291 | try {
|
292 | this.session.ping((err, duration, payload) => {
|
293 | this.keepaliveTrace('Received ping response');
|
294 | clearTimeout(this.keepaliveTimeoutId);
|
295 | });
|
296 | }
|
297 | catch (e) {
|
298 | |
299 |
|
300 | this.transitionToState([connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
|
301 | }
|
302 | }
|
303 | startKeepalivePings() {
|
304 | var _a, _b;
|
305 | this.keepaliveIntervalId = setInterval(() => {
|
306 | this.sendPing();
|
307 | }, this.keepaliveTimeMs);
|
308 | (_b = (_a = this.keepaliveIntervalId).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
309 | |
310 |
|
311 | }
|
312 | |
313 |
|
314 |
|
315 |
|
316 |
|
317 | stopKeepalivePings() {
|
318 | clearInterval(this.keepaliveIntervalId);
|
319 | clearTimeout(this.keepaliveTimeoutId);
|
320 | }
|
321 | createSession(proxyConnectionResult) {
|
322 | var _a, _b, _c;
|
323 | if (proxyConnectionResult.realTarget) {
|
324 | this.remoteName = uri_parser_1.uriToString(proxyConnectionResult.realTarget);
|
325 | this.trace('creating HTTP/2 session through proxy to ' + proxyConnectionResult.realTarget);
|
326 | }
|
327 | else {
|
328 | this.remoteName = null;
|
329 | this.trace('creating HTTP/2 session');
|
330 | }
|
331 | const targetAuthority = resolver_1.getDefaultAuthority((_a = proxyConnectionResult.realTarget) !== null && _a !== void 0 ? _a : this.channelTarget);
|
332 | let connectionOptions = this.credentials._getConnectionOptions() || {};
|
333 | connectionOptions.maxSendHeaderBlockLength = Number.MAX_SAFE_INTEGER;
|
334 | if ('grpc-node.max_session_memory' in this.options) {
|
335 | connectionOptions.maxSessionMemory = this.options['grpc-node.max_session_memory'];
|
336 | }
|
337 | else {
|
338 | |
339 |
|
340 |
|
341 |
|
342 | connectionOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER;
|
343 | }
|
344 | let addressScheme = 'http://';
|
345 | if ('secureContext' in connectionOptions) {
|
346 | addressScheme = 'https://';
|
347 |
|
348 |
|
349 |
|
350 | if (this.options['grpc.ssl_target_name_override']) {
|
351 | const sslTargetNameOverride = this.options['grpc.ssl_target_name_override'];
|
352 | connectionOptions.checkServerIdentity = (host, cert) => {
|
353 | return tls_1.checkServerIdentity(sslTargetNameOverride, cert);
|
354 | };
|
355 | connectionOptions.servername = sslTargetNameOverride;
|
356 | }
|
357 | else {
|
358 | const authorityHostname = (_c = (_b = uri_parser_1.splitHostPort(targetAuthority)) === null || _b === void 0 ? void 0 : _b.host) !== null && _c !== void 0 ? _c : 'localhost';
|
359 |
|
360 | connectionOptions.servername = authorityHostname;
|
361 | }
|
362 | if (proxyConnectionResult.socket) {
|
363 | |
364 |
|
365 |
|
366 |
|
367 |
|
368 | connectionOptions.createConnection = (authority, option) => {
|
369 | return proxyConnectionResult.socket;
|
370 | };
|
371 | }
|
372 | }
|
373 | else {
|
374 | |
375 |
|
376 |
|
377 | connectionOptions.createConnection = (authority, option) => {
|
378 | if (proxyConnectionResult.socket) {
|
379 | return proxyConnectionResult.socket;
|
380 | }
|
381 | else {
|
382 | |
383 |
|
384 |
|
385 | return net.connect(this.subchannelAddress);
|
386 | }
|
387 | };
|
388 | }
|
389 | connectionOptions = Object.assign(Object.assign({}, connectionOptions), this.subchannelAddress);
|
390 | |
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | const session = http2.connect(addressScheme + targetAuthority, connectionOptions);
|
408 | this.session = session;
|
409 | this.channelzSocketRef = channelz_1.registerChannelzSocket(this.subchannelAddressString, () => this.getChannelzSocketInfo(), this.channelzEnabled);
|
410 | if (this.channelzEnabled) {
|
411 | this.childrenTracker.refChild(this.channelzSocketRef);
|
412 | }
|
413 | session.unref();
|
414 | |
415 |
|
416 |
|
417 |
|
418 | session.once('connect', () => {
|
419 | if (this.session === session) {
|
420 | this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING], connectivity_state_1.ConnectivityState.READY);
|
421 | }
|
422 | });
|
423 | session.once('close', () => {
|
424 | if (this.session === session) {
|
425 | this.trace('connection closed');
|
426 | this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
|
427 | |
428 |
|
429 |
|
430 | this.transitionToState([connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.IDLE);
|
431 | }
|
432 | });
|
433 | session.once('goaway', (errorCode, lastStreamID, opaqueData) => {
|
434 | if (this.session === session) {
|
435 | |
436 |
|
437 | if (errorCode === http2.constants.NGHTTP2_ENHANCE_YOUR_CALM &&
|
438 | opaqueData.equals(tooManyPingsData)) {
|
439 | this.keepaliveTimeMs = Math.min(2 * this.keepaliveTimeMs, KEEPALIVE_MAX_TIME_MS);
|
440 | logging.log(constants_1.LogVerbosity.ERROR, `Connection to ${uri_parser_1.uriToString(this.channelTarget)} at ${this.subchannelAddressString} rejected by server because of excess pings. Increasing ping interval to ${this.keepaliveTimeMs} ms`);
|
441 | }
|
442 | this.trace('connection closed by GOAWAY with code ' +
|
443 | errorCode);
|
444 | this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING, connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.IDLE);
|
445 | }
|
446 | });
|
447 | session.once('error', (error) => {
|
448 | |
449 |
|
450 | this.trace('connection closed with error ' +
|
451 | error.message);
|
452 | });
|
453 | if (logging.isTracerEnabled(TRACER_NAME)) {
|
454 | session.on('remoteSettings', (settings) => {
|
455 | this.trace('new settings received' +
|
456 | (this.session !== session ? ' on the old connection' : '') +
|
457 | ': ' +
|
458 | JSON.stringify(settings));
|
459 | });
|
460 | session.on('localSettings', (settings) => {
|
461 | this.trace('local settings acknowledged by remote' +
|
462 | (this.session !== session ? ' on the old connection' : '') +
|
463 | ': ' +
|
464 | JSON.stringify(settings));
|
465 | });
|
466 | }
|
467 | }
|
468 | startConnectingInternal() {
|
469 | var _a, _b;
|
470 | |
471 |
|
472 |
|
473 |
|
474 | const connectionOptions = this.credentials._getConnectionOptions() || {};
|
475 | if ('secureContext' in connectionOptions) {
|
476 | connectionOptions.ALPNProtocols = ['h2'];
|
477 |
|
478 |
|
479 |
|
480 | if (this.options['grpc.ssl_target_name_override']) {
|
481 | const sslTargetNameOverride = this.options['grpc.ssl_target_name_override'];
|
482 | connectionOptions.checkServerIdentity = (host, cert) => {
|
483 | return tls_1.checkServerIdentity(sslTargetNameOverride, cert);
|
484 | };
|
485 | connectionOptions.servername = sslTargetNameOverride;
|
486 | }
|
487 | else {
|
488 | if ('grpc.http_connect_target' in this.options) {
|
489 | |
490 |
|
491 |
|
492 |
|
493 | const targetPath = resolver_1.getDefaultAuthority((_a = uri_parser_1.parseUri(this.options['grpc.http_connect_target'])) !== null && _a !== void 0 ? _a : {
|
494 | path: 'localhost',
|
495 | });
|
496 | const hostPort = uri_parser_1.splitHostPort(targetPath);
|
497 | connectionOptions.servername = (_b = hostPort === null || hostPort === void 0 ? void 0 : hostPort.host) !== null && _b !== void 0 ? _b : targetPath;
|
498 | }
|
499 | }
|
500 | }
|
501 | http_proxy_1.getProxiedConnection(this.subchannelAddress, this.options, connectionOptions).then((result) => {
|
502 | this.createSession(result);
|
503 | }, (reason) => {
|
504 | this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
|
505 | });
|
506 | }
|
507 | handleDisconnect() {
|
508 | this.transitionToState([connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
|
509 | for (const listener of this.disconnectListeners.values()) {
|
510 | listener();
|
511 | }
|
512 | }
|
513 | |
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 |
|
520 | transitionToState(oldStates, newState) {
|
521 | if (oldStates.indexOf(this.connectivityState) === -1) {
|
522 | return false;
|
523 | }
|
524 | this.trace(connectivity_state_1.ConnectivityState[this.connectivityState] +
|
525 | ' -> ' +
|
526 | connectivity_state_1.ConnectivityState[newState]);
|
527 | if (this.channelzEnabled) {
|
528 | this.channelzTrace.addTrace('CT_INFO', connectivity_state_1.ConnectivityState[this.connectivityState] + ' -> ' + connectivity_state_1.ConnectivityState[newState]);
|
529 | }
|
530 | const previousState = this.connectivityState;
|
531 | this.connectivityState = newState;
|
532 | switch (newState) {
|
533 | case connectivity_state_1.ConnectivityState.READY:
|
534 | this.stopBackoff();
|
535 | const session = this.session;
|
536 | session.socket.once('close', () => {
|
537 | if (this.session === session) {
|
538 | this.handleDisconnect();
|
539 | }
|
540 | });
|
541 | if (this.keepaliveWithoutCalls) {
|
542 | this.startKeepalivePings();
|
543 | }
|
544 | break;
|
545 | case connectivity_state_1.ConnectivityState.CONNECTING:
|
546 | this.startBackoff();
|
547 | this.startConnectingInternal();
|
548 | this.continueConnecting = false;
|
549 | break;
|
550 | case connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE:
|
551 | if (this.session) {
|
552 | this.session.close();
|
553 | }
|
554 | this.session = null;
|
555 | this.resetChannelzSocketInfo();
|
556 | this.stopKeepalivePings();
|
557 | |
558 |
|
559 |
|
560 | if (!this.backoffTimeout.isRunning()) {
|
561 | process.nextTick(() => {
|
562 | this.handleBackoffTimer();
|
563 | });
|
564 | }
|
565 | break;
|
566 | case connectivity_state_1.ConnectivityState.IDLE:
|
567 | if (this.session) {
|
568 | this.session.close();
|
569 | }
|
570 | this.session = null;
|
571 | this.resetChannelzSocketInfo();
|
572 | this.stopKeepalivePings();
|
573 | break;
|
574 | default:
|
575 | throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
|
576 | }
|
577 | |
578 |
|
579 | for (const listener of [...this.stateListeners]) {
|
580 | listener(this, previousState, newState);
|
581 | }
|
582 | return true;
|
583 | }
|
584 | |
585 |
|
586 |
|
587 |
|
588 | checkBothRefcounts() {
|
589 | |
590 |
|
591 | if (this.callRefcount === 0 && this.refcount === 0) {
|
592 | if (this.channelzEnabled) {
|
593 | this.channelzTrace.addTrace('CT_INFO', 'Shutting down');
|
594 | }
|
595 | this.transitionToState([connectivity_state_1.ConnectivityState.CONNECTING, connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.IDLE);
|
596 | if (this.channelzEnabled) {
|
597 | channelz_1.unregisterChannelzRef(this.channelzRef);
|
598 | }
|
599 | }
|
600 | }
|
601 | callRef() {
|
602 | this.refTrace('callRefcount ' +
|
603 | this.callRefcount +
|
604 | ' -> ' +
|
605 | (this.callRefcount + 1));
|
606 | if (this.callRefcount === 0) {
|
607 | if (this.session) {
|
608 | this.session.ref();
|
609 | }
|
610 | this.backoffTimeout.ref();
|
611 | if (!this.keepaliveWithoutCalls) {
|
612 | this.startKeepalivePings();
|
613 | }
|
614 | }
|
615 | this.callRefcount += 1;
|
616 | }
|
617 | callUnref() {
|
618 | this.refTrace('callRefcount ' +
|
619 | this.callRefcount +
|
620 | ' -> ' +
|
621 | (this.callRefcount - 1));
|
622 | this.callRefcount -= 1;
|
623 | if (this.callRefcount === 0) {
|
624 | if (this.session) {
|
625 | this.session.unref();
|
626 | }
|
627 | this.backoffTimeout.unref();
|
628 | if (!this.keepaliveWithoutCalls) {
|
629 | clearInterval(this.keepaliveIntervalId);
|
630 | }
|
631 | this.checkBothRefcounts();
|
632 | }
|
633 | }
|
634 | ref() {
|
635 | this.refTrace('refcount ' +
|
636 | this.refcount +
|
637 | ' -> ' +
|
638 | (this.refcount + 1));
|
639 | this.refcount += 1;
|
640 | }
|
641 | unref() {
|
642 | this.refTrace('refcount ' +
|
643 | this.refcount +
|
644 | ' -> ' +
|
645 | (this.refcount - 1));
|
646 | this.refcount -= 1;
|
647 | this.checkBothRefcounts();
|
648 | }
|
649 | unrefIfOneRef() {
|
650 | if (this.refcount === 1) {
|
651 | this.unref();
|
652 | return true;
|
653 | }
|
654 | return false;
|
655 | }
|
656 | |
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 | startCallStream(metadata, callStream, extraFilters) {
|
664 | const headers = metadata.toHttp2Headers();
|
665 | headers[HTTP2_HEADER_AUTHORITY] = callStream.getHost();
|
666 | headers[HTTP2_HEADER_USER_AGENT] = this.userAgent;
|
667 | headers[HTTP2_HEADER_CONTENT_TYPE] = 'application/grpc';
|
668 | headers[HTTP2_HEADER_METHOD] = 'POST';
|
669 | headers[HTTP2_HEADER_PATH] = callStream.getMethod();
|
670 | headers[HTTP2_HEADER_TE] = 'trailers';
|
671 | let http2Stream;
|
672 | |
673 |
|
674 |
|
675 |
|
676 |
|
677 |
|
678 |
|
679 |
|
680 | try {
|
681 | http2Stream = this.session.request(headers);
|
682 | }
|
683 | catch (e) {
|
684 | this.transitionToState([connectivity_state_1.ConnectivityState.READY], connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
|
685 | throw e;
|
686 | }
|
687 | let headersString = '';
|
688 | for (const header of Object.keys(headers)) {
|
689 | headersString += '\t\t' + header + ': ' + headers[header] + '\n';
|
690 | }
|
691 | logging.trace(constants_1.LogVerbosity.DEBUG, 'call_stream', 'Starting stream [' + callStream.getCallNumber() + '] on subchannel ' +
|
692 | '(' + this.channelzRef.id + ') ' +
|
693 | this.subchannelAddressString +
|
694 | ' with headers\n' +
|
695 | headersString);
|
696 | this.flowControlTrace('local window size: ' +
|
697 | this.session.state.localWindowSize +
|
698 | ' remote window size: ' +
|
699 | this.session.state.remoteWindowSize);
|
700 | const streamSession = this.session;
|
701 | this.internalsTrace('session.closed=' +
|
702 | streamSession.closed +
|
703 | ' session.destroyed=' +
|
704 | streamSession.destroyed +
|
705 | ' session.socket.destroyed=' +
|
706 | streamSession.socket.destroyed);
|
707 | let statsTracker;
|
708 | if (this.channelzEnabled) {
|
709 | this.callTracker.addCallStarted();
|
710 | callStream.addStatusWatcher(status => {
|
711 | if (status.code === constants_1.Status.OK) {
|
712 | this.callTracker.addCallSucceeded();
|
713 | }
|
714 | else {
|
715 | this.callTracker.addCallFailed();
|
716 | }
|
717 | });
|
718 | this.streamTracker.addCallStarted();
|
719 | callStream.addStreamEndWatcher(success => {
|
720 | if (streamSession === this.session) {
|
721 | if (success) {
|
722 | this.streamTracker.addCallSucceeded();
|
723 | }
|
724 | else {
|
725 | this.streamTracker.addCallFailed();
|
726 | }
|
727 | }
|
728 | });
|
729 | statsTracker = {
|
730 | addMessageSent: () => {
|
731 | this.messagesSent += 1;
|
732 | this.lastMessageSentTimestamp = new Date();
|
733 | },
|
734 | addMessageReceived: () => {
|
735 | this.messagesReceived += 1;
|
736 | }
|
737 | };
|
738 | }
|
739 | else {
|
740 | statsTracker = {
|
741 | addMessageSent: () => { },
|
742 | addMessageReceived: () => { }
|
743 | };
|
744 | }
|
745 | callStream.attachHttp2Stream(http2Stream, this, extraFilters, statsTracker);
|
746 | }
|
747 | |
748 |
|
749 |
|
750 |
|
751 |
|
752 |
|
753 | startConnecting() {
|
754 | |
755 |
|
756 |
|
757 |
|
758 | if (!this.transitionToState([connectivity_state_1.ConnectivityState.IDLE], connectivity_state_1.ConnectivityState.CONNECTING)) {
|
759 | if (this.connectivityState === connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE) {
|
760 | this.continueConnecting = true;
|
761 | }
|
762 | }
|
763 | }
|
764 | |
765 |
|
766 |
|
767 | getConnectivityState() {
|
768 | return this.connectivityState;
|
769 | }
|
770 | |
771 |
|
772 |
|
773 |
|
774 |
|
775 | addConnectivityStateListener(listener) {
|
776 | this.stateListeners.push(listener);
|
777 | }
|
778 | |
779 |
|
780 |
|
781 |
|
782 |
|
783 | removeConnectivityStateListener(listener) {
|
784 | const listenerIndex = this.stateListeners.indexOf(listener);
|
785 | if (listenerIndex > -1) {
|
786 | this.stateListeners.splice(listenerIndex, 1);
|
787 | }
|
788 | }
|
789 | addDisconnectListener(listener) {
|
790 | this.disconnectListeners.add(listener);
|
791 | }
|
792 | removeDisconnectListener(listener) {
|
793 | this.disconnectListeners.delete(listener);
|
794 | }
|
795 | |
796 |
|
797 |
|
798 | resetBackoff() {
|
799 | this.backoffTimeout.reset();
|
800 | this.transitionToState([connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE], connectivity_state_1.ConnectivityState.CONNECTING);
|
801 | }
|
802 | getAddress() {
|
803 | return this.subchannelAddressString;
|
804 | }
|
805 | getChannelzRef() {
|
806 | return this.channelzRef;
|
807 | }
|
808 | getRealSubchannel() {
|
809 | return this;
|
810 | }
|
811 | }
|
812 | exports.Subchannel = Subchannel;
|
813 |
|
\ | No newline at end of file |