Skip to content

Commit f309912

Browse files
authored
Merge pull request #1406 from murgatroid99/grpc-js_priority_load_balancer
grpc-js: Add ChildLoadBalancerHandler and use it for refactoring
2 parents 075a75b + 8ad1f82 commit f309912

File tree

6 files changed

+303
-284
lines changed

6 files changed

+303
-284
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
18+
import {
19+
LoadBalancer,
20+
ChannelControlHelper,
21+
createLoadBalancer,
22+
} from './load-balancer';
23+
import { SubchannelAddress, Subchannel } from './subchannel';
24+
import { LoadBalancingConfig } from './load-balancing-config';
25+
import { ChannelOptions } from './channel-options';
26+
import { ConnectivityState } from './channel';
27+
import { Picker } from './picker';
28+
29+
const TYPE_NAME = 'child_load_balancer_helper';
30+
31+
export class ChildLoadBalancerHandler implements LoadBalancer {
32+
private currentChild: LoadBalancer | null = null;
33+
private pendingChild: LoadBalancer | null = null;
34+
35+
private ChildPolicyHelper = class {
36+
private child: LoadBalancer | null = null;
37+
constructor(private parent: ChildLoadBalancerHandler) {}
38+
createSubchannel(
39+
subchannelAddress: SubchannelAddress,
40+
subchannelArgs: ChannelOptions
41+
): Subchannel {
42+
return this.parent.channelControlHelper.createSubchannel(
43+
subchannelAddress,
44+
subchannelArgs
45+
);
46+
}
47+
updateState(connectivityState: ConnectivityState, picker: Picker): void {
48+
if (this.calledByPendingChild()) {
49+
if (connectivityState !== ConnectivityState.READY) {
50+
return;
51+
}
52+
this.parent.currentChild?.destroy();
53+
this.parent.currentChild = this.parent.pendingChild;
54+
this.parent.pendingChild = null;
55+
} else if (!this.calledByCurrentChild()) {
56+
return;
57+
}
58+
this.parent.channelControlHelper.updateState(connectivityState, picker);
59+
}
60+
requestReresolution(): void {
61+
const latestChild = this.parent.pendingChild ?? this.parent.currentChild;
62+
if (this.child === latestChild) {
63+
this.parent.channelControlHelper.requestReresolution();
64+
}
65+
}
66+
setChild(newChild: LoadBalancer) {
67+
this.child = newChild;
68+
}
69+
private calledByPendingChild(): boolean {
70+
return this.child === this.parent.pendingChild;
71+
}
72+
private calledByCurrentChild(): boolean {
73+
return this.child === this.parent.currentChild;
74+
}
75+
};
76+
77+
constructor(private readonly channelControlHelper: ChannelControlHelper) {}
78+
79+
/**
80+
* Prerequisites: lbConfig !== null and lbConfig.name is registered
81+
* @param addressList
82+
* @param lbConfig
83+
* @param attributes
84+
*/
85+
updateAddressList(
86+
addressList: SubchannelAddress[],
87+
lbConfig: LoadBalancingConfig,
88+
attributes: { [key: string]: unknown }
89+
): void {
90+
let childToUpdate: LoadBalancer;
91+
if (
92+
this.currentChild === null ||
93+
this.currentChild.getTypeName() !== lbConfig.name
94+
) {
95+
const newHelper = new this.ChildPolicyHelper(this);
96+
const newChild = createLoadBalancer(lbConfig.name, newHelper)!;
97+
newHelper.setChild(newChild);
98+
if (this.currentChild === null) {
99+
this.currentChild = newChild;
100+
childToUpdate = this.currentChild;
101+
} else {
102+
if (this.pendingChild) {
103+
this.pendingChild.destroy();
104+
}
105+
this.pendingChild = newChild;
106+
childToUpdate = this.pendingChild;
107+
}
108+
} else {
109+
if (this.pendingChild === null) {
110+
childToUpdate = this.currentChild;
111+
} else {
112+
childToUpdate = this.pendingChild;
113+
}
114+
}
115+
childToUpdate.updateAddressList(addressList, lbConfig, attributes);
116+
}
117+
exitIdle(): void {
118+
if (this.currentChild) {
119+
this.currentChild.resetBackoff();
120+
if (this.pendingChild) {
121+
this.pendingChild.resetBackoff();
122+
}
123+
}
124+
}
125+
resetBackoff(): void {
126+
if (this.currentChild) {
127+
this.currentChild.resetBackoff();
128+
if (this.pendingChild) {
129+
this.pendingChild.resetBackoff();
130+
}
131+
}
132+
}
133+
destroy(): void {
134+
if (this.currentChild) {
135+
this.currentChild.destroy();
136+
}
137+
if (this.pendingChild) {
138+
this.pendingChild.destroy();
139+
}
140+
}
141+
getTypeName(): string {
142+
return TYPE_NAME;
143+
}
144+
}

