load-balancer-outlier-detection.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. "use strict";
  2. /*
  3. * Copyright 2022 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. var _a;
  19. Object.defineProperty(exports, "__esModule", { value: true });
  20. exports.setup = exports.OutlierDetectionLoadBalancer = exports.OutlierDetectionLoadBalancingConfig = void 0;
  21. const connectivity_state_1 = require("./connectivity-state");
  22. const constants_1 = require("./constants");
  23. const duration_1 = require("./duration");
  24. const experimental_1 = require("./experimental");
  25. const filter_1 = require("./filter");
  26. const load_balancer_1 = require("./load-balancer");
  27. const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
  28. const picker_1 = require("./picker");
  29. const subchannel_address_1 = require("./subchannel-address");
  30. const subchannel_interface_1 = require("./subchannel-interface");
  31. const logging = require("./logging");
  32. const TRACER_NAME = 'outlier_detection';
  33. function trace(text) {
  34. logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
  35. }
  36. const TYPE_NAME = 'outlier_detection';
  37. const OUTLIER_DETECTION_ENABLED = ((_a = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION) !== null && _a !== void 0 ? _a : 'true') === 'true';
  38. const defaultSuccessRateEjectionConfig = {
  39. stdev_factor: 1900,
  40. enforcement_percentage: 100,
  41. minimum_hosts: 5,
  42. request_volume: 100
  43. };
  44. const defaultFailurePercentageEjectionConfig = {
  45. threshold: 85,
  46. enforcement_percentage: 100,
  47. minimum_hosts: 5,
  48. request_volume: 50
  49. };
  50. function validateFieldType(obj, fieldName, expectedType, objectName) {
  51. if (fieldName in obj && typeof obj[fieldName] !== expectedType) {
  52. const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
  53. throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`);
  54. }
  55. }
  56. function validatePositiveDuration(obj, fieldName, objectName) {
  57. const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
  58. if (fieldName in obj) {
  59. if (!duration_1.isDuration(obj[fieldName])) {
  60. throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`);
  61. }
  62. if (!(obj[fieldName].seconds >= 0 && obj[fieldName].seconds <= 315576000000 && obj[fieldName].nanos >= 0 && obj[fieldName].nanos <= 999999999)) {
  63. throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`);
  64. }
  65. }
  66. }
  67. function validatePercentage(obj, fieldName, objectName) {
  68. const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName;
  69. validateFieldType(obj, fieldName, 'number', objectName);
  70. if (fieldName in obj && !(obj[fieldName] >= 0 && obj[fieldName] <= 100)) {
  71. throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`);
  72. }
  73. }
  74. class OutlierDetectionLoadBalancingConfig {
  75. constructor(intervalMs, baseEjectionTimeMs, maxEjectionTimeMs, maxEjectionPercent, successRateEjection, failurePercentageEjection, childPolicy) {
  76. this.childPolicy = childPolicy;
  77. this.intervalMs = intervalMs !== null && intervalMs !== void 0 ? intervalMs : 10000;
  78. this.baseEjectionTimeMs = baseEjectionTimeMs !== null && baseEjectionTimeMs !== void 0 ? baseEjectionTimeMs : 30000;
  79. this.maxEjectionTimeMs = maxEjectionTimeMs !== null && maxEjectionTimeMs !== void 0 ? maxEjectionTimeMs : 300000;
  80. this.maxEjectionPercent = maxEjectionPercent !== null && maxEjectionPercent !== void 0 ? maxEjectionPercent : 10;
  81. this.successRateEjection = successRateEjection ? Object.assign(Object.assign({}, defaultSuccessRateEjectionConfig), successRateEjection) : null;
  82. this.failurePercentageEjection = failurePercentageEjection ? Object.assign(Object.assign({}, defaultFailurePercentageEjectionConfig), failurePercentageEjection) : null;
  83. }
  84. getLoadBalancerName() {
  85. return TYPE_NAME;
  86. }
  87. toJsonObject() {
  88. return {
  89. interval: duration_1.msToDuration(this.intervalMs),
  90. base_ejection_time: duration_1.msToDuration(this.baseEjectionTimeMs),
  91. max_ejection_time: duration_1.msToDuration(this.maxEjectionTimeMs),
  92. max_ejection_percent: this.maxEjectionPercent,
  93. success_rate_ejection: this.successRateEjection,
  94. failure_percentage_ejection: this.failurePercentageEjection,
  95. child_policy: this.childPolicy.map(policy => policy.toJsonObject())
  96. };
  97. }
  98. getIntervalMs() {
  99. return this.intervalMs;
  100. }
  101. getBaseEjectionTimeMs() {
  102. return this.baseEjectionTimeMs;
  103. }
  104. getMaxEjectionTimeMs() {
  105. return this.maxEjectionTimeMs;
  106. }
  107. getMaxEjectionPercent() {
  108. return this.maxEjectionPercent;
  109. }
  110. getSuccessRateEjectionConfig() {
  111. return this.successRateEjection;
  112. }
  113. getFailurePercentageEjectionConfig() {
  114. return this.failurePercentageEjection;
  115. }
  116. getChildPolicy() {
  117. return this.childPolicy;
  118. }
  119. copyWithChildPolicy(childPolicy) {
  120. return new OutlierDetectionLoadBalancingConfig(this.intervalMs, this.baseEjectionTimeMs, this.maxEjectionTimeMs, this.maxEjectionPercent, this.successRateEjection, this.failurePercentageEjection, childPolicy);
  121. }
  122. static createFromJson(obj) {
  123. var _a;
  124. validatePositiveDuration(obj, 'interval');
  125. validatePositiveDuration(obj, 'base_ejection_time');
  126. validatePositiveDuration(obj, 'max_ejection_time');
  127. validatePercentage(obj, 'max_ejection_percent');
  128. if ('success_rate_ejection' in obj) {
  129. if (typeof obj.success_rate_ejection !== 'object') {
  130. throw new Error('outlier detection config success_rate_ejection must be an object');
  131. }
  132. validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection');
  133. validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection');
  134. validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection');
  135. validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection');
  136. }
  137. if ('failure_percentage_ejection' in obj) {
  138. if (typeof obj.failure_percentage_ejection !== 'object') {
  139. throw new Error('outlier detection config failure_percentage_ejection must be an object');
  140. }
  141. validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection');
  142. validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection');
  143. validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection');
  144. validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection');
  145. }
  146. return new OutlierDetectionLoadBalancingConfig(obj.interval ? duration_1.durationToMs(obj.interval) : null, obj.base_ejection_time ? duration_1.durationToMs(obj.base_ejection_time) : null, obj.max_ejection_time ? duration_1.durationToMs(obj.max_ejection_time) : null, (_a = obj.max_ejection_percent) !== null && _a !== void 0 ? _a : null, obj.success_rate_ejection, obj.failure_percentage_ejection, obj.child_policy.map(load_balancer_1.validateLoadBalancingConfig));
  147. }
  148. }
  149. exports.OutlierDetectionLoadBalancingConfig = OutlierDetectionLoadBalancingConfig;
  150. class OutlierDetectionSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper {
  151. constructor(childSubchannel, mapEntry) {
  152. super(childSubchannel);
  153. this.mapEntry = mapEntry;
  154. this.stateListeners = [];
  155. this.ejected = false;
  156. this.refCount = 0;
  157. this.childSubchannelState = childSubchannel.getConnectivityState();
  158. childSubchannel.addConnectivityStateListener((subchannel, previousState, newState) => {
  159. this.childSubchannelState = newState;
  160. if (!this.ejected) {
  161. for (const listener of this.stateListeners) {
  162. listener(this, previousState, newState);
  163. }
  164. }
  165. });
  166. }
  167. getConnectivityState() {
  168. if (this.ejected) {
  169. return connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE;
  170. }
  171. else {
  172. return this.childSubchannelState;
  173. }
  174. }
  175. /**
  176. * Add a listener function to be called whenever the wrapper's
  177. * connectivity state changes.
  178. * @param listener
  179. */
  180. addConnectivityStateListener(listener) {
  181. this.stateListeners.push(listener);
  182. }
  183. /**
  184. * Remove a listener previously added with `addConnectivityStateListener`
  185. * @param listener A reference to a function previously passed to
  186. * `addConnectivityStateListener`
  187. */
  188. removeConnectivityStateListener(listener) {
  189. const listenerIndex = this.stateListeners.indexOf(listener);
  190. if (listenerIndex > -1) {
  191. this.stateListeners.splice(listenerIndex, 1);
  192. }
  193. }
  194. ref() {
  195. this.child.ref();
  196. this.refCount += 1;
  197. }
  198. unref() {
  199. this.child.unref();
  200. this.refCount -= 1;
  201. if (this.refCount <= 0) {
  202. if (this.mapEntry) {
  203. const index = this.mapEntry.subchannelWrappers.indexOf(this);
  204. if (index >= 0) {
  205. this.mapEntry.subchannelWrappers.splice(index, 1);
  206. }
  207. }
  208. }
  209. }
  210. eject() {
  211. this.ejected = true;
  212. for (const listener of this.stateListeners) {
  213. listener(this, this.childSubchannelState, connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE);
  214. }
  215. }
  216. uneject() {
  217. this.ejected = false;
  218. for (const listener of this.stateListeners) {
  219. listener(this, connectivity_state_1.ConnectivityState.TRANSIENT_FAILURE, this.childSubchannelState);
  220. }
  221. }
  222. getMapEntry() {
  223. return this.mapEntry;
  224. }
  225. getWrappedSubchannel() {
  226. return this.child;
  227. }
  228. }
  229. function createEmptyBucket() {
  230. return {
  231. success: 0,
  232. failure: 0
  233. };
  234. }
  235. class CallCounter {
  236. constructor() {
  237. this.activeBucket = createEmptyBucket();
  238. this.inactiveBucket = createEmptyBucket();
  239. }
  240. addSuccess() {
  241. this.activeBucket.success += 1;
  242. }
  243. addFailure() {
  244. this.activeBucket.failure += 1;
  245. }
  246. switchBuckets() {
  247. this.inactiveBucket = this.activeBucket;
  248. this.activeBucket = createEmptyBucket();
  249. }
  250. getLastSuccesses() {
  251. return this.inactiveBucket.success;
  252. }
  253. getLastFailures() {
  254. return this.inactiveBucket.failure;
  255. }
  256. }
  257. class OutlierDetectionCounterFilter extends filter_1.BaseFilter {
  258. constructor(callCounter) {
  259. super();
  260. this.callCounter = callCounter;
  261. }
  262. receiveTrailers(status) {
  263. if (status.code === constants_1.Status.OK) {
  264. this.callCounter.addSuccess();
  265. }
  266. else {
  267. this.callCounter.addFailure();
  268. }
  269. return status;
  270. }
  271. }
  272. class OutlierDetectionCounterFilterFactory {
  273. constructor(callCounter) {
  274. this.callCounter = callCounter;
  275. }
  276. createFilter(callStream) {
  277. return new OutlierDetectionCounterFilter(this.callCounter);
  278. }
  279. }
  280. class OutlierDetectionPicker {
  281. constructor(wrappedPicker, countCalls) {
  282. this.wrappedPicker = wrappedPicker;
  283. this.countCalls = countCalls;
  284. }
  285. pick(pickArgs) {
  286. const wrappedPick = this.wrappedPicker.pick(pickArgs);
  287. if (wrappedPick.pickResultType === picker_1.PickResultType.COMPLETE) {
  288. const subchannelWrapper = wrappedPick.subchannel;
  289. const mapEntry = subchannelWrapper.getMapEntry();
  290. if (mapEntry) {
  291. const extraFilterFactories = [...wrappedPick.extraFilterFactories];
  292. if (this.countCalls) {
  293. extraFilterFactories.push(new OutlierDetectionCounterFilterFactory(mapEntry.counter));
  294. }
  295. return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel(), extraFilterFactories: extraFilterFactories });
  296. }
  297. else {
  298. return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel() });
  299. }
  300. }
  301. else {
  302. return wrappedPick;
  303. }
  304. }
  305. }
  306. class OutlierDetectionLoadBalancer {
  307. constructor(channelControlHelper) {
  308. this.addressMap = new Map();
  309. this.latestConfig = null;
  310. this.timerStartTime = null;
  311. this.childBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler(experimental_1.createChildChannelControlHelper(channelControlHelper, {
  312. createSubchannel: (subchannelAddress, subchannelArgs) => {
  313. const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs);
  314. const mapEntry = this.addressMap.get(subchannel_address_1.subchannelAddressToString(subchannelAddress));
  315. const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry);
  316. if ((mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.currentEjectionTimestamp) !== null) {
  317. // If the address is ejected, propagate that to the new subchannel wrapper
  318. subchannelWrapper.eject();
  319. }
  320. mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.subchannelWrappers.push(subchannelWrapper);
  321. return subchannelWrapper;
  322. },
  323. updateState: (connectivityState, picker) => {
  324. if (connectivityState === connectivity_state_1.ConnectivityState.READY) {
  325. channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()));
  326. }
  327. else {
  328. channelControlHelper.updateState(connectivityState, picker);
  329. }
  330. }
  331. }));
  332. this.ejectionTimer = setInterval(() => { }, 0);
  333. clearInterval(this.ejectionTimer);
  334. }
  335. isCountingEnabled() {
  336. return this.latestConfig !== null &&
  337. (this.latestConfig.getSuccessRateEjectionConfig() !== null ||
  338. this.latestConfig.getFailurePercentageEjectionConfig() !== null);
  339. }
  340. getCurrentEjectionPercent() {
  341. let ejectionCount = 0;
  342. for (const mapEntry of this.addressMap.values()) {
  343. if (mapEntry.currentEjectionTimestamp !== null) {
  344. ejectionCount += 1;
  345. }
  346. }
  347. return (ejectionCount * 100) / this.addressMap.size;
  348. }
  349. runSuccessRateCheck(ejectionTimestamp) {
  350. if (!this.latestConfig) {
  351. return;
  352. }
  353. const successRateConfig = this.latestConfig.getSuccessRateEjectionConfig();
  354. if (!successRateConfig) {
  355. return;
  356. }
  357. trace('Running success rate check');
  358. // Step 1
  359. const targetRequestVolume = successRateConfig.request_volume;
  360. let addresesWithTargetVolume = 0;
  361. const successRates = [];
  362. for (const mapEntry of this.addressMap.values()) {
  363. const successes = mapEntry.counter.getLastSuccesses();
  364. const failures = mapEntry.counter.getLastFailures();
  365. if (successes + failures >= targetRequestVolume) {
  366. addresesWithTargetVolume += 1;
  367. successRates.push(successes / (successes + failures));
  368. }
  369. }
  370. trace('Found ' + addresesWithTargetVolume + ' success rate candidates; currentEjectionPercent=' + this.getCurrentEjectionPercent() + ' successRates=[' + successRates + ']');
  371. if (addresesWithTargetVolume < successRateConfig.minimum_hosts) {
  372. return;
  373. }
  374. // Step 2
  375. const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length;
  376. let successRateDeviationSum = 0;
  377. for (const rate of successRates) {
  378. const deviation = rate - successRateMean;
  379. successRateDeviationSum += deviation * deviation;
  380. }
  381. const successRateVariance = successRateDeviationSum / successRates.length;
  382. const successRateStdev = Math.sqrt(successRateVariance);
  383. const ejectionThreshold = successRateMean - successRateStdev * (successRateConfig.stdev_factor / 1000);
  384. trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold);
  385. // Step 3
  386. for (const [address, mapEntry] of this.addressMap.entries()) {
  387. // Step 3.i
  388. if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) {
  389. break;
  390. }
  391. // Step 3.ii
  392. const successes = mapEntry.counter.getLastSuccesses();
  393. const failures = mapEntry.counter.getLastFailures();
  394. if (successes + failures < targetRequestVolume) {
  395. continue;
  396. }
  397. // Step 3.iii
  398. const successRate = successes / (successes + failures);
  399. trace('Checking candidate ' + address + ' successRate=' + successRate);
  400. if (successRate < ejectionThreshold) {
  401. const randomNumber = Math.random() * 100;
  402. trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + successRateConfig.enforcement_percentage);
  403. if (randomNumber < successRateConfig.enforcement_percentage) {
  404. trace('Ejecting candidate ' + address);
  405. this.eject(mapEntry, ejectionTimestamp);
  406. }
  407. }
  408. }
  409. }
  410. runFailurePercentageCheck(ejectionTimestamp) {
  411. if (!this.latestConfig) {
  412. return;
  413. }
  414. const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig();
  415. if (!failurePercentageConfig) {
  416. return;
  417. }
  418. trace('Running failure percentage check. threshold=' + failurePercentageConfig.threshold + ' request volume threshold=' + failurePercentageConfig.request_volume);
  419. // Step 1
  420. let addressesWithTargetVolume = 0;
  421. for (const mapEntry of this.addressMap.values()) {
  422. const successes = mapEntry.counter.getLastSuccesses();
  423. const failures = mapEntry.counter.getLastFailures();
  424. if (successes + failures >= failurePercentageConfig.request_volume) {
  425. addressesWithTargetVolume += 1;
  426. }
  427. }
  428. if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) {
  429. return;
  430. }
  431. // Step 2
  432. for (const [address, mapEntry] of this.addressMap.entries()) {
  433. // Step 2.i
  434. if (this.getCurrentEjectionPercent() >= this.latestConfig.getMaxEjectionPercent()) {
  435. break;
  436. }
  437. // Step 2.ii
  438. const successes = mapEntry.counter.getLastSuccesses();
  439. const failures = mapEntry.counter.getLastFailures();
  440. trace('Candidate successes=' + successes + ' failures=' + failures);
  441. if (successes + failures < failurePercentageConfig.request_volume) {
  442. continue;
  443. }
  444. // Step 2.iii
  445. const failurePercentage = (failures * 100) / (failures + successes);
  446. if (failurePercentage > failurePercentageConfig.threshold) {
  447. const randomNumber = Math.random() * 100;
  448. trace('Candidate ' + address + ' randomNumber=' + randomNumber + ' enforcement_percentage=' + failurePercentageConfig.enforcement_percentage);
  449. if (randomNumber < failurePercentageConfig.enforcement_percentage) {
  450. trace('Ejecting candidate ' + address);
  451. this.eject(mapEntry, ejectionTimestamp);
  452. }
  453. }
  454. }
  455. }
  456. eject(mapEntry, ejectionTimestamp) {
  457. mapEntry.currentEjectionTimestamp = new Date();
  458. mapEntry.ejectionTimeMultiplier += 1;
  459. for (const subchannelWrapper of mapEntry.subchannelWrappers) {
  460. subchannelWrapper.eject();
  461. }
  462. }
  463. uneject(mapEntry) {
  464. mapEntry.currentEjectionTimestamp = null;
  465. for (const subchannelWrapper of mapEntry.subchannelWrappers) {
  466. subchannelWrapper.uneject();
  467. }
  468. }
  469. switchAllBuckets() {
  470. for (const mapEntry of this.addressMap.values()) {
  471. mapEntry.counter.switchBuckets();
  472. }
  473. }
  474. startTimer(delayMs) {
  475. this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs);
  476. }
  477. runChecks() {
  478. const ejectionTimestamp = new Date();
  479. trace('Ejection timer running');
  480. this.switchAllBuckets();
  481. if (!this.latestConfig) {
  482. return;
  483. }
  484. this.timerStartTime = ejectionTimestamp;
  485. this.startTimer(this.latestConfig.getIntervalMs());
  486. this.runSuccessRateCheck(ejectionTimestamp);
  487. this.runFailurePercentageCheck(ejectionTimestamp);
  488. for (const [address, mapEntry] of this.addressMap.entries()) {
  489. if (mapEntry.currentEjectionTimestamp === null) {
  490. if (mapEntry.ejectionTimeMultiplier > 0) {
  491. mapEntry.ejectionTimeMultiplier -= 1;
  492. }
  493. }
  494. else {
  495. const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs();
  496. const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs();
  497. const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime());
  498. returnTime.setMilliseconds(returnTime.getMilliseconds() + Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs)));
  499. if (returnTime < new Date()) {
  500. trace('Unejecting ' + address);
  501. this.uneject(mapEntry);
  502. }
  503. }
  504. }
  505. }
  506. updateAddressList(addressList, lbConfig, attributes) {
  507. if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) {
  508. return;
  509. }
  510. const subchannelAddresses = new Set();
  511. for (const address of addressList) {
  512. subchannelAddresses.add(subchannel_address_1.subchannelAddressToString(address));
  513. }
  514. for (const address of subchannelAddresses) {
  515. if (!this.addressMap.has(address)) {
  516. trace('Adding map entry for ' + address);
  517. this.addressMap.set(address, {
  518. counter: new CallCounter(),
  519. currentEjectionTimestamp: null,
  520. ejectionTimeMultiplier: 0,
  521. subchannelWrappers: []
  522. });
  523. }
  524. }
  525. for (const key of this.addressMap.keys()) {
  526. if (!subchannelAddresses.has(key)) {
  527. trace('Removing map entry for ' + key);
  528. this.addressMap.delete(key);
  529. }
  530. }
  531. const childPolicy = load_balancer_1.getFirstUsableConfig(lbConfig.getChildPolicy(), true);
  532. this.childBalancer.updateAddressList(addressList, childPolicy, attributes);
  533. if (lbConfig.getSuccessRateEjectionConfig() || lbConfig.getFailurePercentageEjectionConfig()) {
  534. if (this.timerStartTime) {
  535. trace('Previous timer existed. Replacing timer');
  536. clearTimeout(this.ejectionTimer);
  537. const remainingDelay = lbConfig.getIntervalMs() - ((new Date()).getTime() - this.timerStartTime.getTime());
  538. this.startTimer(remainingDelay);
  539. }
  540. else {
  541. trace('Starting new timer');
  542. this.timerStartTime = new Date();
  543. this.startTimer(lbConfig.getIntervalMs());
  544. this.switchAllBuckets();
  545. }
  546. }
  547. else {
  548. trace('Counting disabled. Cancelling timer.');
  549. this.timerStartTime = null;
  550. clearTimeout(this.ejectionTimer);
  551. for (const mapEntry of this.addressMap.values()) {
  552. this.uneject(mapEntry);
  553. mapEntry.ejectionTimeMultiplier = 0;
  554. }
  555. }
  556. this.latestConfig = lbConfig;
  557. }
  558. exitIdle() {
  559. this.childBalancer.exitIdle();
  560. }
  561. resetBackoff() {
  562. this.childBalancer.resetBackoff();
  563. }
  564. destroy() {
  565. clearTimeout(this.ejectionTimer);
  566. this.childBalancer.destroy();
  567. }
  568. getTypeName() {
  569. return TYPE_NAME;
  570. }
  571. }
  572. exports.OutlierDetectionLoadBalancer = OutlierDetectionLoadBalancer;
  573. function setup() {
  574. if (OUTLIER_DETECTION_ENABLED) {
  575. experimental_1.registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig);
  576. }
  577. }
  578. exports.setup = setup;
  579. //# sourceMappingURL=load-balancer-outlier-detection.js.map