UNPKG

5.6 kBPlain TextView Raw
1/*
2 * Copyright 2020 gRPC authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18import {
19 LoadBalancer,
20 ChannelControlHelper,
21 TypedLoadBalancingConfig,
22 createLoadBalancer,
23} from './load-balancer';
24import { Endpoint, SubchannelAddress } from './subchannel-address';
25import { ChannelOptions } from './channel-options';
26import { ConnectivityState } from './connectivity-state';
27import { Picker } from './picker';
28import type { ChannelRef, SubchannelRef } from './channelz';
29import { SubchannelInterface } from './subchannel-interface';
30
31const TYPE_NAME = 'child_load_balancer_helper';
32
33export class ChildLoadBalancerHandler implements LoadBalancer {
34 private currentChild: LoadBalancer | null = null;
35 private pendingChild: LoadBalancer | null = null;
36 private latestConfig: TypedLoadBalancingConfig | null = null;
37
38 private ChildPolicyHelper = class {
39 private child: LoadBalancer | null = null;
40 constructor(private parent: ChildLoadBalancerHandler) {}
41 createSubchannel(
42 subchannelAddress: SubchannelAddress,
43 subchannelArgs: ChannelOptions
44 ): SubchannelInterface {
45 return this.parent.channelControlHelper.createSubchannel(
46 subchannelAddress,
47 subchannelArgs
48 );
49 }
50 updateState(connectivityState: ConnectivityState, picker: Picker): void {
51 if (this.calledByPendingChild()) {
52 if (connectivityState === ConnectivityState.CONNECTING) {
53 return;
54 }
55 this.parent.currentChild?.destroy();
56 this.parent.currentChild = this.parent.pendingChild;
57 this.parent.pendingChild = null;
58 } else if (!this.calledByCurrentChild()) {
59 return;
60 }
61 this.parent.channelControlHelper.updateState(connectivityState, picker);
62 }
63 requestReresolution(): void {
64 const latestChild = this.parent.pendingChild ?? this.parent.currentChild;
65 if (this.child === latestChild) {
66 this.parent.channelControlHelper.requestReresolution();
67 }
68 }
69 setChild(newChild: LoadBalancer) {
70 this.child = newChild;
71 }
72 addChannelzChild(child: ChannelRef | SubchannelRef) {
73 this.parent.channelControlHelper.addChannelzChild(child);
74 }
75 removeChannelzChild(child: ChannelRef | SubchannelRef) {
76 this.parent.channelControlHelper.removeChannelzChild(child);
77 }
78
79 private calledByPendingChild(): boolean {
80 return this.child === this.parent.pendingChild;
81 }
82 private calledByCurrentChild(): boolean {
83 return this.child === this.parent.currentChild;
84 }
85 };
86
87 constructor(
88 private readonly channelControlHelper: ChannelControlHelper,
89 private readonly options: ChannelOptions
90 ) {}
91
92 protected configUpdateRequiresNewPolicyInstance(
93 oldConfig: TypedLoadBalancingConfig,
94 newConfig: TypedLoadBalancingConfig
95 ): boolean {
96 return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName();
97 }
98
99 /**
100 * Prerequisites: lbConfig !== null and lbConfig.name is registered
101 * @param endpointList
102 * @param lbConfig
103 * @param attributes
104 */
105 updateAddressList(
106 endpointList: Endpoint[],
107 lbConfig: TypedLoadBalancingConfig,
108 attributes: { [key: string]: unknown }
109 ): void {
110 let childToUpdate: LoadBalancer;
111 if (
112 this.currentChild === null ||
113 this.latestConfig === null ||
114 this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig)
115 ) {
116 const newHelper = new this.ChildPolicyHelper(this);
117 const newChild = createLoadBalancer(lbConfig, newHelper, this.options)!;
118 newHelper.setChild(newChild);
119 if (this.currentChild === null) {
120 this.currentChild = newChild;
121 childToUpdate = this.currentChild;
122 } else {
123 if (this.pendingChild) {
124 this.pendingChild.destroy();
125 }
126 this.pendingChild = newChild;
127 childToUpdate = this.pendingChild;
128 }
129 } else {
130 if (this.pendingChild === null) {
131 childToUpdate = this.currentChild;
132 } else {
133 childToUpdate = this.pendingChild;
134 }
135 }
136 this.latestConfig = lbConfig;
137 childToUpdate.updateAddressList(endpointList, lbConfig, attributes);
138 }
139 exitIdle(): void {
140 if (this.currentChild) {
141 this.currentChild.exitIdle();
142 if (this.pendingChild) {
143 this.pendingChild.exitIdle();
144 }
145 }
146 }
147 resetBackoff(): void {
148 if (this.currentChild) {
149 this.currentChild.resetBackoff();
150 if (this.pendingChild) {
151 this.pendingChild.resetBackoff();
152 }
153 }
154 }
155 destroy(): void {
156 /* Note: state updates are only propagated from the child balancer if that
157 * object is equal to this.currentChild or this.pendingChild. Since this
158 * function sets both of those to null, no further state updates will
159 * occur after this function returns. */
160 if (this.currentChild) {
161 this.currentChild.destroy();
162 this.currentChild = null;
163 }
164 if (this.pendingChild) {
165 this.pendingChild.destroy();
166 this.pendingChild = null;
167 }
168 }
169 getTypeName(): string {
170 return TYPE_NAME;
171 }
172}