packages/grpc-js/src/load-balancer-pick-first.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
129129
* @param channelControlHelper `ChannelControlHelper` instance provided by
130130
* this load balancer's owner.
131131
*/
132-
constructor(private channelControlHelper: ChannelControlHelper) {
132+
constructor(private readonly channelControlHelper: ChannelControlHelper) {
133133
this.subchannelStateCounts = {
134134
[ConnectivityState.CONNECTING]: 0,
135135
[ConnectivityState.IDLE]: 0,
@@ -384,7 +384,7 @@ export class PickFirstLoadBalancer implements LoadBalancer {
384384

385385
updateAddressList(
386386
addressList: SubchannelAddress[],
387-
lbConfig: LoadBalancingConfig | null
387+
lbConfig: LoadBalancingConfig
388388
): void {
389389
// lbConfig has no useful information for pick first load balancing
390390
/* To avoid unnecessary churn, we only do something with this address list
@@ -436,10 +436,6 @@ export class PickFirstLoadBalancer implements LoadBalancer {
436436
getTypeName(): string {
437437
return TYPE_NAME;
438438
}
439-
440-
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper) {
441-
this.channelControlHelper = channelControlHelper;
442-
}
443439
}
444440

445441
export function setup(): void {

packages/grpc-js/src/load-balancer-round-robin.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
9494

9595
private currentReadyPicker: RoundRobinPicker | null = null;
9696

97-
constructor(private channelControlHelper: ChannelControlHelper) {
97+
constructor(private readonly channelControlHelper: ChannelControlHelper) {
9898
this.subchannelStateCounts = {
9999
[ConnectivityState.CONNECTING]: 0,
100100
[ConnectivityState.IDLE]: 0,
@@ -188,7 +188,7 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
188188

189189
updateAddressList(
190190
addressList: SubchannelAddress[],
191-
lbConfig: LoadBalancingConfig | null
191+
lbConfig: LoadBalancingConfig
192192
): void {
193193
this.resetSubchannelList();
194194
trace(
@@ -228,11 +228,6 @@ export class RoundRobinLoadBalancer implements LoadBalancer {
228228
getTypeName(): string {
229229
return TYPE_NAME;
230230
}
231-
replaceChannelControlHelper(
232-
channelControlHelper: ChannelControlHelper
233-
): void {
234-
this.channelControlHelper = channelControlHelper;
235-
}
236231
}
237232

238233
export function setup() {

packages/grpc-js/src/load-balancer.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export interface LoadBalancer {
6767
*/
6868
updateAddressList(
6969
addressList: SubchannelAddress[],
70-
lbConfig: LoadBalancingConfig | null,
70+
lbConfig: LoadBalancingConfig,
7171
attributes: { [key: string]: unknown }
7272
): void;
7373
/**
@@ -91,11 +91,6 @@ export interface LoadBalancer {
9191
* balancer implementation class was registered with.
9292
*/
9393
getTypeName(): string;
94-
/**
95-
* Replace the existing ChannelControlHelper with a new one
96-
* @param channelControlHelper The new ChannelControlHelper to use from now on
97-
*/
98-
replaceChannelControlHelper(channelControlHelper: ChannelControlHelper): void;
9994
}
10095

10196
export interface LoadBalancerConstructor {
@@ -128,6 +123,17 @@ export function isLoadBalancerNameRegistered(typeName: string): boolean {
128123
return typeName in registeredLoadBalancerTypes;
129124
}
130125

126+
export function getFirstUsableConfig(
127+
configs: LoadBalancingConfig[]
128+
): LoadBalancingConfig | null {
129+
for (const config of configs) {
130+
if (config.name in registeredLoadBalancerTypes) {
131+
return config;
132+
}
133+
}
134+
return null;
135+
}
136+
131137
export function registerAll() {
132138
load_balancer_pick_first.setup();
133139
load_balancer_round_robin.setup();

packages/grpc-js/src/load-balancing-config.ts

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
* runtime */
2626
/* eslint-disable @typescript-eslint/no-explicit-any */
2727

28+
export type PickFirstConfig = {};
29+
2830
export type RoundRobinConfig = {};
2931

3032
export interface XdsConfig {
@@ -37,11 +39,69 @@ export interface GrpcLbConfig {
3739
childPolicy: LoadBalancingConfig[];
3840
}
3941

40-
export interface LoadBalancingConfig {
41-
/* Exactly one of these must be set for a config to be valid */
42-
round_robin?: RoundRobinConfig;
43-
xds?: XdsConfig;
44-
grpclb?: GrpcLbConfig;
42+
export interface PriorityChild {
43+
config: LoadBalancingConfig[];
44+
}
45+
46+
export interface PriorityLbConfig {
47+
children: Map<string, PriorityChild>;
48+
priorities: string[];
49+
}
50+
51+
export interface PickFirstLoadBalancingConfig {
52+
name: 'pick_first';
53+
pick_first: PickFirstConfig;
54+
}
55+
56+
export interface RoundRobinLoadBalancingConfig {
57+
name: 'round_robin';
58+
round_robin: RoundRobinConfig;
59+
}
60+
61+
export interface XdsLoadBalancingConfig {
62+
name: 'xds';
63+
xds: XdsConfig;
64+
}
65+
66+
export interface GrpcLbLoadBalancingConfig {
67+
name: 'grpclb';
68+
grpclb: GrpcLbConfig;
69+
}
70+
71+
export interface PriorityLoadBalancingConfig {
72+
name: 'priority';
73+
priority: PriorityLbConfig;
74+
}
75+
76+
export type LoadBalancingConfig =
77+
| PickFirstLoadBalancingConfig
78+
| RoundRobinLoadBalancingConfig
79+
| XdsLoadBalancingConfig
80+
| GrpcLbLoadBalancingConfig
81+
| PriorityLoadBalancingConfig;
82+
83+
export function isRoundRobinLoadBalancingConfig(
84+
lbconfig: LoadBalancingConfig
85+
): lbconfig is RoundRobinLoadBalancingConfig {
86+
return lbconfig.name === 'round_robin';
87+
}
88+
89+
export function isXdsLoadBalancingConfig(
90+
lbconfig: LoadBalancingConfig
91+
): lbconfig is XdsLoadBalancingConfig {
92+
return lbconfig.name === 'xds';
93+
}
94+
95+
export function isGrpcLbLoadBalancingConfig(
96+
lbconfig: LoadBalancingConfig
97+
): lbconfig is GrpcLbLoadBalancingConfig {
98+
return lbconfig.name === 'grpclb';
99+
}
100+
101+
export function isPriorityLoadBalancingConfig(
102+
lbconfig: LoadBalancingConfig
103+
): lbconfig is PriorityLoadBalancingConfig {
104+
return lbconfig.name === 'priority';
45105
}
46106

47107
/* In these functions we assume the input came from a JSON object. Therefore we
@@ -97,17 +157,26 @@ export function validateConfig(obj: any): LoadBalancingConfig {
97157
throw new Error('Multiple load balancing policies configured');
98158
}
99159
if (obj['round_robin'] instanceof Object) {
100-
return { round_robin: {} };
160+
return {
161+
name: 'round_robin',
162+
round_robin: {},
163+
};
101164
}
102165
}
103166
if ('xds' in obj) {
104167
if ('grpclb' in obj) {
105168
throw new Error('Multiple load balancing policies configured');
106169
}
107-
return { xds: validateXdsConfig(obj.xds) };
170+
return {
171+
name: 'xds',
172+
xds: validateXdsConfig(obj.xds),
173+
};
108174
}
109175
if ('grpclb' in obj) {
110-
return { grpclb: validateGrpcLbConfig(obj.grpclb) };
176+
return {
177+
name: 'grpclb',
178+
grpclb: validateGrpcLbConfig(obj.grpclb),
179+
};
111180
}
112181
throw new Error('No recognized load balancing policy configured');
113182
}

0 commit comments

Comments
 (0)