summaryrefslogtreecommitdiff
path: root/frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts')
-rw-r--r--frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts403
1 files changed, 403 insertions, 0 deletions
diff --git a/frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts b/frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts
new file mode 100644
index 0000000..ceec4b5
--- /dev/null
+++ b/frontend-old/node_modules/@grpc/grpc-js/src/resolving-load-balancer.ts
@@ -0,0 +1,403 @@
+/*
+ * 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 {
+ ChannelControlHelper,
+ LoadBalancer,
+ LoadBalancingConfig,
+ getFirstUsableConfig,
+} from './load-balancer';
+import {
+ MethodConfig,
+ ServiceConfig,
+ validateServiceConfig,
+} from './service-config';
+import { ConnectivityState } from './connectivity-state';
+import { ConfigSelector, createResolver, Resolver } from './resolver';
+import { ServiceError } from './call';
+import { Picker, UnavailablePicker, QueuePicker } from './picker';
+import { BackoffOptions, BackoffTimeout } from './backoff-timeout';
+import { Status } from './constants';
+import { StatusObject } from './call-interface';
+import { Metadata } from './metadata';
+import * as logging from './logging';
+import { LogVerbosity } from './constants';
+import { SubchannelAddress } from './subchannel-address';
+import { GrpcUri, uriToString } from './uri-parser';
+import { ChildLoadBalancerHandler } from './load-balancer-child-handler';
+import { ChannelOptions } from './channel-options';
+
+const TRACER_NAME = 'resolving_load_balancer';
+
+function trace(text: string): void {
+ logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
+}
+
+type NameMatchLevel = 'EMPTY' | 'SERVICE' | 'SERVICE_AND_METHOD';
+
+/**
+ * Name match levels in order from most to least specific. This is the order in
+ * which searches will be performed.
+ */
+const NAME_MATCH_LEVEL_ORDER: NameMatchLevel[] = [
+ 'SERVICE_AND_METHOD',
+ 'SERVICE',
+ 'EMPTY',
+];
+
+function hasMatchingName(
+ service: string,
+ method: string,
+ methodConfig: MethodConfig,
+ matchLevel: NameMatchLevel
+): boolean {
+ for (const name of methodConfig.name) {
+ switch (matchLevel) {
+ case 'EMPTY':
+ if (!name.service && !name.method) {
+ return true;
+ }
+ break;
+ case 'SERVICE':
+ if (name.service === service && !name.method) {
+ return true;
+ }
+ break;
+ case 'SERVICE_AND_METHOD':
+ if (name.service === service && name.method === method) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function findMatchingConfig(
+ service: string,
+ method: string,
+ methodConfigs: MethodConfig[],
+ matchLevel: NameMatchLevel
+): MethodConfig | null {
+ for (const config of methodConfigs) {
+ if (hasMatchingName(service, method, config, matchLevel)) {
+ return config;
+ }
+ }
+ return null;
+}
+
+function getDefaultConfigSelector(
+ serviceConfig: ServiceConfig | null
+): ConfigSelector {
+ return function defaultConfigSelector(
+ methodName: string,
+ metadata: Metadata
+ ) {
+ const splitName = methodName.split('/').filter(x => x.length > 0);
+ const service = splitName[0] ?? '';
+ const method = splitName[1] ?? '';
+ if (serviceConfig && serviceConfig.methodConfig) {
+ /* Check for the following in order, and return the first method
+ * config that matches:
+ * 1. A name that exactly matches the service and method
+ * 2. A name with no method set that matches the service
+ * 3. An empty name
+ */
+ for (const matchLevel of NAME_MATCH_LEVEL_ORDER) {
+ const matchingConfig = findMatchingConfig(
+ service,
+ method,
+ serviceConfig.methodConfig,
+ matchLevel
+ );
+ if (matchingConfig) {
+ return {
+ methodConfig: matchingConfig,
+ pickInformation: {},
+ status: Status.OK,
+ dynamicFilterFactories: [],
+ };
+ }
+ }
+ }
+ return {
+ methodConfig: { name: [] },
+ pickInformation: {},
+ status: Status.OK,
+ dynamicFilterFactories: [],
+ };
+ };
+}
+
+export interface ResolutionCallback {
+ (serviceConfig: ServiceConfig, configSelector: ConfigSelector): void;
+}
+
+export interface ResolutionFailureCallback {
+ (status: StatusObject): void;
+}
+
+export class ResolvingLoadBalancer implements LoadBalancer {
+ /**
+ * The resolver class constructed for the target address.
+ */
+ private readonly innerResolver: Resolver;
+
+ private readonly childLoadBalancer: ChildLoadBalancerHandler;
+ private latestChildState: ConnectivityState = ConnectivityState.IDLE;
+ private latestChildPicker: Picker = new QueuePicker(this);
+ /**
+ * This resolving load balancer's current connectivity state.
+ */
+ private currentState: ConnectivityState = ConnectivityState.IDLE;
+ private readonly defaultServiceConfig: ServiceConfig;
+ /**
+ * The service config object from the last successful resolution, if
+ * available. A value of null indicates that we have not yet received a valid
+ * service config from the resolver.
+ */
+ private previousServiceConfig: ServiceConfig | null = null;
+
+ /**
+ * The backoff timer for handling name resolution failures.
+ */
+ private readonly backoffTimeout: BackoffTimeout;
+
+ /**
+ * Indicates whether we should attempt to resolve again after the backoff
+ * timer runs out.
+ */
+ private continueResolving = false;
+
+ /**
+ * Wrapper class that behaves like a `LoadBalancer` and also handles name
+ * resolution internally.
+ * @param target The address of the backend to connect to.
+ * @param channelControlHelper `ChannelControlHelper` instance provided by
+ * this load balancer's owner.
+ * @param defaultServiceConfig The default service configuration to be used
+ * if none is provided by the name resolver. A `null` value indicates
+ * that the default behavior should be the default unconfigured behavior.
+ * In practice, that means using the "pick first" load balancer
+ * implmentation
+ */
+ constructor(
+ private readonly target: GrpcUri,
+ private readonly channelControlHelper: ChannelControlHelper,
+ channelOptions: ChannelOptions,
+ private readonly onSuccessfulResolution: ResolutionCallback,
+ private readonly onFailedResolution: ResolutionFailureCallback
+ ) {
+ if (channelOptions['grpc.service_config']) {
+ this.defaultServiceConfig = validateServiceConfig(
+ JSON.parse(channelOptions['grpc.service_config']!)
+ );
+ } else {
+ this.defaultServiceConfig = {
+ loadBalancingConfig: [],
+ methodConfig: [],
+ };
+ }
+ this.updateState(ConnectivityState.IDLE, new QueuePicker(this));
+ this.childLoadBalancer = new ChildLoadBalancerHandler({
+ createSubchannel:
+ channelControlHelper.createSubchannel.bind(channelControlHelper),
+ requestReresolution: () => {
+ /* If the backoffTimeout is running, we're still backing off from
+ * making resolve requests, so we shouldn't make another one here.
+ * In that case, the backoff timer callback will call
+ * updateResolution */
+ if (this.backoffTimeout.isRunning()) {
+ trace('requestReresolution delayed by backoff timer until ' + this.backoffTimeout.getEndTime().toISOString());
+ this.continueResolving = true;
+ } else {
+ this.updateResolution();
+ }
+ },
+ updateState: (newState: ConnectivityState, picker: Picker) => {
+ this.latestChildState = newState;
+ this.latestChildPicker = picker;
+ this.updateState(newState, picker);
+ },
+ addChannelzChild:
+ channelControlHelper.addChannelzChild.bind(channelControlHelper),
+ removeChannelzChild:
+ channelControlHelper.removeChannelzChild.bind(channelControlHelper),
+ });
+ this.innerResolver = createResolver(
+ target,
+ {
+ onSuccessfulResolution: (
+ addressList: SubchannelAddress[],
+ serviceConfig: ServiceConfig | null,
+ serviceConfigError: ServiceError | null,
+ configSelector: ConfigSelector | null,
+ attributes: { [key: string]: unknown }
+ ) => {
+ this.backoffTimeout.stop();
+ this.backoffTimeout.reset();
+ let workingServiceConfig: ServiceConfig | null = null;
+ /* This first group of conditionals implements the algorithm described
+ * in https://github.com/grpc/proposal/blob/master/A21-service-config-error-handling.md
+ * in the section called "Behavior on receiving a new gRPC Config".
+ */
+ if (serviceConfig === null) {
+ // Step 4 and 5
+ if (serviceConfigError === null) {
+ // Step 5
+ this.previousServiceConfig = null;
+ workingServiceConfig = this.defaultServiceConfig;
+ } else {
+ // Step 4
+ if (this.previousServiceConfig === null) {
+ // Step 4.ii
+ this.handleResolutionFailure(serviceConfigError);
+ } else {
+ // Step 4.i
+ workingServiceConfig = this.previousServiceConfig;
+ }
+ }
+ } else {
+ // Step 3
+ workingServiceConfig = serviceConfig;
+ this.previousServiceConfig = serviceConfig;
+ }
+ const workingConfigList =
+ workingServiceConfig?.loadBalancingConfig ?? [];
+ const loadBalancingConfig = getFirstUsableConfig(
+ workingConfigList,
+ true
+ );
+ if (loadBalancingConfig === null) {
+ // There were load balancing configs but none are supported. This counts as a resolution failure
+ this.handleResolutionFailure({
+ code: Status.UNAVAILABLE,
+ details:
+ 'All load balancer options in service config are not compatible',
+ metadata: new Metadata(),
+ });
+ return;
+ }
+ this.childLoadBalancer.updateAddressList(
+ addressList,
+ loadBalancingConfig,
+ attributes
+ );
+ const finalServiceConfig =
+ workingServiceConfig ?? this.defaultServiceConfig;
+ this.onSuccessfulResolution(
+ finalServiceConfig,
+ configSelector ?? getDefaultConfigSelector(finalServiceConfig)
+ );
+ },
+ onError: (error: StatusObject) => {
+ this.handleResolutionFailure(error);
+ },
+ },
+ channelOptions
+ );
+ const backoffOptions: BackoffOptions = {
+ initialDelay: channelOptions['grpc.initial_reconnect_backoff_ms'],
+ maxDelay: channelOptions['grpc.max_reconnect_backoff_ms'],
+ };
+ this.backoffTimeout = new BackoffTimeout(() => {
+ if (this.continueResolving) {
+ this.updateResolution();
+ this.continueResolving = false;
+ } else {
+ this.updateState(this.latestChildState, this.latestChildPicker);
+ }
+ }, backoffOptions);
+ this.backoffTimeout.unref();
+ }
+
+ private updateResolution() {
+ this.innerResolver.updateResolution();
+ if (this.currentState === ConnectivityState.IDLE) {
+ this.updateState(ConnectivityState.CONNECTING, new QueuePicker(this));
+ }
+ this.backoffTimeout.runOnce();
+ }
+
+ private updateState(connectivityState: ConnectivityState, picker: Picker) {
+ trace(
+ uriToString(this.target) +
+ ' ' +
+ ConnectivityState[this.currentState] +
+ ' -> ' +
+ ConnectivityState[connectivityState]
+ );
+ // Ensure that this.exitIdle() is called by the picker
+ if (connectivityState === ConnectivityState.IDLE) {
+ picker = new QueuePicker(this);
+ }
+ this.currentState = connectivityState;
+ this.channelControlHelper.updateState(connectivityState, picker);
+ }
+
+ private handleResolutionFailure(error: StatusObject) {
+ if (this.latestChildState === ConnectivityState.IDLE) {
+ this.updateState(
+ ConnectivityState.TRANSIENT_FAILURE,
+ new UnavailablePicker(error)
+ );
+ this.onFailedResolution(error);
+ }
+ }
+
+ exitIdle() {
+ if (
+ this.currentState === ConnectivityState.IDLE ||
+ this.currentState === ConnectivityState.TRANSIENT_FAILURE
+ ) {
+ if (this.backoffTimeout.isRunning()) {
+ this.continueResolving = true;
+ } else {
+ this.updateResolution();
+ }
+ }
+ this.childLoadBalancer.exitIdle();
+ }
+
+ updateAddressList(
+ addressList: SubchannelAddress[],
+ lbConfig: LoadBalancingConfig | null
+ ): never {
+ throw new Error('updateAddressList not supported on ResolvingLoadBalancer');
+ }
+
+ resetBackoff() {
+ this.backoffTimeout.reset();
+ this.childLoadBalancer.resetBackoff();
+ }
+
+ destroy() {
+ this.childLoadBalancer.destroy();
+ this.innerResolver.destroy();
+ this.backoffTimeout.reset();
+ this.backoffTimeout.stop();
+ this.latestChildState = ConnectivityState.IDLE;
+ this.latestChildPicker = new QueuePicker(this);
+ this.currentState = ConnectivityState.IDLE;
+ this.previousServiceConfig = null;
+ this.continueResolving = false;
+ }
+
+ getTypeName() {
+ return 'resolving_load_balancer';
+ }
+}