1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import {
|
19 | LoadBalancer,
|
20 | ChannelControlHelper,
|
21 | TypedLoadBalancingConfig,
|
22 | createLoadBalancer,
|
23 | } from './load-balancer';
|
24 | import { Endpoint, SubchannelAddress } from './subchannel-address';
|
25 | import { ChannelOptions } from './channel-options';
|
26 | import { ConnectivityState } from './connectivity-state';
|
27 | import { Picker } from './picker';
|
28 | import type { ChannelRef, SubchannelRef } from './channelz';
|
29 | import { SubchannelInterface } from './subchannel-interface';
|
30 |
|
31 | const TYPE_NAME = 'child_load_balancer_helper';
|
32 |
|
33 | export 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 |
|
101 |
|
102 |
|
103 |
|
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 | |
157 |
|
158 |
|
159 |
|
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 | }
|