|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var _a; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.OutlierDetectionLoadBalancer = exports.OutlierDetectionLoadBalancingConfig = void 0; |
|
exports.setup = setup; |
|
const connectivity_state_1 = require("./connectivity-state"); |
|
const constants_1 = require("./constants"); |
|
const duration_1 = require("./duration"); |
|
const experimental_1 = require("./experimental"); |
|
const load_balancer_1 = require("./load-balancer"); |
|
const load_balancer_child_handler_1 = require("./load-balancer-child-handler"); |
|
const picker_1 = require("./picker"); |
|
const subchannel_address_1 = require("./subchannel-address"); |
|
const subchannel_interface_1 = require("./subchannel-interface"); |
|
const logging = require("./logging"); |
|
const TRACER_NAME = 'outlier_detection'; |
|
function trace(text) { |
|
logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, text); |
|
} |
|
const TYPE_NAME = 'outlier_detection'; |
|
const OUTLIER_DETECTION_ENABLED = ((_a = process.env.GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION) !== null && _a !== void 0 ? _a : 'true') === 'true'; |
|
const defaultSuccessRateEjectionConfig = { |
|
stdev_factor: 1900, |
|
enforcement_percentage: 100, |
|
minimum_hosts: 5, |
|
request_volume: 100, |
|
}; |
|
const defaultFailurePercentageEjectionConfig = { |
|
threshold: 85, |
|
enforcement_percentage: 100, |
|
minimum_hosts: 5, |
|
request_volume: 50, |
|
}; |
|
function validateFieldType(obj, fieldName, expectedType, objectName) { |
|
if (fieldName in obj && |
|
obj[fieldName] !== undefined && |
|
typeof obj[fieldName] !== expectedType) { |
|
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; |
|
throw new Error(`outlier detection config ${fullFieldName} parse error: expected ${expectedType}, got ${typeof obj[fieldName]}`); |
|
} |
|
} |
|
function validatePositiveDuration(obj, fieldName, objectName) { |
|
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; |
|
if (fieldName in obj && obj[fieldName] !== undefined) { |
|
if (!(0, duration_1.isDuration)(obj[fieldName])) { |
|
throw new Error(`outlier detection config ${fullFieldName} parse error: expected Duration, got ${typeof obj[fieldName]}`); |
|
} |
|
if (!(obj[fieldName].seconds >= 0 && |
|
obj[fieldName].seconds <= 315576000000 && |
|
obj[fieldName].nanos >= 0 && |
|
obj[fieldName].nanos <= 999999999)) { |
|
throw new Error(`outlier detection config ${fullFieldName} parse error: values out of range for non-negative Duaration`); |
|
} |
|
} |
|
} |
|
function validatePercentage(obj, fieldName, objectName) { |
|
const fullFieldName = objectName ? `${objectName}.${fieldName}` : fieldName; |
|
validateFieldType(obj, fieldName, 'number', objectName); |
|
if (fieldName in obj && |
|
obj[fieldName] !== undefined && |
|
!(obj[fieldName] >= 0 && obj[fieldName] <= 100)) { |
|
throw new Error(`outlier detection config ${fullFieldName} parse error: value out of range for percentage (0-100)`); |
|
} |
|
} |
|
class OutlierDetectionLoadBalancingConfig { |
|
constructor(intervalMs, baseEjectionTimeMs, maxEjectionTimeMs, maxEjectionPercent, successRateEjection, failurePercentageEjection, childPolicy) { |
|
this.childPolicy = childPolicy; |
|
if (childPolicy.getLoadBalancerName() === 'pick_first') { |
|
throw new Error('outlier_detection LB policy cannot have a pick_first child policy'); |
|
} |
|
this.intervalMs = intervalMs !== null && intervalMs !== void 0 ? intervalMs : 10000; |
|
this.baseEjectionTimeMs = baseEjectionTimeMs !== null && baseEjectionTimeMs !== void 0 ? baseEjectionTimeMs : 30000; |
|
this.maxEjectionTimeMs = maxEjectionTimeMs !== null && maxEjectionTimeMs !== void 0 ? maxEjectionTimeMs : 300000; |
|
this.maxEjectionPercent = maxEjectionPercent !== null && maxEjectionPercent !== void 0 ? maxEjectionPercent : 10; |
|
this.successRateEjection = successRateEjection |
|
? Object.assign(Object.assign({}, defaultSuccessRateEjectionConfig), successRateEjection) : null; |
|
this.failurePercentageEjection = failurePercentageEjection |
|
? Object.assign(Object.assign({}, defaultFailurePercentageEjectionConfig), failurePercentageEjection) : null; |
|
} |
|
getLoadBalancerName() { |
|
return TYPE_NAME; |
|
} |
|
toJsonObject() { |
|
var _a, _b; |
|
return { |
|
outlier_detection: { |
|
interval: (0, duration_1.msToDuration)(this.intervalMs), |
|
base_ejection_time: (0, duration_1.msToDuration)(this.baseEjectionTimeMs), |
|
max_ejection_time: (0, duration_1.msToDuration)(this.maxEjectionTimeMs), |
|
max_ejection_percent: this.maxEjectionPercent, |
|
success_rate_ejection: (_a = this.successRateEjection) !== null && _a !== void 0 ? _a : undefined, |
|
failure_percentage_ejection: (_b = this.failurePercentageEjection) !== null && _b !== void 0 ? _b : undefined, |
|
child_policy: [this.childPolicy.toJsonObject()], |
|
}, |
|
}; |
|
} |
|
getIntervalMs() { |
|
return this.intervalMs; |
|
} |
|
getBaseEjectionTimeMs() { |
|
return this.baseEjectionTimeMs; |
|
} |
|
getMaxEjectionTimeMs() { |
|
return this.maxEjectionTimeMs; |
|
} |
|
getMaxEjectionPercent() { |
|
return this.maxEjectionPercent; |
|
} |
|
getSuccessRateEjectionConfig() { |
|
return this.successRateEjection; |
|
} |
|
getFailurePercentageEjectionConfig() { |
|
return this.failurePercentageEjection; |
|
} |
|
getChildPolicy() { |
|
return this.childPolicy; |
|
} |
|
static createFromJson(obj) { |
|
var _a; |
|
validatePositiveDuration(obj, 'interval'); |
|
validatePositiveDuration(obj, 'base_ejection_time'); |
|
validatePositiveDuration(obj, 'max_ejection_time'); |
|
validatePercentage(obj, 'max_ejection_percent'); |
|
if ('success_rate_ejection' in obj && |
|
obj.success_rate_ejection !== undefined) { |
|
if (typeof obj.success_rate_ejection !== 'object') { |
|
throw new Error('outlier detection config success_rate_ejection must be an object'); |
|
} |
|
validateFieldType(obj.success_rate_ejection, 'stdev_factor', 'number', 'success_rate_ejection'); |
|
validatePercentage(obj.success_rate_ejection, 'enforcement_percentage', 'success_rate_ejection'); |
|
validateFieldType(obj.success_rate_ejection, 'minimum_hosts', 'number', 'success_rate_ejection'); |
|
validateFieldType(obj.success_rate_ejection, 'request_volume', 'number', 'success_rate_ejection'); |
|
} |
|
if ('failure_percentage_ejection' in obj && |
|
obj.failure_percentage_ejection !== undefined) { |
|
if (typeof obj.failure_percentage_ejection !== 'object') { |
|
throw new Error('outlier detection config failure_percentage_ejection must be an object'); |
|
} |
|
validatePercentage(obj.failure_percentage_ejection, 'threshold', 'failure_percentage_ejection'); |
|
validatePercentage(obj.failure_percentage_ejection, 'enforcement_percentage', 'failure_percentage_ejection'); |
|
validateFieldType(obj.failure_percentage_ejection, 'minimum_hosts', 'number', 'failure_percentage_ejection'); |
|
validateFieldType(obj.failure_percentage_ejection, 'request_volume', 'number', 'failure_percentage_ejection'); |
|
} |
|
if (!('child_policy' in obj) || !Array.isArray(obj.child_policy)) { |
|
throw new Error('outlier detection config child_policy must be an array'); |
|
} |
|
const childPolicy = (0, load_balancer_1.selectLbConfigFromList)(obj.child_policy); |
|
if (!childPolicy) { |
|
throw new Error('outlier detection config child_policy: no valid recognized policy found'); |
|
} |
|
return new OutlierDetectionLoadBalancingConfig(obj.interval ? (0, duration_1.durationToMs)(obj.interval) : null, obj.base_ejection_time ? (0, duration_1.durationToMs)(obj.base_ejection_time) : null, obj.max_ejection_time ? (0, 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, childPolicy); |
|
} |
|
} |
|
exports.OutlierDetectionLoadBalancingConfig = OutlierDetectionLoadBalancingConfig; |
|
class OutlierDetectionSubchannelWrapper extends subchannel_interface_1.BaseSubchannelWrapper { |
|
constructor(childSubchannel, mapEntry) { |
|
super(childSubchannel); |
|
this.mapEntry = mapEntry; |
|
this.refCount = 0; |
|
} |
|
ref() { |
|
this.child.ref(); |
|
this.refCount += 1; |
|
} |
|
unref() { |
|
this.child.unref(); |
|
this.refCount -= 1; |
|
if (this.refCount <= 0) { |
|
if (this.mapEntry) { |
|
const index = this.mapEntry.subchannelWrappers.indexOf(this); |
|
if (index >= 0) { |
|
this.mapEntry.subchannelWrappers.splice(index, 1); |
|
} |
|
} |
|
} |
|
} |
|
eject() { |
|
this.setHealthy(false); |
|
} |
|
uneject() { |
|
this.setHealthy(true); |
|
} |
|
getMapEntry() { |
|
return this.mapEntry; |
|
} |
|
getWrappedSubchannel() { |
|
return this.child; |
|
} |
|
} |
|
function createEmptyBucket() { |
|
return { |
|
success: 0, |
|
failure: 0, |
|
}; |
|
} |
|
class CallCounter { |
|
constructor() { |
|
this.activeBucket = createEmptyBucket(); |
|
this.inactiveBucket = createEmptyBucket(); |
|
} |
|
addSuccess() { |
|
this.activeBucket.success += 1; |
|
} |
|
addFailure() { |
|
this.activeBucket.failure += 1; |
|
} |
|
switchBuckets() { |
|
this.inactiveBucket = this.activeBucket; |
|
this.activeBucket = createEmptyBucket(); |
|
} |
|
getLastSuccesses() { |
|
return this.inactiveBucket.success; |
|
} |
|
getLastFailures() { |
|
return this.inactiveBucket.failure; |
|
} |
|
} |
|
class OutlierDetectionPicker { |
|
constructor(wrappedPicker, countCalls) { |
|
this.wrappedPicker = wrappedPicker; |
|
this.countCalls = countCalls; |
|
} |
|
pick(pickArgs) { |
|
const wrappedPick = this.wrappedPicker.pick(pickArgs); |
|
if (wrappedPick.pickResultType === picker_1.PickResultType.COMPLETE) { |
|
const subchannelWrapper = wrappedPick.subchannel; |
|
const mapEntry = subchannelWrapper.getMapEntry(); |
|
if (mapEntry) { |
|
let onCallEnded = wrappedPick.onCallEnded; |
|
if (this.countCalls) { |
|
onCallEnded = statusCode => { |
|
var _a; |
|
if (statusCode === constants_1.Status.OK) { |
|
mapEntry.counter.addSuccess(); |
|
} |
|
else { |
|
mapEntry.counter.addFailure(); |
|
} |
|
(_a = wrappedPick.onCallEnded) === null || _a === void 0 ? void 0 : _a.call(wrappedPick, statusCode); |
|
}; |
|
} |
|
return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel(), onCallEnded: onCallEnded }); |
|
} |
|
else { |
|
return Object.assign(Object.assign({}, wrappedPick), { subchannel: subchannelWrapper.getWrappedSubchannel() }); |
|
} |
|
} |
|
else { |
|
return wrappedPick; |
|
} |
|
} |
|
} |
|
class OutlierDetectionLoadBalancer { |
|
constructor(channelControlHelper) { |
|
this.entryMap = new subchannel_address_1.EndpointMap(); |
|
this.latestConfig = null; |
|
this.timerStartTime = null; |
|
this.childBalancer = new load_balancer_child_handler_1.ChildLoadBalancerHandler((0, experimental_1.createChildChannelControlHelper)(channelControlHelper, { |
|
createSubchannel: (subchannelAddress, subchannelArgs) => { |
|
const originalSubchannel = channelControlHelper.createSubchannel(subchannelAddress, subchannelArgs); |
|
const mapEntry = this.entryMap.getForSubchannelAddress(subchannelAddress); |
|
const subchannelWrapper = new OutlierDetectionSubchannelWrapper(originalSubchannel, mapEntry); |
|
if ((mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.currentEjectionTimestamp) !== null) { |
|
|
|
subchannelWrapper.eject(); |
|
} |
|
mapEntry === null || mapEntry === void 0 ? void 0 : mapEntry.subchannelWrappers.push(subchannelWrapper); |
|
return subchannelWrapper; |
|
}, |
|
updateState: (connectivityState, picker, errorMessage) => { |
|
if (connectivityState === connectivity_state_1.ConnectivityState.READY) { |
|
channelControlHelper.updateState(connectivityState, new OutlierDetectionPicker(picker, this.isCountingEnabled()), errorMessage); |
|
} |
|
else { |
|
channelControlHelper.updateState(connectivityState, picker, errorMessage); |
|
} |
|
}, |
|
})); |
|
this.ejectionTimer = setInterval(() => { }, 0); |
|
clearInterval(this.ejectionTimer); |
|
} |
|
isCountingEnabled() { |
|
return (this.latestConfig !== null && |
|
(this.latestConfig.getSuccessRateEjectionConfig() !== null || |
|
this.latestConfig.getFailurePercentageEjectionConfig() !== null)); |
|
} |
|
getCurrentEjectionPercent() { |
|
let ejectionCount = 0; |
|
for (const mapEntry of this.entryMap.values()) { |
|
if (mapEntry.currentEjectionTimestamp !== null) { |
|
ejectionCount += 1; |
|
} |
|
} |
|
return (ejectionCount * 100) / this.entryMap.size; |
|
} |
|
runSuccessRateCheck(ejectionTimestamp) { |
|
if (!this.latestConfig) { |
|
return; |
|
} |
|
const successRateConfig = this.latestConfig.getSuccessRateEjectionConfig(); |
|
if (!successRateConfig) { |
|
return; |
|
} |
|
trace('Running success rate check'); |
|
|
|
const targetRequestVolume = successRateConfig.request_volume; |
|
let addresesWithTargetVolume = 0; |
|
const successRates = []; |
|
for (const [endpoint, mapEntry] of this.entryMap.entries()) { |
|
const successes = mapEntry.counter.getLastSuccesses(); |
|
const failures = mapEntry.counter.getLastFailures(); |
|
trace('Stats for ' + |
|
(0, subchannel_address_1.endpointToString)(endpoint) + |
|
': successes=' + |
|
successes + |
|
' failures=' + |
|
failures + |
|
' targetRequestVolume=' + |
|
targetRequestVolume); |
|
if (successes + failures >= targetRequestVolume) { |
|
addresesWithTargetVolume += 1; |
|
successRates.push(successes / (successes + failures)); |
|
} |
|
} |
|
trace('Found ' + |
|
addresesWithTargetVolume + |
|
' success rate candidates; currentEjectionPercent=' + |
|
this.getCurrentEjectionPercent() + |
|
' successRates=[' + |
|
successRates + |
|
']'); |
|
if (addresesWithTargetVolume < successRateConfig.minimum_hosts) { |
|
return; |
|
} |
|
|
|
const successRateMean = successRates.reduce((a, b) => a + b) / successRates.length; |
|
let successRateDeviationSum = 0; |
|
for (const rate of successRates) { |
|
const deviation = rate - successRateMean; |
|
successRateDeviationSum += deviation * deviation; |
|
} |
|
const successRateVariance = successRateDeviationSum / successRates.length; |
|
const successRateStdev = Math.sqrt(successRateVariance); |
|
const ejectionThreshold = successRateMean - |
|
successRateStdev * (successRateConfig.stdev_factor / 1000); |
|
trace('stdev=' + successRateStdev + ' ejectionThreshold=' + ejectionThreshold); |
|
|
|
for (const [address, mapEntry] of this.entryMap.entries()) { |
|
|
|
if (this.getCurrentEjectionPercent() >= |
|
this.latestConfig.getMaxEjectionPercent()) { |
|
break; |
|
} |
|
|
|
const successes = mapEntry.counter.getLastSuccesses(); |
|
const failures = mapEntry.counter.getLastFailures(); |
|
if (successes + failures < targetRequestVolume) { |
|
continue; |
|
} |
|
|
|
const successRate = successes / (successes + failures); |
|
trace('Checking candidate ' + address + ' successRate=' + successRate); |
|
if (successRate < ejectionThreshold) { |
|
const randomNumber = Math.random() * 100; |
|
trace('Candidate ' + |
|
address + |
|
' randomNumber=' + |
|
randomNumber + |
|
' enforcement_percentage=' + |
|
successRateConfig.enforcement_percentage); |
|
if (randomNumber < successRateConfig.enforcement_percentage) { |
|
trace('Ejecting candidate ' + address); |
|
this.eject(mapEntry, ejectionTimestamp); |
|
} |
|
} |
|
} |
|
} |
|
runFailurePercentageCheck(ejectionTimestamp) { |
|
if (!this.latestConfig) { |
|
return; |
|
} |
|
const failurePercentageConfig = this.latestConfig.getFailurePercentageEjectionConfig(); |
|
if (!failurePercentageConfig) { |
|
return; |
|
} |
|
trace('Running failure percentage check. threshold=' + |
|
failurePercentageConfig.threshold + |
|
' request volume threshold=' + |
|
failurePercentageConfig.request_volume); |
|
|
|
let addressesWithTargetVolume = 0; |
|
for (const mapEntry of this.entryMap.values()) { |
|
const successes = mapEntry.counter.getLastSuccesses(); |
|
const failures = mapEntry.counter.getLastFailures(); |
|
if (successes + failures >= failurePercentageConfig.request_volume) { |
|
addressesWithTargetVolume += 1; |
|
} |
|
} |
|
if (addressesWithTargetVolume < failurePercentageConfig.minimum_hosts) { |
|
return; |
|
} |
|
|
|
for (const [address, mapEntry] of this.entryMap.entries()) { |
|
|
|
if (this.getCurrentEjectionPercent() >= |
|
this.latestConfig.getMaxEjectionPercent()) { |
|
break; |
|
} |
|
|
|
const successes = mapEntry.counter.getLastSuccesses(); |
|
const failures = mapEntry.counter.getLastFailures(); |
|
trace('Candidate successes=' + successes + ' failures=' + failures); |
|
if (successes + failures < failurePercentageConfig.request_volume) { |
|
continue; |
|
} |
|
|
|
const failurePercentage = (failures * 100) / (failures + successes); |
|
if (failurePercentage > failurePercentageConfig.threshold) { |
|
const randomNumber = Math.random() * 100; |
|
trace('Candidate ' + |
|
address + |
|
' randomNumber=' + |
|
randomNumber + |
|
' enforcement_percentage=' + |
|
failurePercentageConfig.enforcement_percentage); |
|
if (randomNumber < failurePercentageConfig.enforcement_percentage) { |
|
trace('Ejecting candidate ' + address); |
|
this.eject(mapEntry, ejectionTimestamp); |
|
} |
|
} |
|
} |
|
} |
|
eject(mapEntry, ejectionTimestamp) { |
|
mapEntry.currentEjectionTimestamp = new Date(); |
|
mapEntry.ejectionTimeMultiplier += 1; |
|
for (const subchannelWrapper of mapEntry.subchannelWrappers) { |
|
subchannelWrapper.eject(); |
|
} |
|
} |
|
uneject(mapEntry) { |
|
mapEntry.currentEjectionTimestamp = null; |
|
for (const subchannelWrapper of mapEntry.subchannelWrappers) { |
|
subchannelWrapper.uneject(); |
|
} |
|
} |
|
switchAllBuckets() { |
|
for (const mapEntry of this.entryMap.values()) { |
|
mapEntry.counter.switchBuckets(); |
|
} |
|
} |
|
startTimer(delayMs) { |
|
var _a, _b; |
|
this.ejectionTimer = setTimeout(() => this.runChecks(), delayMs); |
|
(_b = (_a = this.ejectionTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a); |
|
} |
|
runChecks() { |
|
const ejectionTimestamp = new Date(); |
|
trace('Ejection timer running'); |
|
this.switchAllBuckets(); |
|
if (!this.latestConfig) { |
|
return; |
|
} |
|
this.timerStartTime = ejectionTimestamp; |
|
this.startTimer(this.latestConfig.getIntervalMs()); |
|
this.runSuccessRateCheck(ejectionTimestamp); |
|
this.runFailurePercentageCheck(ejectionTimestamp); |
|
for (const [address, mapEntry] of this.entryMap.entries()) { |
|
if (mapEntry.currentEjectionTimestamp === null) { |
|
if (mapEntry.ejectionTimeMultiplier > 0) { |
|
mapEntry.ejectionTimeMultiplier -= 1; |
|
} |
|
} |
|
else { |
|
const baseEjectionTimeMs = this.latestConfig.getBaseEjectionTimeMs(); |
|
const maxEjectionTimeMs = this.latestConfig.getMaxEjectionTimeMs(); |
|
const returnTime = new Date(mapEntry.currentEjectionTimestamp.getTime()); |
|
returnTime.setMilliseconds(returnTime.getMilliseconds() + |
|
Math.min(baseEjectionTimeMs * mapEntry.ejectionTimeMultiplier, Math.max(baseEjectionTimeMs, maxEjectionTimeMs))); |
|
if (returnTime < new Date()) { |
|
trace('Unejecting ' + address); |
|
this.uneject(mapEntry); |
|
} |
|
} |
|
} |
|
} |
|
updateAddressList(endpointList, lbConfig, options) { |
|
if (!(lbConfig instanceof OutlierDetectionLoadBalancingConfig)) { |
|
return; |
|
} |
|
trace('Received update with config: ' + JSON.stringify(lbConfig.toJsonObject(), undefined, 2)); |
|
for (const endpoint of endpointList) { |
|
if (!this.entryMap.has(endpoint)) { |
|
trace('Adding map entry for ' + (0, subchannel_address_1.endpointToString)(endpoint)); |
|
this.entryMap.set(endpoint, { |
|
counter: new CallCounter(), |
|
currentEjectionTimestamp: null, |
|
ejectionTimeMultiplier: 0, |
|
subchannelWrappers: [], |
|
}); |
|
} |
|
} |
|
this.entryMap.deleteMissing(endpointList); |
|
const childPolicy = lbConfig.getChildPolicy(); |
|
this.childBalancer.updateAddressList(endpointList, childPolicy, options); |
|
if (lbConfig.getSuccessRateEjectionConfig() || |
|
lbConfig.getFailurePercentageEjectionConfig()) { |
|
if (this.timerStartTime) { |
|
trace('Previous timer existed. Replacing timer'); |
|
clearTimeout(this.ejectionTimer); |
|
const remainingDelay = lbConfig.getIntervalMs() - |
|
(new Date().getTime() - this.timerStartTime.getTime()); |
|
this.startTimer(remainingDelay); |
|
} |
|
else { |
|
trace('Starting new timer'); |
|
this.timerStartTime = new Date(); |
|
this.startTimer(lbConfig.getIntervalMs()); |
|
this.switchAllBuckets(); |
|
} |
|
} |
|
else { |
|
trace('Counting disabled. Cancelling timer.'); |
|
this.timerStartTime = null; |
|
clearTimeout(this.ejectionTimer); |
|
for (const mapEntry of this.entryMap.values()) { |
|
this.uneject(mapEntry); |
|
mapEntry.ejectionTimeMultiplier = 0; |
|
} |
|
} |
|
this.latestConfig = lbConfig; |
|
} |
|
exitIdle() { |
|
this.childBalancer.exitIdle(); |
|
} |
|
resetBackoff() { |
|
this.childBalancer.resetBackoff(); |
|
} |
|
destroy() { |
|
clearTimeout(this.ejectionTimer); |
|
this.childBalancer.destroy(); |
|
} |
|
getTypeName() { |
|
return TYPE_NAME; |
|
} |
|
} |
|
exports.OutlierDetectionLoadBalancer = OutlierDetectionLoadBalancer; |
|
function setup() { |
|
if (OUTLIER_DETECTION_ENABLED) { |
|
(0, experimental_1.registerLoadBalancerType)(TYPE_NAME, OutlierDetectionLoadBalancer, OutlierDetectionLoadBalancingConfig); |
|
} |
|
} |
|
|