UNPKG

5.5 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 LoadBalancingConfig,
22 createLoadBalancer,
23} from './load-balancer';
24import { SubchannelAddress } from './subchannel-address';
25import { ChannelOptions } from './channel-options';
26import { ConnectivityState } from './connectivity-state';
27import { Picker } from './picker';
28import { 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: LoadBalancingConfig | 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(private readonly channelControlHelper: ChannelControlHelper) {}
88
89 protected configUpdateRequiresNewPolicyInstance(
90 oldConfig: LoadBalancingConfig,
91 newConfig: LoadBalancingConfig
92 ): boolean {
93 return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName();
94 }
95
96 /**
97 * Prerequisites: lbConfig !== null and lbConfig.name is registered
98 * @param addressList
99 * @param lbConfig
100 * @param attributes
101 */
102 updateAddressList(
103 addressList: SubchannelAddress[],
104 lbConfig: LoadBalancingConfig,
105 attributes: { [key: string]: unknown }
106 ): void {
107 let childToUpdate: LoadBalancer;
108 if (
109 this.currentChild === null ||
110 this.latestConfig === null ||
111 this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig)
112 ) {
113 const newHelper = new this.ChildPolicyHelper(this);
114 const newChild = createLoadBalancer(lbConfig, newHelper)!;
115 newHelper.setChild(newChild);
116 if (this.currentChild === null) {
117 this.currentChild = newChild;
118 childToUpdate = this.currentChild;
119 } else {
120 if (this.pendingChild) {
121 this.pendingChild.destroy();
122 }
123 this.pendingChild = newChild;
124 childToUpdate = this.pendingChild;
125 }
126 } else {
127 if (this.pendingChild === null) {
128 childToUpdate = this.currentChild;
129 } else {
130 childToUpdate = this.pendingChild;
131 }
132 }
133 this.latestConfig = lbConfig;
134 childToUpdate.updateAddressList(addressList, lbConfig, attributes);
135 }
136 exitIdle(): void {
137 if (this.currentChild) {
138 this.currentChild.exitIdle();
139 if (this.pendingChild) {
140 this.pendingChild.exitIdle();
141 }
142 }
143 }
144 resetBackoff(): void {
145 if (this.currentChild) {
146 this.currentChild.resetBackoff();
147 if (this.pendingChild) {
148 this.pendingChild.resetBackoff();
149 }
150 }
151 }
152 destroy(): void {
153 /* Note: state updates are only propagated from the child balancer if that
154 * object is equal to this.currentChild or this.pendingChild. Since this
155 * function sets both of those to null, no further state updates will
156 * occur after this function returns. */
157 if (this.currentChild) {
158 this.currentChild.destroy();
159 this.currentChild = null;
160 }
161 if (this.pendingChild) {
162 this.pendingChild.destroy();
163 this.pendingChild = null;
164 }
165 }
166 getTypeName(): string {
167 return TYPE_NAME;
168 }
169}