UNPKG

25.7 kBJavaScriptView Raw
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 */
18var _a;
19Object.defineProperty(exports, "__esModule", { value: true });
20exports.setup = exports.OutlierDetectionLoadBalancer = exports.OutlierDetectionLoadBalancingConfig = void 0;
21const connectivity_state_1 = require("./connectivity-state");
22const constants_1 = require("./constants");
23const duration_1 = require("./duration");
24const experimental_1 = require("./experimental");
25const filter_1 = require("./filter");
26const load_balancer_1 = require("./load-balancer");
27const load_balancer_child_handler_1 = require("./load-balancer-child-handler");
28const picker_1 = require("./picker");
29const subchannel_address_1 = require("./subchannel-address");
30const subchannel_interface_1 = require("./subchannel-interface");
31const logging = require("./logging");
32const TRACER_NAME = 'outlier_detection';
33function trace(text) {
34 logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text);
35}
36const TYPE_NAME = 'outlier_detection';
37const OUTLIER_DETECTION_ENABLED = ((_a = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION) !== null && _a !== void 0 ? _a : 'true') === 'true';
38const defaultSuccessRateEjectionConfig = {
39 stdev_factor: 1900,
40 enforcement_percentage: 100,
41 minimum_hosts: 5,
42 request_volume: 100
43};
44const defaultFailurePercentageEjectionConfig = {
45 threshold: 85,
46 enforcement_percentage: 100,
47 minimum_hosts: 5,
48 request_volume: 50
49};
50function 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}
56function 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}
67function 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}
74class 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}
149exports.OutlierDetectionLoadBalancingConfig = OutlierDetectionLoadBalancingConfig;
150class 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}
229function createEmptyBucket() {
230 return {
231 success: 0,
232 failure: 0
233 };
234}
235class 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}
257class 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}
272class OutlierDetectionCounterFilterFactory {
273 constructor(callCounter) {
274 this.callCounter = callCounter;
275 }
276 createFilter(callStream) {
277 return new OutlierDetectionCounterFilter(this.callCounter);
278 }
279}
280class 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}
306class 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}
572exports.OutlierDetectionLoadBalancer = OutlierDetectionLoadBalancer;
573function setup() {
574 if (OUTLIER_DETECTION_ENABLED) {
575 experimental_1.registerLoadBalancerType(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig);
576 }
577}
578exports.setup = setup;
579//# sourceMappingURL=load-balancer-outlier-detection.js.map
\No newline at end of file