load-balancer-child-handler.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. import {
  18. LoadBalancer,
  19. ChannelControlHelper,
  20. TypedLoadBalancingConfig,
  21. createLoadBalancer,
  22. } from './load-balancer';
  23. import { Endpoint, SubchannelAddress } from './subchannel-address';
  24. import { ChannelOptions } from './channel-options';
  25. import { ConnectivityState } from './connectivity-state';
  26. import { Picker } from './picker';
  27. import type { ChannelRef, SubchannelRef } from './channelz';
  28. import { SubchannelInterface } from './subchannel-interface';
  29. import { ChannelCredentials } from './channel-credentials';
  30. const TYPE_NAME = 'child_load_balancer_helper';
  31. export class ChildLoadBalancerHandler implements LoadBalancer {
  32. private currentChild: LoadBalancer | null = null;
  33. private pendingChild: LoadBalancer | null = null;
  34. private latestConfig: TypedLoadBalancingConfig | null = null;
  35. private ChildPolicyHelper = class {
  36. private child: LoadBalancer | null = null;
  37. constructor(private parent: ChildLoadBalancerHandler) {}
  38. createSubchannel(
  39. subchannelAddress: SubchannelAddress,
  40. subchannelArgs: ChannelOptions,
  41. credentialsOverride: ChannelCredentials | null
  42. ): SubchannelInterface {
  43. return this.parent.channelControlHelper.createSubchannel(
  44. subchannelAddress,
  45. subchannelArgs,
  46. credentialsOverride
  47. );
  48. }
  49. updateState(connectivityState: ConnectivityState, picker: Picker): void {
  50. if (this.calledByPendingChild()) {
  51. if (connectivityState === ConnectivityState.CONNECTING) {
  52. return;
  53. }
  54. this.parent.currentChild?.destroy();
  55. this.parent.currentChild = this.parent.pendingChild;
  56. this.parent.pendingChild = null;
  57. } else if (!this.calledByCurrentChild()) {
  58. return;
  59. }
  60. this.parent.channelControlHelper.updateState(connectivityState, picker);
  61. }
  62. requestReresolution(): void {
  63. const latestChild = this.parent.pendingChild ?? this.parent.currentChild;
  64. if (this.child === latestChild) {
  65. this.parent.channelControlHelper.requestReresolution();
  66. }
  67. }
  68. setChild(newChild: LoadBalancer) {
  69. this.child = newChild;
  70. }
  71. addChannelzChild(child: ChannelRef | SubchannelRef) {
  72. this.parent.channelControlHelper.addChannelzChild(child);
  73. }
  74. removeChannelzChild(child: ChannelRef | SubchannelRef) {
  75. this.parent.channelControlHelper.removeChannelzChild(child);
  76. }
  77. private calledByPendingChild(): boolean {
  78. return this.child === this.parent.pendingChild;
  79. }
  80. private calledByCurrentChild(): boolean {
  81. return this.child === this.parent.currentChild;
  82. }
  83. };
  84. constructor(
  85. private readonly channelControlHelper: ChannelControlHelper,
  86. private readonly credentials: ChannelCredentials,
  87. private readonly options: ChannelOptions
  88. ) {}
  89. protected configUpdateRequiresNewPolicyInstance(
  90. oldConfig: TypedLoadBalancingConfig,
  91. newConfig: TypedLoadBalancingConfig
  92. ): boolean {
  93. return oldConfig.getLoadBalancerName() !== newConfig.getLoadBalancerName();
  94. }
  95. /**
  96. * Prerequisites: lbConfig !== null and lbConfig.name is registered
  97. * @param endpointList
  98. * @param lbConfig
  99. * @param attributes
  100. */
  101. updateAddressList(
  102. endpointList: Endpoint[],
  103. lbConfig: TypedLoadBalancingConfig,
  104. attributes: { [key: string]: unknown }
  105. ): void {
  106. let childToUpdate: LoadBalancer;
  107. if (
  108. this.currentChild === null ||
  109. this.latestConfig === null ||
  110. this.configUpdateRequiresNewPolicyInstance(this.latestConfig, lbConfig)
  111. ) {
  112. const newHelper = new this.ChildPolicyHelper(this);
  113. const newChild = createLoadBalancer(lbConfig, newHelper, this.credentials, this.options)!;
  114. newHelper.setChild(newChild);
  115. if (this.currentChild === null) {
  116. this.currentChild = newChild;
  117. childToUpdate = this.currentChild;
  118. } else {
  119. if (this.pendingChild) {
  120. this.pendingChild.destroy();
  121. }
  122. this.pendingChild = newChild;
  123. childToUpdate = this.pendingChild;
  124. }
  125. } else {
  126. if (this.pendingChild === null) {
  127. childToUpdate = this.currentChild;
  128. } else {
  129. childToUpdate = this.pendingChild;
  130. }
  131. }
  132. this.latestConfig = lbConfig;
  133. childToUpdate.updateAddressList(endpointList, lbConfig, attributes);
  134. }
  135. exitIdle(): void {
  136. if (this.currentChild) {
  137. this.currentChild.exitIdle();
  138. if (this.pendingChild) {
  139. this.pendingChild.exitIdle();
  140. }
  141. }
  142. }
  143. resetBackoff(): void {
  144. if (this.currentChild) {
  145. this.currentChild.resetBackoff();
  146. if (this.pendingChild) {
  147. this.pendingChild.resetBackoff();
  148. }
  149. }
  150. }
  151. destroy(): void {
  152. /* Note: state updates are only propagated from the child balancer if that
  153. * object is equal to this.currentChild or this.pendingChild. Since this
  154. * function sets both of those to null, no further state updates will
  155. * occur after this function returns. */
  156. if (this.currentChild) {
  157. this.currentChild.destroy();
  158. this.currentChild = null;
  159. }
  160. if (this.pendingChild) {
  161. this.pendingChild.destroy();
  162. this.pendingChild = null;
  163. }
  164. }
  165. getTypeName(): string {
  166. return TYPE_NAME;
  167. }
  168. }