@fly/edge
Version:
Fly's TypeScript Edge
225 lines (224 loc) • 26.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports._internal = exports.syncBackends = void 0;
/**
* A fetch function load balancer. Distributes requests to a set of backends; attempts to
* send requests to most recently healthy backends using a 2 random (pick two healthiest,
* randomize which gets requests).
*
* If all backends are healthy, tries to evenly distribute requests as much as possible.
*
* When backends return server errors (500-599) it retries idempotent requests
* until it gets a good response, or all backends have been tried.
*
* @param backends fetch functions for each backend to balance accross
* @returns a function that behaves just like fetch, with a `.backends` property for
* retrieving backend stats.
*/
function balancer(backends) {
let tracked = syncBackends([], backends);
const fn = async function fetchBalancer(req, init) {
if (typeof req === "string") {
req = new Request(req);
}
const url = new URL(req.url);
let trackLatency = url.pathname === "/"; // this would be configurable in real life
const attempted = new Set();
while (attempted.size < tracked.length) {
let backend = null;
const [backendA, backendB] = chooseBackends(tracked, attempted);
if (!backendA) {
return new Response("No backend available", { status: 502 });
}
if (!backendB) {
backend = backendA;
}
else {
// randomize between 2 good candidates
backend = (Math.floor(Math.random() * 2) == 0) ? backendA : backendB;
}
const promise = backend.proxy(req, init);
if (backend.scoredRequestCount != backend.requestCount) {
// fixup score
// this should be relatively concurrent with the fetch promise
scoreHealth(backend);
}
backend.requestCount += 1;
attempted.add(backend);
const start = Date.now();
let resp;
try {
resp = await promise;
}
catch (e) {
resp = proxyError;
trackLatency = false;
}
setFixedArrayValue(backend.statuses, resp.status, 10, backend.requestCount);
if (trackLatency) {
const ms = Date.now() - start;
setFixedArrayValue(backend.latencies, ms, 10, backend.requestCount);
scoreLatency(backend);
}
// save backend stats every 3s
/*if(!backend.lastSaved || (Date.now() - backend.lastSaved) > 3000){
}*/
if (resp.status >= 500 && resp.status < 600) {
backend.lastError = Date.now();
// always recompute score on errors
scoreHealth(backend);
// clear out response to trigger retry
if (canRetry(req, resp)) {
continue;
}
}
return resp;
}
return proxyError;
};
const balancer = Object.assign(fn, {
backends: tracked,
updateBackends: (backends) => balancer.backends = tracked = syncBackends(tracked, backends)
});
return balancer;
}
exports.default = balancer;
const proxyError = new Response("couldn't connect to origin", { status: 502 });
function syncBackends(current, replacements) {
const idx = new Map();
for (const b of current) {
idx.set(b.proxy, b);
}
const updated = [];
for (const fn of replacements) {
if (typeof fn !== "function") {
throw Error("Backend must be a fetch like function");
}
const b = idx.get(fn) || {
proxy: fn,
requestCount: 0,
scoredRequestCount: 0,
statuses: Array(10),
latencies: Array(10),
lastError: 0,
healthScore: 1,
latencyScore: 1,
errorCount: 0
};
updated.push(b);
}
return updated;
}
exports.syncBackends = syncBackends;
// compute a backend health score with time + status codes
function scoreHealth(backend, errorBasis) {
if (typeof errorBasis !== "number" && !errorBasis)
errorBasis = Date.now();
const timeSinceError = (errorBasis - backend.lastError);
const statuses = backend.statuses;
const timeWeight = (backend.lastError === 0 && 0) ||
((timeSinceError < 1000) && 1) ||
((timeSinceError < 3000) && 0.8) ||
((timeSinceError < 5000) && 0.3) ||
((timeSinceError < 10000) && 0.1) ||
0;
if (statuses.length == 0)
return 0;
let requests = 0;
let errors = 0;
for (let i = 0; i < statuses.length; i++) {
const status = statuses[i];
if (status && !isNaN(status)) {
requests += 1;
if (status >= 500 && status < 600) {
errors += 1;
}
}
}
const healthScore = (1 - (timeWeight * (errors / requests)));
backend.healthScore = healthScore;
backend.scoredRequestCount = backend.requestCount;
return healthScore;
}
function scoreLatency(backend) {
let total = 0;
for (const l of backend.latencies) {
total += l;
}
const avgLatency = total / backend.latencies.length;
backend.latencyScore = orderOfMagnitude(avgLatency);
return backend.latencyScore;
}
function canRetry(req, resp) {
if (resp && resp.status < 500)
return false; // don't retry normal boring errors or success
if (req.method == "GET" || req.method == "HEAD")
return true;
return false;
}
function chooseBackends(backends, attempted) {
let b1;
let b2;
for (let i = 0; i < backends.length; i++) {
const b = backends[i];
if (attempted && attempted.has(b))
continue;
if (!b1) {
b1 = b;
continue;
}
if (!b2) {
b2 = b;
continue;
}
const old1 = b1;
b1 = bestBackend(b, b1);
if (old1 != b1) {
// b1 got replaced, make sure it's not better
b2 = bestBackend(old1, b2);
}
else {
b2 = bestBackend(b, b2);
}
}
// if two best backends have different latency, use only the fastest one
if (b1 && b2 && b1.latencyScore < b2.latencyScore)
return [b1];
if (b1 && b2 && b2.latencyScore < b1.latencyScore)
return [b2];
return [b1, b2];
}
function bestBackend(b1, b2) {
// simple health check before we compare latency
if (b1.healthScore < 0.85 && b2.healthScore > 0.85) {
return b2;
}
if (b2.healthScore < 0.85 && b1.healthScore > 0.85) {
return b1;
}
if (b1.latencyScore < b2.latencyScore ||
(b1.latencyScore == b2.latencyScore && b1.requestCount < b2.requestCount)) {
return b1;
}
return b2;
}
function setFixedArrayValue(arr, value, maxLength, totalCount) {
if (arr.length < maxLength) {
arr.push(value);
}
else {
arr[(totalCount - 1) % arr.length] = value;
}
}
function orderOfMagnitude(value) {
//https://stackoverflow.com/questions/23917074/javascript-flooring-number-to-order-of-magnitude
const order = Math.floor(Math.log(value) / Math.LN10);
return Math.pow(10, order);
}
/** @private */
exports._internal = {
chooseBackends,
scoreHealth,
scoreLatency
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFsYW5jZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYmFsYW5jZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBRUE7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILFNBQXdCLFFBQVEsQ0FBQyxRQUF5QjtJQUN4RCxJQUFJLE9BQU8sR0FBRyxZQUFZLENBQUMsRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFBO0lBRXhDLE1BQU0sRUFBRSxHQUFHLEtBQUssVUFBVSxhQUFhLENBQUMsR0FBZ0IsRUFBRSxJQUE4QjtRQUN0RixJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRTtZQUMzQixHQUFHLEdBQUcsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7U0FDdkI7UUFDRCxNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDNUIsSUFBSSxZQUFZLEdBQUcsR0FBRyxDQUFDLFFBQVEsS0FBSyxHQUFHLENBQUEsQ0FBQywwQ0FBMEM7UUFDbEYsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQVcsQ0FBQTtRQUNwQyxPQUFPLFNBQVMsQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sRUFBRTtZQUN0QyxJQUFJLE9BQU8sR0FBbUIsSUFBSSxDQUFBO1lBQ2xDLE1BQU0sQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLEdBQUcsY0FBYyxDQUFDLE9BQU8sRUFBRSxTQUFTLENBQUMsQ0FBQTtZQUUvRCxJQUFJLENBQUMsUUFBUSxFQUFFO2dCQUNiLE9BQU8sSUFBSSxRQUFRLENBQUMsc0JBQXNCLEVBQUUsRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQTthQUM3RDtZQUNELElBQUksQ0FBQyxRQUFRLEVBQUU7Z0JBQ2IsT0FBTyxHQUFHLFFBQVEsQ0FBQTthQUNuQjtpQkFBTTtnQkFDTCxzQ0FBc0M7Z0JBQ3RDLE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxL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QUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEtBQUssQ0FBQTtLQUMxQztBQUNILENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLEtBQWE7SUFDckMsK0ZBQStGO0lBQy9GLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDckQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQTtBQUM1QixDQUFDO0FBRUQsZUFBZTtBQUNGLFFBQUEsU0FBUyxHQUFHO0lBQ3ZCLGNBQWM7SUFDZCxXQUFXO0lBQ1gsWUFBWTtDQUNiLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBGZXRjaEZ1bmN0aW9uIH0gZnJvbSBcIi4vZmV0Y2hcIjtcblxuLyoqXG4gKiBBIGZldGNoIGZ1bmN0aW9uIGxvYWQgYmFsYW5jZXIuIERpc3RyaWJ1dGVzIHJlcXVlc3RzIHRvIGEgc2V0IG9mIGJhY2tlbmRzOyBhdHRlbXB0cyB0byBcbiAqIHNlbmQgcmVxdWVzdHMgdG8gbW9zdCByZWNlbnRseSBoZWFsdGh5IGJhY2tlbmRzIHVzaW5nIGEgMiByYW5kb20gKHBpY2sgdHdvIGhlYWx0aGllc3QsIFxuICogcmFuZG9taXplIHdoaWNoIGdldHMgcmVxdWVzdHMpLlxuICogXG4gKiBJZiBhbGwgYmFja2VuZHMgYXJlIGhlYWx0aHksIHRyaWVzIHRvIGV2ZW5seSBkaXN0cmlidXRlIHJlcXVlc3RzIGFzIG11Y2ggYXMgcG9zc2libGUuXG4gKiBcbiAqIFdoZW4gYmFja2VuZHMgcmV0dXJuIHNlcnZlciBlcnJvcnMgKDUwMC01OTkpIGl0IHJldHJpZXMgaWRlbXBvdGVudCByZXF1ZXN0c1xuICogIHVudGlsIGl0IGdldHMgYSBnb29kIHJlc3BvbnNlLCBvciBhbGwgYmFja2VuZHMgaGF2ZSBiZWVuIHRyaWVkLlxuICogXG4gKiBAcGFyYW0gYmFja2VuZHMgZmV0Y2ggZnVuY3Rpb25zIGZvciBlYWNoIGJhY2tlbmQgdG8gYmFsYW5jZSBhY2Nyb3NzXG4gKiBAcmV0dXJucyBhIGZ1bmN0aW9uIHRoYXQgYmVoYXZlcyBqdXN0IGxpa2UgZmV0Y2gsIHdpdGggYSBgLmJhY2tlbmRzYCBwcm9wZXJ0eSBmb3IgXG4gKiByZXRyaWV2aW5nIGJhY2tlbmQgc3RhdHMuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIGJhbGFuY2VyKGJhY2tlbmRzOiBGZXRjaEZ1bmN0aW9uW10pIHtcbiAgbGV0IHRyYWNrZWQgPSBzeW5jQmFja2VuZHMoW10sIGJhY2tlbmRzKVxuXG4gIGNvbnN0IGZuID0gYXN5bmMgZnVuY3Rpb24gZmV0Y2hCYWxhbmNlcihyZXE6IFJlcXVlc3RJbmZvLCBpbml0PzogUmVxdWVzdEluaXQgfCB1bmRlZmluZWQpOiBQcm9taXNlPFJlc3BvbnNlPiB7XG4gICAgaWYgKHR5cGVvZiByZXEgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHJlcSA9IG5ldyBSZXF1ZXN0KHJlcSlcbiAgICB9XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKVxuICAgIGxldCB0cmFja0xhdGVuY3kgPSB1cmwucGF0aG5hbWUgPT09IFwiL1wiIC8vIHRoaXMgd291bGQgYmUgY29uZmlndXJhYmxlIGluIHJlYWwgbGlmZVxuICAgIGNvbnN0IGF0dGVtcHRlZCA9IG5ldyBTZXQ8QmFja2VuZD4oKVxuICAgIHdoaWxlIChhdHRlbXB0ZWQuc2l6ZSA8IHRyYWNrZWQubGVuZ3RoKSB7XG4gICAgICBsZXQgYmFja2VuZDogQmFja2VuZCB8IG51bGwgPSBudWxsXG4gICAgICBjb25zdCBbYmFja2VuZEEsIGJhY2tlbmRCXSA9IGNob29zZUJhY2tlbmRzKHRyYWNrZWQsIGF0dGVtcHRlZClcblxuICAgICAgaWYgKCFiYWNrZW5kQSkge1xuICAgICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKFwiTm8gYmFja2VuZCBhdmFpbGFibGVcIiwgeyBzdGF0dXM6IDUwMiB9KVxuICAgICAgfVxuICAgICAgaWYgKCFiYWNrZW5kQikge1xuICAgICAgICBiYWNrZW5kID0gYmFja2VuZEFcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIHJhbmRvbWl6ZSBiZXR3ZWVuIDIgZ29vZCBjYW5kaWRhdGVzXG4gICAgICAgIGJhY2tlbmQgPSAoTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMikgPT0gMCkgPyBiYWNrZW5kQSA6IGJhY2tlbmRCXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHByb21pc2UgPSBiYWNrZW5kLnByb3h5KHJlcSwgaW5pdClcbiAgICAgIGlmIChiYWNrZW5kLnNjb3JlZFJlcXVlc3RDb3VudCAhPSBiYWNrZW5kLnJlcXVlc3RDb3VudCkge1xuICAgICAgICAvLyBmaXh1cCBzY29yZVxuICAgICAgICAvLyB0aGlzIHNob3VsZCBiZSByZWxhdGl2ZWx5IGNvbmN1cnJlbnQgd2l0aCB0aGUgZmV0Y2ggcHJvbWlzZVxuICAgICAgICBzY29yZUhlYWx0aChiYWNrZW5kKVxuICAgICAgfVxuICAgICAgYmFja2VuZC5yZXF1ZXN0Q291bnQgKz0gMVxuICAgICAgYXR0ZW1wdGVkLmFkZChiYWNrZW5kKVxuXG4gICAgICBjb25zdCBzdGFydCA9IERhdGUubm93KClcbiAgICAgIGxldCByZXNwOiBSZXNwb25zZVxuICAgICAgdHJ5IHtcbiAgICAgICAgcmVzcCA9IGF3YWl0IHByb21pc2VcbiAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgcmVzcCA9IHByb3h5RXJyb3JcbiAgICAgICAgdHJhY2tMYXRlbmN5ID0gZmFsc2VcbiAgICAgIH1cbiAgICAgIHNldEZpeGVkQXJyYXlWYWx1ZShiYWNrZW5kLnN0YXR1c2VzLCByZXNwLnN0YXR1cywgMTAsIGJhY2tlbmQucmVxdWVzdENvdW50KVxuXG4gICAgICBpZih0cmFja0xhdGVuY3kpe1xuICAgICAgICBjb25zdCBtcyA9IERhdGUubm93KCkgLSBzdGFydFxuICAgICAgICBzZXRGaXhlZEFycmF5VmFsdWUoYmFja2VuZC5sYXRlbmNpZXMsIG1zLCAxMCwgYmFja2VuZC5yZXF1ZXN0Q291bnQpXG4gICAgICAgIHNjb3JlTGF0ZW5jeShiYWNrZW5kKVxuICAgICAgfVxuXG4gICAgICAvLyBzYXZlIGJhY2tlbmQgc3RhdHMgZXZlcnkgM3NcbiAgICAgIC8qaWYoIWJhY2tlbmQubGFzdFNhdmVkIHx8IChEYXRlLm5vdygpIC0gYmFja2VuZC5sYXN0U2F2ZWQpID4gMzAwMCl7XG5cbiAgICAgIH0qL1xuXG4gICAgICBpZiAocmVzcC5zdGF0dXMgPj0gNTAwICYmIHJlc3Auc3RhdHVzIDwgNjAwKSB7XG4gICAgICAgIGJhY2tlbmQubGFzdEVycm9yID0gRGF0ZS5ub3coKVxuICAgICAgICAvLyBhbHdheXMgcmVjb21wdXRlIHNjb3JlIG9uIGVycm9yc1xuICAgICAgICBzY29yZUhlYWx0aChiYWNrZW5kKVxuXG4gICAgICAgIC8vIGNsZWFyIG91dCByZXNwb25zZSB0byB0cmlnZ2VyIHJldHJ5XG4gICAgICAgIGlmIChjYW5SZXRyeShyZXEsIHJlc3ApKSB7XG4gICAgICAgICAgY29udGludWVcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gcmVzcFxuICAgIH1cblxuICAgIHJldHVybiBwcm94eUVycm9yXG4gIH1cblxuICBjb25zdCBiYWxhbmNlciA9IE9iamVjdC5hc3NpZ24oZm4sIHtcbiAgICBiYWNrZW5kczogdHJhY2tlZCxcbiAgICB1cGRhdGVCYWNrZW5kczogKGJhY2tlbmRzOiBGZXRjaEZ1bmN0aW9uW10pID0+IGJhbGFuY2VyLmJhY2tlbmRzID0gdHJhY2tlZCA9IHN5bmNCYWNrZW5kcyh0cmFja2VkLCBiYWNrZW5kcylcbiAgfSlcblxuICByZXR1cm4gYmFsYW5jZXI7XG59XG5jb25zdCBwcm94eUVycm9yID0gbmV3IFJlc3BvbnNlKFwiY291bGRuJ3QgY29ubmVjdCB0byBvcmlnaW5cIiwgeyBzdGF0dXM6IDUwMiB9KVxuXG5leHBvcnQgZnVuY3Rpb24gc3luY0JhY2tlbmRzKGN1cnJlbnQ6IEJhY2tlbmRbXSwgcmVwbGFjZW1lbnRzOiBGZXRjaEZ1bmN0aW9uW10pe1xuICBjb25zdCBpZHggPSBuZXcgTWFwPEZldGNoRnVuY3Rpb24sIEJhY2tlbmQ+KClcbiAgZm9yKGNvbnN0IGIgb2YgY3VycmVudCl7XG4gICAgaWR4LnNldChiLnByb3h5LCBiKVxuICB9XG5cbiAgY29uc3QgdXBkYXRlZDogQmFja2VuZFtdID0gW11cblxuICBmb3IoY29uc3QgZm4gb2YgcmVwbGFjZW1lbnRzKXtcbiAgICBpZiAodHlwZW9mIGZuICE9PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgIHRocm93IEVycm9yKFwiQmFja2VuZCBtdXN0IGJlIGEgZmV0Y2ggbGlrZSBmdW5jdGlvblwiKVxuICAgIH1cbiAgICBjb25zdCBiID0gaWR4LmdldChmbikgfHwge1xuICAgICAgcHJveHk6IGZuLFxuICAgICAgcmVxdWVzdENvdW50OiAwLFxuICAgICAgc2NvcmVkUmVxdWVzdENvdW50OiAwLFxuICAgICAgc3RhdHVzZXM6IEFycmF5PG51bWJlcj4oMTApLFxuICAgICAgbGF0ZW5jaWVzOiBBcnJheTxudW1iZXI+KDEwKSxcbiAgICAgIGxhc3RFcnJvcjogMCxcbiAgICAgIGhlYWx0aFNjb3JlOiAxLFxuICAgICAgbGF0ZW5jeVNjb3JlOiAxLFxuICAgICAgZXJyb3JDb3VudDogMFxuICAgIH1cblxuICAgIHVwZGF0ZWQucHVzaChiKVxuICB9XG4gIHJldHVybiB1cGRhdGVkO1xufVxuXG4vKipcbiAqIFJlcHJlc2VudHMgYSBiYWNrZW5kIHdpdGggaGVhbHRoIGFuZCBzdGF0aXN0aWNzLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEJhY2tlbmQge1xuICBwcm94eTogKHJlcTogUmVxdWVzdEluZm8sIGluaXQ/OiBSZXF1ZXN0SW5pdCB8IHVuZGVmaW5lZCkgPT4gUHJvbWlzZTxSZXNwb25zZT4sXG4gIHJlcXVlc3RDb3VudDogMCxcbiAgc2NvcmVkUmVxdWVzdENvdW50OiAwLFxuICBzdGF0dXNlczogbnVtYmVyW10sXG4gIGxhdGVuY2llczogbnVtYmVyW10sXG4gIGxhc3RFcnJvcjogbnVtYmVyLFxuICBoZWFsdGhTY29yZTogbnVtYmVyLFxuICBsYXRlbmN5U2NvcmU6IG51bWJlcixcbiAgZXJyb3JDb3VudDogMCxcbiAgbGFzdFNhdmVkPzogbnVtYmVyXG59XG4vLyBjb21wdXRlIGEgYmFja2VuZCBoZWFsdGggc2NvcmUgd2l0aCB0aW1lICsgc3RhdHVzIGNvZGVzXG5mdW5jdGlvbiBzY29yZUhlYWx0aChiYWNrZW5kOiBCYWNrZW5kLCBlcnJvckJhc2lzPzogbnVtYmVyKSB7XG4gIGlmICh0eXBlb2YgZXJyb3JCYXNpcyAhPT0gXCJudW1iZXJcIiAmJiAhZXJyb3JCYXNpcykgZXJyb3JCYXNpcyA9IERhdGUubm93KClcblxuICBjb25zdCB0aW1lU2luY2VFcnJvciA9IChlcnJvckJhc2lzIC0gYmFja2VuZC5sYXN0RXJyb3IpXG4gIGNvbnN0IHN0YXR1c2VzID0gYmFja2VuZC5zdGF0dXNlc1xuICBjb25zdCB0aW1lV2VpZ2h0ID0gKGJhY2tlbmQubGFzdEVycm9yID09PSAwICYmIDApIHx8XG4gICAgKCh0aW1lU2luY2VFcnJvciA8IDEwMDApICYmIDEpIHx8XG4gICAgKCh0aW1lU2luY2VFcnJvciA8IDMwMDApICYmIDAuOCkgfHxcbiAgICAoKHRpbWVTaW5jZUVycm9yIDwgNTAwMCkgJiYgMC4zKSB8fFxuICAgICgodGltZVNpbmNlRXJyb3IgPCAxMDAwMCkgJiYgMC4xKSB8fFxuICAgIDA7XG4gIGlmIChzdGF0dXNlcy5sZW5ndGggPT0gMCkgcmV0dXJuIDBcbiAgbGV0IHJlcXVlc3RzID0gMFxuICBsZXQgZXJyb3JzID0gMFxuICBmb3IgKGxldCBpID0gMDsgaSA8IHN0YXR1c2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3Qgc3RhdHVzID0gc3RhdHVzZXNbaV1cbiAgICBpZiAoc3RhdHVzICYmICFpc05hTihzdGF0dXMpKSB7XG4gICAgICByZXF1ZXN0cyArPSAxXG4gICAgICBpZiAoc3RhdHVzID49IDUwMCAmJiBzdGF0dXMgPCA2MDApIHtcbiAgICAgICAgZXJyb3JzICs9IDFcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgY29uc3QgaGVhbHRoU2NvcmUgPSAoMSAtICh0aW1lV2VpZ2h0ICogKGVycm9ycyAvIHJlcXVlc3RzKSkpXG4gIGJhY2tlbmQuaGVhbHRoU2NvcmUgPSBoZWFsdGhTY29yZVxuICBiYWNrZW5kLnNjb3JlZFJlcXVlc3RDb3VudCA9IGJhY2tlbmQucmVxdWVzdENvdW50XG4gIHJldHVybiBoZWFsdGhTY29yZVxufVxuZnVuY3Rpb24gc2NvcmVMYXRlbmN5KGJhY2tlbmQ6IEJhY2tlbmQpe1xuICBsZXQgdG90YWwgPSAwXG4gIGZvcihjb25zdCBsIG9mIGJhY2tlbmQubGF0ZW5jaWVzKXtcbiAgICB0b3RhbCArPSBsXG4gIH1cbiAgY29uc3QgYXZnTGF0ZW5jeSA9IHRvdGFsIC8gYmFja2VuZC5sYXRlbmNpZXMubGVuZ3RoXG5cbiAgYmFja2VuZC5sYXRlbmN5U2NvcmUgPSBvcmRlck9mTWFnbml0dWRlKGF2Z0xhdGVuY3kpXG4gIHJldHVybiBiYWNrZW5kLmxhdGVuY3lTY29yZVxufVxuZnVuY3Rpb24gY2FuUmV0cnkocmVxOiBSZXF1ZXN0LCByZXNwOiBSZXNwb25zZSkge1xuICBpZiAocmVzcCAmJiByZXNwLnN0YXR1cyA8IDUwMCkgcmV0dXJuIGZhbHNlIC8vIGRvbid0IHJldHJ5IG5vcm1hbCBib3JpbmcgZXJyb3JzIG9yIHN1Y2Nlc3NcbiAgaWYgKHJlcS5tZXRob2QgPT0gXCJHRVRcIiB8fCByZXEubWV0aG9kID09IFwiSEVBRFwiKSByZXR1cm4gdHJ1ZVxuICByZXR1cm4gZmFsc2Vcbn1cblxuZnVuY3Rpb24gY2hvb3NlQmFja2VuZHMoYmFja2VuZHM6IEJhY2tlbmRbXSwgYXR0ZW1wdGVkPzogU2V0PEJhY2tlbmQ+KSB7XG4gIGxldCBiMTogQmFja2VuZCB8IHVuZGVmaW5lZFxuICBsZXQgYjI6IEJhY2tlbmQgfCB1bmRlZmluZWRcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBiYWNrZW5kcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGIgPSBiYWNrZW5kc1tpXVxuICAgIGlmIChhdHRlbXB0ZWQgJiYgYXR0ZW1wdGVkLmhhcyhiKSkgY29udGludWU7XG5cbiAgICBpZiAoIWIxKSB7XG4gICAgICBiMSA9IGJcbiAgICAgIGNvbnRpbnVlXG4gICAgfVxuICAgIGlmICghYjIpIHtcbiAgICAgIGIyID0gYlxuICAgICAgY29udGludWVcbiAgICB9XG5cbiAgICBjb25zdCBvbGQxID0gYjFcbiAgICBiMSA9IGJlc3RCYWNrZW5kKGIsIGIxKVxuXG4gICAgaWYgKG9sZDEgIT0gYjEpIHtcbiAgICAgIC8vIGIxIGdvdCByZXBsYWNlZCwgbWFrZSBzdXJlIGl0J3Mgbm90IGJldHRlclxuICAgICAgYjIgPSBiZXN0QmFja2VuZChvbGQxLCBiMilcbiAgICB9IGVsc2Uge1xuICAgICAgYjIgPSBiZXN0QmFja2VuZChiLCBiMilcbiAgICB9XG4gIH1cblxuICAvLyBpZiB0d28gYmVzdCBiYWNrZW5kcyBoYXZlIGRpZmZlcmVudCBsYXRlbmN5LCB1c2Ugb25seSB0aGUgZmFzdGVzdCBvbmVcbiAgaWYoYjEgJiYgYjIgJiYgYjEubGF0ZW5jeVNjb3JlIDwgYjIubGF0ZW5jeVNjb3JlKSByZXR1cm4gW2IxXVxuICBpZihiMSAmJiBiMiAmJiBiMi5sYXRlbmN5U2NvcmUgPCBiMS5sYXRlbmN5U2NvcmUpIHJldHVybiBbYjJdXG4gIFxuICByZXR1cm4gW2IxLCBiMl1cbn1cblxuZnVuY3Rpb24gYmVzdEJhY2tlbmQoYjE6IEJhY2tlbmQsIGIyOiBCYWNrZW5kKSB7XG4gIC8vIHNpbXBsZSBoZWFsdGggY2hlY2sgYmVmb3JlIHdlIGNvbXBhcmUgbGF0ZW5jeVxuICBpZihiMS5oZWFsdGhTY29yZSA8IDAuODUgJiYgYjIuaGVhbHRoU2NvcmUgPiAwLjg1KXtcbiAgICByZXR1cm4gYjJcbiAgfVxuICBpZihiMi5oZWFsdGhTY29yZSA8IDAuODUgJiYgYjEuaGVhbHRoU2NvcmUgPiAwLjg1KXtcbiAgICByZXR1cm4gYjFcbiAgfVxuICBpZiAoXG4gICAgYjEubGF0ZW5jeVNjb3JlIDwgYjIubGF0ZW5jeVNjb3JlIHx8XG4gICAgKGIxLmxhdGVuY3lTY29yZSA9PSBiMi5sYXRlbmN5U2NvcmUgJiYgYjEucmVxdWVzdENvdW50IDwgYjIucmVxdWVzdENvdW50KVxuICApIHtcbiAgICByZXR1cm4gYjFcbiAgfVxuICByZXR1cm4gYjJcbn1cblxuZnVuY3Rpb24gc2V0Rml4ZWRBcnJheVZhbHVlPFQ+KGFycjogVFtdLCB2YWx1ZTogVCwgbWF4TGVuZ3RoOiBudW1iZXIsIHRvdGFsQ291bnQ6IG51bWJlcil7XG4gIGlmKGFyci5sZW5ndGggPCBtYXhMZW5ndGgpe1xuICAgIGFyci5wdXNoKHZhbHVlKVxuICB9ZWxzZXtcbiAgICBhcnJbKHRvdGFsQ291bnQtIDEpICUgYXJyLmxlbmd0aF0gPSB2YWx1ZVxuICB9XG59XG5cbmZ1bmN0aW9uIG9yZGVyT2ZNYWduaXR1ZGUodmFsdWU6IG51bWJlcil7XG4gIC8vaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjM5MTcwNzQvamF2YXNjcmlwdC1mbG9vcmluZy1udW1iZXItdG8tb3JkZXItb2YtbWFnbml0dWRlXG4gIGNvbnN0IG9yZGVyID0gTWF0aC5mbG9vcihNYXRoLmxvZyh2YWx1ZSkgLyBNYXRoLkxOMTApXG4gIHJldHVybiBNYXRoLnBvdygxMCwgb3JkZXIpXG59XG5cbi8qKiBAcHJpdmF0ZSAqL1xuZXhwb3J0IGNvbnN0IF9pbnRlcm5hbCA9IHtcbiAgY2hvb3NlQmFja2VuZHMsXG4gIHNjb3JlSGVhbHRoLFxuICBzY29yZUxhdGVuY3lcbn0iXX0=