|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { |
|
LoadBalancer, |
|
ChannelControlHelper, |
|
LoadBalancingConfig, |
|
createLoadBalancer, |
|
} from './load-balancer'; |
|
import { SubchannelAddress } from './subchannel-address'; |
|
import { ChannelOptions } from './channel-options'; |
|
import { ConnectivityState } from './connectivity-state'; |
|
import { Picker } from './picker'; |
|
import { ChannelRef, SubchannelRef } from './channelz'; |
|
import { SubchannelInterface } from './subchannel-interface'; |
|
|
|
const TYPE_NAME = 'child_load_balancer_helper'; |
|
|
|
export class ChildLoadBalancerHandler implements LoadBalancer { |
|
private currentChild: LoadBalancer | null = null; |
|
private pendingChild: LoadBalancer | null = null; |
|
private latestConfig: LoadBalancingConfig | null = null; |
|
|
|
private ChildPolicyHelper = class { |
|
private child: LoadBalancer | null = null; |
|
constructor(private parent: ChildLoadBalancerHandler) {} |
|
createSubchannel( |
|
subchannelAddress: SubchannelAddress, |
|
subchannelArgs: ChannelOptions |
|
): SubchannelInterface { |
|
return this.parent.channelControlHelper.createSubchannel( |
|
subchannelAddress, |
|
subchannelArgs |
|
); |
|
} |
|
updateState(connectivityState: ConnectivityState, picker: Picker): void { |
|
if (this.calledByPendingChild()) { |
|
if (connectivityState === ConnectivityState.CONNECTING) { |
|
return; |
|
} |
|
this.parent.currentChild?.destroy(); |
|
this.parent.currentChild = this.parent.pendingChild; |
|
this.parent.pendingChild = null; |
|
} else if (!this.calledByCurrentChild()) { |
|
return; |
|
} |
|
this.parent.channelControlHelper.updateState(connectivityState, picker); |
|
} |
|
requestReresolution(): void { |
|
const latestChild = this.parent.pendingChild ?? this.parent.currentChild; |
|
if (this.child === latestChild) { |
|
this.parent.channelControlHelper.requestReresolution(); |
|
} |
|
} |
|
setChild(newChild: LoadBalancer) { |
|
this.child = newChild; |
|
} |
|
addChannelzChild(child: ChannelRef | SubchannelRef) { |
|
this.parent.channelControlHelper.addChannelzChild(child); |
|
} |
|
removeChannelzChild(child: ChannelRef | SubchannelRef) { |
|
this.parent.channelControlHelper.removeChannelzChild(child); |
|
} |
|
|
|
private calledByPendingChild(): boolean { |
|
return this.child === this.parent.pendingChild; |
|
} |
|
private calledByCurrentChild(): boolean { |
|
return this.child === this.parent.currentChild; |
|
} |
|
}; |
|
|
|
constructor(private readonly channelControlHelper: ChannelControlHelper) {} |
|
|
|
protected configUpdateRequiresNewPolicyInstance( |
|
oldConfig: LoadBalancingConfig, |
|
newConfig: LoadBalancingConfig |
|
): boolean { |
|
return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateAddressList( |
|
addressList: SubchannelAddress[], |
|
lbConfig: LoadBalancingConfig, |
|
attributes: { [key: string]: unknown } |
|
): void { |
|
let childToUpdate: LoadBalancer; |
|
if ( |
|
this.currentChild === null || |
|
this.latestConfig === null || |
|
this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig) |
|
) { |
|
const newHelper = new this.ChildPolicyHelper(this); |
|
const newChild = createLoadBalancer(lbConfig, newHelper)!; |
|
newHelper.setChild(newChild); |
|
if (this.currentChild === null) { |
|
this.currentChild = newChild; |
|
childToUpdate = this.currentChild; |
|
} else { |
|
if (this.pendingChild) { |
|
this.pendingChild.destroy(); |
|
} |
|
this.pendingChild = newChild; |
|
childToUpdate = this.pendingChild; |
|
} |
|
} else { |
|
if (this.pendingChild === null) { |
|
childToUpdate = this.currentChild; |
|
} else { |
|
childToUpdate = this.pendingChild; |
|
} |
|
} |
|
this.latestConfig = lbConfig; |
|
childToUpdate.updateAddressList(addressList, lbConfig, attributes); |
|
} |
|
exitIdle(): void { |
|
if (this.currentChild) { |
|
this.currentChild.exitIdle(); |
|
if (this.pendingChild) { |
|
this.pendingChild.exitIdle(); |
|
} |
|
} |
|
} |
|
resetBackoff(): void { |
|
if (this.currentChild) { |
|
this.currentChild.resetBackoff(); |
|
if (this.pendingChild) { |
|
this.pendingChild.resetBackoff(); |
|
} |
|
} |
|
} |
|
destroy(): void { |
|
|
|
|
|
|
|
|
|
if (this.currentChild) { |
|
this.currentChild.destroy(); |
|
this.currentChild = null; |
|
} |
|
if (this.pendingChild) { |
|
this.pendingChild.destroy(); |
|
this.pendingChild = null; |
|
} |
|
} |
|
getTypeName(): string { |
|
return TYPE_NAME; |
|
} |
|
} |
|
|