Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
162 lines
4.6 KiB
JavaScript
162 lines
4.6 KiB
JavaScript
"use strict";
|
|
|
|
const dns = require("node:dns");
|
|
|
|
const DEFAULT_TIMEOUT_MS = 3000;
|
|
const DEFAULT_CACHE_TTL_MS = 30000;
|
|
const DEFAULT_FAILURE_CACHE_TTL_MS = 5000;
|
|
const MAX_CACHE_ENTRIES = 256;
|
|
const MAX_CONCURRENT_PROBES = 8;
|
|
const MAX_QUEUED_PROBES = 64;
|
|
|
|
function createTcpConnectLatencyProbe({
|
|
net,
|
|
lookup = dns.lookup,
|
|
now = () => performance.now(),
|
|
cacheNow = () => Date.now(),
|
|
cacheTtlMs = DEFAULT_CACHE_TTL_MS,
|
|
failureCacheTtlMs = DEFAULT_FAILURE_CACHE_TTL_MS,
|
|
maxCacheEntries = MAX_CACHE_ENTRIES,
|
|
maxConcurrentProbes = MAX_CONCURRENT_PROBES,
|
|
maxQueuedProbes = MAX_QUEUED_PROBES,
|
|
setTimer = setTimeout,
|
|
clearTimer = clearTimeout,
|
|
}) {
|
|
const resultCache = new Map();
|
|
const inFlight = new Map();
|
|
const queue = [];
|
|
let activeProbeCount = 0;
|
|
const concurrentLimit = Math.max(1, Math.floor(maxConcurrentProbes));
|
|
const queueLimit = Math.max(0, Math.floor(maxQueuedProbes));
|
|
|
|
function runProbe({ hostname, port, timeoutMs }) {
|
|
return new Promise((resolve) => {
|
|
let startedAt = net.isIP?.(hostname) ? now() : null;
|
|
let settled = false;
|
|
let socket = null;
|
|
|
|
const finish = (latencyMs) => {
|
|
if (settled) return;
|
|
settled = true;
|
|
try { socket?.destroy(); } catch { /* best-effort cleanup */ }
|
|
resolve(latencyMs);
|
|
};
|
|
|
|
try {
|
|
const connectOptions = { host: hostname, port };
|
|
if (startedAt === null) {
|
|
connectOptions.lookup = (host, options, callback) => {
|
|
lookup(host, options, (err, address, family) => {
|
|
startedAt = now();
|
|
callback(err, address, family);
|
|
});
|
|
};
|
|
}
|
|
socket = net.createConnection(connectOptions, () => {
|
|
const connectedAt = now();
|
|
finish(Math.max(0, Math.round(connectedAt - (startedAt ?? connectedAt))));
|
|
});
|
|
socket.once("error", () => finish(null));
|
|
socket.setTimeout(timeoutMs, () => finish(null));
|
|
} catch {
|
|
finish(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
function startScheduledProbe(task) {
|
|
activeProbeCount += 1;
|
|
let result;
|
|
try {
|
|
result = Promise.resolve(task());
|
|
} catch (err) {
|
|
result = Promise.reject(err);
|
|
}
|
|
return result.finally(() => {
|
|
activeProbeCount -= 1;
|
|
while (activeProbeCount < concurrentLimit && queue.length > 0) {
|
|
const next = queue.shift();
|
|
clearTimer(next.timer);
|
|
const remainingMs = next.deadline - cacheNow();
|
|
if (remainingMs <= 0) {
|
|
next.resolve(null);
|
|
continue;
|
|
}
|
|
startScheduledProbe(() => next.task(remainingMs)).then(next.resolve, next.reject);
|
|
}
|
|
});
|
|
}
|
|
|
|
function scheduleProbe(task, timeoutMs) {
|
|
if (activeProbeCount < concurrentLimit) return startScheduledProbe(() => task(timeoutMs));
|
|
if (queue.length >= queueLimit) return Promise.resolve(null);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const entry = {
|
|
task,
|
|
resolve,
|
|
reject,
|
|
deadline: cacheNow() + timeoutMs,
|
|
timer: null,
|
|
};
|
|
entry.timer = setTimer(() => {
|
|
const index = queue.indexOf(entry);
|
|
if (index >= 0) queue.splice(index, 1);
|
|
resolve(null);
|
|
}, timeoutMs);
|
|
queue.push(entry);
|
|
});
|
|
}
|
|
|
|
function cacheResult(key, value) {
|
|
resultCache.delete(key);
|
|
resultCache.set(key, {
|
|
value,
|
|
expiresAt: cacheNow() + (value === null ? failureCacheTtlMs : cacheTtlMs),
|
|
});
|
|
while (resultCache.size > maxCacheEntries) {
|
|
resultCache.delete(resultCache.keys().next().value);
|
|
}
|
|
}
|
|
|
|
return function measureTcpConnectLatency({ hostname, port, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
if (!hostname || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
const key = `${String(hostname).toLowerCase()}\u0000${port}`;
|
|
const pending = inFlight.get(key);
|
|
if (pending) return pending;
|
|
|
|
const cached = resultCache.get(key);
|
|
const checkedAt = cacheNow();
|
|
if (cached && cached.expiresAt > checkedAt) {
|
|
resultCache.delete(key);
|
|
resultCache.set(key, cached);
|
|
return Promise.resolve(cached.value);
|
|
}
|
|
resultCache.delete(key);
|
|
|
|
const promise = scheduleProbe(
|
|
(remainingMs) => runProbe({ hostname, port, timeoutMs: remainingMs }),
|
|
timeoutMs,
|
|
)
|
|
.catch(() => null)
|
|
.then((value) => {
|
|
inFlight.delete(key);
|
|
cacheResult(key, value);
|
|
return value;
|
|
});
|
|
inFlight.set(key, promise);
|
|
return promise;
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
createTcpConnectLatencyProbe,
|
|
DEFAULT_TIMEOUT_MS,
|
|
DEFAULT_CACHE_TTL_MS,
|
|
MAX_CONCURRENT_PROBES,
|
|
MAX_QUEUED_PROBES,
|
|
};
|