summaryrefslogtreecommitdiff
path: root/frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts
diff options
context:
space:
mode:
authoraltaf-creator <dev@altafcreator.com>2025-11-09 11:15:19 +0800
committeraltaf-creator <dev@altafcreator.com>2025-11-09 11:15:19 +0800
commit8eff962cab608341a6f2fedc640a0e32d96f26e2 (patch)
tree05534d1a720ddc3691d346c69b4972555820a061 /frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts
pain
Diffstat (limited to 'frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts')
-rw-r--r--frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts482
1 files changed, 482 insertions, 0 deletions
diff --git a/frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts b/frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts
new file mode 100644
index 0000000..eff7509
--- /dev/null
+++ b/frontend-old/node_modules/@grpc/grpc-js/src/subchannel.ts
@@ -0,0 +1,482 @@
+/*
+ * Copyright 2019 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 { ChannelCredentials } from './channel-credentials';
+import { Metadata } from './metadata';
+import { ChannelOptions } from './channel-options';
+import { ConnectivityState } from './connectivity-state';
+import { BackoffTimeout, BackoffOptions } from './backoff-timeout';
+import * as logging from './logging';
+import { LogVerbosity, Status } from './constants';
+import { GrpcUri, uriToString } from './uri-parser';
+import {
+ SubchannelAddress,
+ subchannelAddressToString,
+} from './subchannel-address';
+import {
+ SubchannelRef,
+ ChannelzTrace,
+ ChannelzChildrenTracker,
+ SubchannelInfo,
+ registerChannelzSubchannel,
+ ChannelzCallTracker,
+ unregisterChannelzRef,
+} from './channelz';
+import {
+ ConnectivityStateListener,
+ SubchannelInterface,
+} from './subchannel-interface';
+import { SubchannelCallInterceptingListener } from './subchannel-call';
+import { SubchannelCall } from './subchannel-call';
+import { CallEventTracker, SubchannelConnector, Transport } from './transport';
+
+const TRACER_NAME = 'subchannel';
+
+/* setInterval and setTimeout only accept signed 32 bit integers. JS doesn't
+ * have a constant for the max signed 32 bit integer, so this is a simple way
+ * to calculate it */
+const KEEPALIVE_MAX_TIME_MS = ~(1 << 31);
+
+export class Subchannel {
+ /**
+ * The subchannel's current connectivity state. Invariant: `session` === `null`
+ * if and only if `connectivityState` is IDLE or TRANSIENT_FAILURE.
+ */
+ private connectivityState: ConnectivityState = ConnectivityState.IDLE;
+ /**
+ * The underlying http2 session used to make requests.
+ */
+ private transport: Transport | null = null;
+ /**
+ * Indicates that the subchannel should transition from TRANSIENT_FAILURE to
+ * CONNECTING instead of IDLE when the backoff timeout ends.
+ */
+ private continueConnecting = false;
+ /**
+ * A list of listener functions that will be called whenever the connectivity
+ * state changes. Will be modified by `addConnectivityStateListener` and
+ * `removeConnectivityStateListener`
+ */
+ private stateListeners: Set<ConnectivityStateListener> = new Set();
+
+ private backoffTimeout: BackoffTimeout;
+
+ private keepaliveTime: number;
+ /**
+ * Tracks channels and subchannel pools with references to this subchannel
+ */
+ private refcount = 0;
+
+ /**
+ * A string representation of the subchannel address, for logging/tracing
+ */
+ private subchannelAddressString: string;
+
+ // Channelz info
+ private readonly channelzEnabled: boolean = true;
+ private channelzRef: SubchannelRef;
+ private channelzTrace: ChannelzTrace;
+ private callTracker = new ChannelzCallTracker();
+ private childrenTracker = new ChannelzChildrenTracker();
+
+ // Channelz socket info
+ private streamTracker = new ChannelzCallTracker();
+
+ /**
+ * A class representing a connection to a single backend.
+ * @param channelTarget The target string for the channel as a whole
+ * @param subchannelAddress The address for the backend that this subchannel
+ * will connect to
+ * @param options The channel options, plus any specific subchannel options
+ * for this subchannel
+ * @param credentials The channel credentials used to establish this
+ * connection
+ */
+ constructor(
+ private channelTarget: GrpcUri,
+ private subchannelAddress: SubchannelAddress,
+ private options: ChannelOptions,
+ private credentials: ChannelCredentials,
+ private connector: SubchannelConnector
+ ) {
+ const backoffOptions: BackoffOptions = {
+ initialDelay: options['grpc.initial_reconnect_backoff_ms'],
+ maxDelay: options['grpc.max_reconnect_backoff_ms'],
+ };
+ this.backoffTimeout = new BackoffTimeout(() => {
+ this.handleBackoffTimer();
+ }, backoffOptions);
+ this.backoffTimeout.unref();
+ this.subchannelAddressString = subchannelAddressToString(subchannelAddress);
+
+ this.keepaliveTime = options['grpc.keepalive_time_ms'] ?? -1;
+
+ if (options['grpc.enable_channelz'] === 0) {
+ this.channelzEnabled = false;
+ }
+ this.channelzTrace = new ChannelzTrace();
+ this.channelzRef = registerChannelzSubchannel(
+ this.subchannelAddressString,
+ () => this.getChannelzInfo(),
+ this.channelzEnabled
+ );
+ if (this.channelzEnabled) {
+ this.channelzTrace.addTrace('CT_INFO', 'Subchannel created');
+ }
+ this.trace(
+ 'Subchannel constructed with options ' +
+ JSON.stringify(options, undefined, 2)
+ );
+ }
+
+ private getChannelzInfo(): SubchannelInfo {
+ return {
+ state: this.connectivityState,
+ trace: this.channelzTrace,
+ callTracker: this.callTracker,
+ children: this.childrenTracker.getChildLists(),
+ target: this.subchannelAddressString,
+ };
+ }
+
+ private trace(text: string): void {
+ logging.trace(
+ LogVerbosity.DEBUG,
+ TRACER_NAME,
+ '(' +
+ this.channelzRef.id +
+ ') ' +
+ this.subchannelAddressString +
+ ' ' +
+ text
+ );
+ }
+
+ private refTrace(text: string): void {
+ logging.trace(
+ LogVerbosity.DEBUG,
+ 'subchannel_refcount',
+ '(' +
+ this.channelzRef.id +
+ ') ' +
+ this.subchannelAddressString +
+ ' ' +
+ text
+ );
+ }
+
+ private handleBackoffTimer() {
+ if (this.continueConnecting) {
+ this.transitionToState(
+ [ConnectivityState.TRANSIENT_FAILURE],
+ ConnectivityState.CONNECTING
+ );
+ } else {
+ this.transitionToState(
+ [ConnectivityState.TRANSIENT_FAILURE],
+ ConnectivityState.IDLE
+ );
+ }
+ }
+
+ /**
+ * Start a backoff timer with the current nextBackoff timeout
+ */
+ private startBackoff() {
+ this.backoffTimeout.runOnce();
+ }
+
+ private stopBackoff() {
+ this.backoffTimeout.stop();
+ this.backoffTimeout.reset();
+ }
+
+ private startConnectingInternal() {
+ let options = this.options;
+ if (options['grpc.keepalive_time_ms']) {
+ const adjustedKeepaliveTime = Math.min(
+ this.keepaliveTime,
+ KEEPALIVE_MAX_TIME_MS
+ );
+ options = { ...options, 'grpc.keepalive_time_ms': adjustedKeepaliveTime };
+ }
+ this.connector
+ .connect(this.subchannelAddress, this.credentials, options)
+ .then(
+ transport => {
+ if (
+ this.transitionToState(
+ [ConnectivityState.CONNECTING],
+ ConnectivityState.READY
+ )
+ ) {
+ this.transport = transport;
+ if (this.channelzEnabled) {
+ this.childrenTracker.refChild(transport.getChannelzRef());
+ }
+ transport.addDisconnectListener(tooManyPings => {
+ this.transitionToState(
+ [ConnectivityState.READY],
+ ConnectivityState.IDLE
+ );
+ if (tooManyPings && this.keepaliveTime > 0) {
+ this.keepaliveTime *= 2;
+ logging.log(
+ LogVerbosity.ERROR,
+ `Connection to ${uriToString(this.channelTarget)} at ${
+ this.subchannelAddressString
+ } rejected by server because of excess pings. Increasing ping interval to ${
+ this.keepaliveTime
+ } ms`
+ );
+ }
+ });
+ } else {
+ /* If we can't transition from CONNECTING to READY here, we will
+ * not be using this transport, so release its resources. */
+ transport.shutdown();
+ }
+ },
+ error => {
+ this.transitionToState(
+ [ConnectivityState.CONNECTING],
+ ConnectivityState.TRANSIENT_FAILURE,
+ `${error}`
+ );
+ }
+ );
+ }
+
+ /**
+ * Initiate a state transition from any element of oldStates to the new
+ * state. If the current connectivityState is not in oldStates, do nothing.
+ * @param oldStates The set of states to transition from
+ * @param newState The state to transition to
+ * @returns True if the state changed, false otherwise
+ */
+ private transitionToState(
+ oldStates: ConnectivityState[],
+ newState: ConnectivityState,
+ errorMessage?: string
+ ): boolean {
+ if (oldStates.indexOf(this.connectivityState) === -1) {
+ return false;
+ }
+ this.trace(
+ ConnectivityState[this.connectivityState] +
+ ' -> ' +
+ ConnectivityState[newState]
+ );
+ if (this.channelzEnabled) {
+ this.channelzTrace.addTrace(
+ 'CT_INFO',
+ 'Connectivity state change to ' + ConnectivityState[newState]
+ );
+ }
+ const previousState = this.connectivityState;
+ this.connectivityState = newState;
+ switch (newState) {
+ case ConnectivityState.READY:
+ this.stopBackoff();
+ break;
+ case ConnectivityState.CONNECTING:
+ this.startBackoff();
+ this.startConnectingInternal();
+ this.continueConnecting = false;
+ break;
+ case ConnectivityState.TRANSIENT_FAILURE:
+ if (this.channelzEnabled && this.transport) {
+ this.childrenTracker.unrefChild(this.transport.getChannelzRef());
+ }
+ this.transport?.shutdown();
+ this.transport = null;
+ /* If the backoff timer has already ended by the time we get to the
+ * TRANSIENT_FAILURE state, we want to immediately transition out of
+ * TRANSIENT_FAILURE as though the backoff timer is ending right now */
+ if (!this.backoffTimeout.isRunning()) {
+ process.nextTick(() => {
+ this.handleBackoffTimer();
+ });
+ }
+ break;
+ case ConnectivityState.IDLE:
+ if (this.channelzEnabled && this.transport) {
+ this.childrenTracker.unrefChild(this.transport.getChannelzRef());
+ }
+ this.transport?.shutdown();
+ this.transport = null;
+ break;
+ default:
+ throw new Error(`Invalid state: unknown ConnectivityState ${newState}`);
+ }
+ for (const listener of this.stateListeners) {
+ listener(this, previousState, newState, this.keepaliveTime, errorMessage);
+ }
+ return true;
+ }
+
+ ref() {
+ this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount + 1));
+ this.refcount += 1;
+ }
+
+ unref() {
+ this.refTrace('refcount ' + this.refcount + ' -> ' + (this.refcount - 1));
+ this.refcount -= 1;
+ if (this.refcount === 0) {
+ if (this.channelzEnabled) {
+ this.channelzTrace.addTrace('CT_INFO', 'Shutting down');
+ }
+ if (this.channelzEnabled) {
+ unregisterChannelzRef(this.channelzRef);
+ }
+ process.nextTick(() => {
+ this.transitionToState(
+ [ConnectivityState.CONNECTING, ConnectivityState.READY],
+ ConnectivityState.IDLE
+ );
+ });
+ }
+ }
+
+ unrefIfOneRef(): boolean {
+ if (this.refcount === 1) {
+ this.unref();
+ return true;
+ }
+ return false;
+ }
+
+ createCall(
+ metadata: Metadata,
+ host: string,
+ method: string,
+ listener: SubchannelCallInterceptingListener
+ ): SubchannelCall {
+ if (!this.transport) {
+ throw new Error('Cannot create call, subchannel not READY');
+ }
+ let statsTracker: Partial<CallEventTracker>;
+ if (this.channelzEnabled) {
+ this.callTracker.addCallStarted();
+ this.streamTracker.addCallStarted();
+ statsTracker = {
+ onCallEnd: status => {
+ if (status.code === Status.OK) {
+ this.callTracker.addCallSucceeded();
+ } else {
+ this.callTracker.addCallFailed();
+ }
+ },
+ };
+ } else {
+ statsTracker = {};
+ }
+ return this.transport.createCall(
+ metadata,
+ host,
+ method,
+ listener,
+ statsTracker
+ );
+ }
+
+ /**
+ * If the subchannel is currently IDLE, start connecting and switch to the
+ * CONNECTING state. If the subchannel is current in TRANSIENT_FAILURE,
+ * the next time it would transition to IDLE, start connecting again instead.
+ * Otherwise, do nothing.
+ */
+ startConnecting() {
+ process.nextTick(() => {
+ /* First, try to transition from IDLE to connecting. If that doesn't happen
+ * because the state is not currently IDLE, check if it is
+ * TRANSIENT_FAILURE, and if so indicate that it should go back to
+ * connecting after the backoff timer ends. Otherwise do nothing */
+ if (
+ !this.transitionToState(
+ [ConnectivityState.IDLE],
+ ConnectivityState.CONNECTING
+ )
+ ) {
+ if (this.connectivityState === ConnectivityState.TRANSIENT_FAILURE) {
+ this.continueConnecting = true;
+ }
+ }
+ });
+ }
+
+ /**
+ * Get the subchannel's current connectivity state.
+ */
+ getConnectivityState() {
+ return this.connectivityState;
+ }
+
+ /**
+ * Add a listener function to be called whenever the subchannel's
+ * connectivity state changes.
+ * @param listener
+ */
+ addConnectivityStateListener(listener: ConnectivityStateListener) {
+ this.stateListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener previously added with `addConnectivityStateListener`
+ * @param listener A reference to a function previously passed to
+ * `addConnectivityStateListener`
+ */
+ removeConnectivityStateListener(listener: ConnectivityStateListener) {
+ this.stateListeners.delete(listener);
+ }
+
+ /**
+ * Reset the backoff timeout, and immediately start connecting if in backoff.
+ */
+ resetBackoff() {
+ process.nextTick(() => {
+ this.backoffTimeout.reset();
+ this.transitionToState(
+ [ConnectivityState.TRANSIENT_FAILURE],
+ ConnectivityState.CONNECTING
+ );
+ });
+ }
+
+ getAddress(): string {
+ return this.subchannelAddressString;
+ }
+
+ getChannelzRef(): SubchannelRef {
+ return this.channelzRef;
+ }
+
+ getRealSubchannel(): this {
+ return this;
+ }
+
+ realSubchannelEquals(other: SubchannelInterface): boolean {
+ return other.getRealSubchannel() === this;
+ }
+
+ throttleKeepalive(newKeepaliveTime: number) {
+ if (newKeepaliveTime > this.keepaliveTime) {
+ this.keepaliveTime = newKeepaliveTime;
+ }
+ }
+}