Initial Commit
This commit is contained in:
482
node_modules/@grpc/grpc-js/src/subchannel.ts
generated
vendored
Normal file
482
node_modules/@grpc/grpc-js/src/subchannel.ts
generated
vendored
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user