diff options
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.ts | 403 |
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'; + } +} |
