@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
691 lines (690 loc) • 28.6 kB
JavaScript
import { ErrorFactory } from "../utils/errorHandling.js";
import { PROXY_SOCKET_OFFER_TIMEOUT, PROXY_SOCKET_COMMIT_TIMEOUT, } from "./rollingWorkerProtocol.js";
const DEFAULT_READY_TIMEOUT_MS = 120_000;
const DEFAULT_SOCKET_QUEUE_LIMIT = 1_024;
const DEFAULT_SOCKET_QUEUE_TIMEOUT_MS = 30_000;
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 30_000;
const DEFAULT_SOCKET_TRANSFER_TIMEOUT_MS = 60_000;
const MAX_RECENT_SUPERVISOR_EVENTS = 100;
/**
* Owns worker generations while the caller owns the public listening socket.
* Sockets are transferred once to the active worker, so response bytes never
* traverse a supervisor-side proxy hop.
*/
export class RollingWorkerSupervisor {
options;
generation = 0;
active = null;
candidate = null;
draining = new Map();
queuedSockets = [];
flushingSockets = false;
consecutiveOfferTimeouts = 0;
lastStallReplacementAt = 0;
transferStateTimer;
replacement = null;
rejectedSockets = 0;
failedTransfers = 0;
recentEvents = [];
lastFailure = null;
closed = false;
shutdownPromise = null;
shutdownWaiters = new Set();
constructor(options) {
this.options = {
...options,
readyTimeoutMs: options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS,
socketQueueLimit: options.socketQueueLimit ?? DEFAULT_SOCKET_QUEUE_LIMIT,
maxPendingTransfers: Math.max(1, options.maxPendingTransfers ?? 16),
socketQueueTimeoutMs: options.socketQueueTimeoutMs ?? DEFAULT_SOCKET_QUEUE_TIMEOUT_MS,
shutdownTimeoutMs: options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS,
};
}
snapshot() {
return {
generation: this.generation,
active: this.active
? {
pid: this.active.handle.pid,
version: this.active.version,
generation: this.active.generation,
}
: null,
candidate: this.candidate
? {
pid: this.candidate.handle.pid,
expectedVersion: this.candidate.expectedVersion,
generation: this.candidate.generation,
}
: null,
draining: [...this.draining.values()].map((worker) => ({
pid: worker.handle.pid,
version: worker.version,
generation: worker.generation,
})),
queuedSockets: this.queuedSockets.length,
pendingTransfers: (this.active?.pendingTransfers ?? 0) +
[...this.draining.values()].reduce((total, worker) => total + worker.pendingTransfers, 0),
rejectedSockets: this.rejectedSockets,
failedTransfers: this.failedTransfers,
recentEvents: [...this.recentEvents],
lastFailure: this.lastFailure,
};
}
start(expectedVersion) {
if (this.active) {
return this.active.version === expectedVersion
? Promise.resolve(this.snapshot())
: this.replace(expectedVersion);
}
return this.replace(expectedVersion);
}
replace(expectedVersion) {
if (this.closed) {
return Promise.reject(ErrorFactory.proxyWorkerLifecycle("rolling worker supervisor is closed"));
}
if (!/^\d+\.\d+\.\d+$/.test(expectedVersion)) {
return Promise.reject(ErrorFactory.proxyWorkerLifecycle(`invalid expected worker version: ${expectedVersion}`, { expectedVersion }));
}
if (this.replacement) {
return this.candidate?.expectedVersion === expectedVersion
? this.replacement
: Promise.reject(ErrorFactory.proxyWorkerLifecycle(`worker replacement for v${this.candidate?.expectedVersion ?? "unknown"} is already in progress`, { requestedVersion: expectedVersion }));
}
this.replacement = this.spawnCandidate(expectedVersion).finally(() => {
this.replacement = null;
});
return this.replacement;
}
/**
* Admit a paused socket to the active generation or the bounded
* readiness queue.
*/
acceptSocket(socket) {
socket.pause();
if (this.closed) {
this.rejectSocket(socket, undefined, undefined, "supervisor_closed");
return;
}
const admission = {
socket,
// Preserve the original offer + commit allowance. Queued sockets also
// retain their existing queue wait, but no retry ever renews this budget.
deadlineAt: Date.now() +
(this.active?.handle.socketTransferTimeoutMs ??
DEFAULT_SOCKET_TRANSFER_TIMEOUT_MS),
retried: false,
};
if (this.active &&
this.queuedSockets.length === 0 &&
this.active.pendingTransfers < this.options.maxPendingTransfers) {
this.transferSocket(this.active, admission);
return;
}
admission.deadlineAt += this.options.socketQueueTimeoutMs;
this.queueSocket(admission);
this.flushQueuedSockets();
}
/**
* Retain a socket only within the queue capacity and deadline, recording
* classified rejection.
*/
queueSocket(admission) {
const { socket } = admission;
if (this.queuedSockets.length >= this.options.socketQueueLimit) {
this.rejectSocket(socket, undefined, undefined, "queue_capacity");
return;
}
const queued = {
...admission,
timeout: setTimeout(() => {
const index = this.queuedSockets.indexOf(queued);
if (index >= 0) {
this.queuedSockets.splice(index, 1);
this.rejectSocket(socket, undefined, undefined, "queue_timeout");
}
}, Math.max(0, Math.min(this.options.socketQueueTimeoutMs, admission.deadlineAt - Date.now()))),
};
queued.timeout.unref?.();
this.queuedSockets.push(queued);
this.scheduleTransferState();
}
close() {
if (this.shutdownPromise) {
return this.shutdownPromise;
}
this.closed = true;
this.shutdownPromise = this.closeWorkers();
return this.shutdownPromise;
}
async closeWorkers() {
for (const queued of this.queuedSockets.splice(0)) {
clearTimeout(queued.timeout);
this.rejectSocket(queued.socket);
}
if (this.candidate) {
this.candidate.settle(new Error("rolling worker supervisor closed during replacement"));
}
if (this.active) {
this.requestWorkerShutdown(this.active);
}
for (const worker of this.draining.values()) {
this.requestWorkerShutdown(worker);
}
this.publishState();
if (!this.active && this.draining.size === 0) {
return;
}
await new Promise((resolve) => {
const finish = () => {
clearTimeout(timeout);
this.shutdownWaiters.delete(finish);
resolve();
};
const timeout = setTimeout(() => {
this.forceTerminateWorkers();
finish();
}, this.options.shutdownTimeoutMs);
timeout.unref?.();
this.shutdownWaiters.add(finish);
});
}
requestWorkerShutdown(worker) {
try {
worker.handle.sendControl({
type: "proxy-worker:shutdown",
generation: worker.generation,
});
}
catch {
worker.handle.terminate("SIGTERM");
}
}
forceTerminateWorkers() {
if (this.active) {
this.active.handle.terminate("SIGTERM");
this.active.dispose();
this.active = null;
}
for (const worker of this.draining.values()) {
worker.handle.terminate("SIGTERM");
worker.dispose();
}
this.draining.clear();
this.publishState();
}
notifyShutdownWaiters() {
if (!this.closed || this.active || this.draining.size > 0) {
return;
}
for (const finish of [...this.shutdownWaiters]) {
finish();
}
}
/**
* Validate candidate readiness and activation while preserving an
* independent listener for actual exit.
*/
spawnCandidate(expectedVersion) {
const generation = ++this.generation;
let workerProcessInstanceId;
let handle;
try {
handle = this.options.spawnWorker(generation, expectedVersion);
}
catch (error) {
this.recordFailure(generation, expectedVersion, "startup", error instanceof Error ? error.message : String(error));
this.publishState();
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
}
// Keep this evidence listener until actual exit, even when candidate
// failure or a fatal message detaches the operational state listeners.
handle.onExit((code, signal) => {
this.recordEvent({
type: "worker_exit",
generation,
version: expectedVersion,
workerPid: handle.pid,
workerProcessInstanceId,
workerExitCode: code,
workerExitSignal: signal,
});
});
return new Promise((resolve, reject) => {
let settled = false;
const finish = (error, phase = "startup", details) => {
if (settled) {
return;
}
settled = true;
clearTimeout(readyTimeout);
if (error) {
if (!this.closed) {
this.recordFailure(generation, expectedVersion, phase, error.message, details);
}
if (this.candidate?.generation === generation) {
this.candidate = null;
}
// A candidate that never became ready may also ignore SIGTERM.
// Bound its cleanup independently of the still-serving generation.
const killTimeout = setTimeout(() => handle.terminate("SIGKILL"), 1_000);
killTimeout.unref?.();
const offCandidateExit = handle.onExit(() => {
clearTimeout(killTimeout);
offCandidateExit();
});
handle.terminate("SIGTERM");
dispose();
this.publishState();
reject(error);
return;
}
resolve(this.snapshot());
};
const offMessage = handle.onMessage((message) => {
if (message.generation !== generation || message.pid !== handle.pid) {
return;
}
if (message.type === "proxy-worker:fatal") {
const error = new Error(`worker ${handle.pid} failed: ${message.message}`);
if (this.candidate?.generation === generation) {
finish(error);
}
else if (this.active?.generation === generation) {
this.active.dispose();
this.active = null;
handle.terminate("SIGTERM");
this.recordFailure(generation, expectedVersion, "runtime", message.message);
this.options.log?.(`[proxy-supervisor] active worker reported fatal generation=${generation} pid=${handle.pid}: ${message.message}`);
this.publishState();
this.notifyShutdownWaiters();
}
else if (this.draining.has(generation)) {
// A retiring generation still owns requests and budget leases.
// Preserve its fatal cause without dropping actual-exit tracking.
this.recordFailure(generation, expectedVersion, "runtime", message.message, { workerPid: handle.pid });
this.options.log?.(`[proxy-supervisor] draining worker reported fatal generation=${generation} pid=${handle.pid}: ${message.message}`);
this.publishState();
}
return;
}
if (message.type === "proxy-worker:replacement-requested") {
if (this.active?.generation === generation) {
this.options.onReplacementRequested?.({
generation,
pid: handle.pid,
reason: message.reason,
});
}
return;
}
if (message.type === "proxy-worker:ready") {
workerProcessInstanceId = message.processInstanceId;
if (message.version !== expectedVersion) {
finish(new Error(`worker ${handle.pid} reported v${message.version}; expected v${expectedVersion}`));
return;
}
const candidate = this.candidate;
if (!candidate) {
return;
}
if (!candidate.activationRequested) {
try {
handle.sendControl({
type: "proxy-worker:activate",
generation,
});
candidate.activationRequested = true;
}
catch (error) {
finish(error instanceof Error ? error : new Error(String(error)), "activation");
}
}
return;
}
if (message.type !== "proxy-worker:activated") {
return;
}
if (!this.candidate?.activationRequested) {
finish(new Error(`worker ${handle.pid} acknowledged activation before readiness`), "activation");
return;
}
const previous = this.active;
const activated = {
handle,
generation,
version: expectedVersion,
dispose,
pendingTransfers: 0,
drainRequested: false,
};
this.active = activated;
this.consecutiveOfferTimeouts = 0;
this.candidate = null;
this.flushQueuedSockets();
if (previous) {
this.draining.set(previous.generation, previous);
// A worker can still own socket transfers accepted before this
// activation. Draining it before their IPC commits makes those
// clients fail even though the replacement is healthy.
previous.drainRequested = true;
this.maybeDrainWorker(previous);
}
this.options.log?.(`[proxy-supervisor] activated generation=${generation} pid=${handle.pid} version=${expectedVersion}`);
this.recordEvent({
type: "activated",
generation,
version: expectedVersion,
});
this.publishState();
finish();
});
const offExit = handle.onExit((code, signal) => {
if (this.candidate?.generation === generation) {
finish(new Error(`worker ${handle.pid} exited before readiness (code=${code ?? "none"}, signal=${signal ?? "none"})`), "startup", {
workerPid: handle.pid,
workerExitCode: code,
workerExitSignal: signal,
});
return;
}
if (this.active?.generation === generation) {
this.active.dispose();
this.active = null;
if (!this.closed) {
this.recordFailure(generation, expectedVersion, "runtime", `worker exited (code=${code ?? "none"}, signal=${signal ?? "none"})`, {
workerPid: handle.pid,
workerExitCode: code,
workerExitSignal: signal,
});
}
this.options.log?.(`[proxy-supervisor] active worker exited generation=${generation} pid=${handle.pid} code=${code ?? "none"} signal=${signal ?? "none"}`);
}
const drained = this.draining.get(generation);
if (drained) {
drained.dispose();
this.draining.delete(generation);
}
this.publishState();
this.notifyShutdownWaiters();
});
const dispose = () => {
offMessage();
offExit();
};
const readyTimeout = setTimeout(() => {
finish(new Error(`worker ${handle.pid} did not become ready within ${this.options.readyTimeoutMs}ms`));
}, this.options.readyTimeoutMs);
readyTimeout.unref?.();
this.candidate = {
handle,
generation,
version: expectedVersion,
pendingTransfers: 0,
drainRequested: false,
expectedVersion,
activationRequested: false,
dispose,
settle: finish,
};
this.publishState();
});
}
flushQueuedSockets() {
if (this.flushingSockets) {
return;
}
this.flushingSockets = true;
try {
while (this.active &&
this.queuedSockets.length > 0 &&
this.active.pendingTransfers < this.options.maxPendingTransfers) {
const queued = this.queuedSockets.shift();
if (!queued) {
break;
}
clearTimeout(queued.timeout);
this.transferSocket(this.active, queued);
}
}
finally {
this.flushingSockets = false;
}
}
transferSocket(worker, admission) {
const { socket } = admission;
if (Date.now() >= admission.deadlineAt) {
this.rejectSocket(socket, worker.generation, worker.version, "admission_timeout");
return;
}
worker.pendingTransfers += 1;
this.scheduleTransferState();
let settled = false;
const complete = (error) => {
if (settled) {
return;
}
settled = true;
worker.pendingTransfers = Math.max(0, worker.pendingTransfers - 1);
if (error) {
this.handleTransferFailure(worker, admission, error);
}
else if (this.active?.generation === worker.generation) {
this.consecutiveOfferTimeouts = 0;
}
this.maybeDrainWorker(worker);
this.flushQueuedSockets();
this.scheduleTransferState();
};
try {
worker.handle.sendSocket(worker.generation, socket, complete, admission.deadlineAt);
}
catch (error) {
complete(error instanceof Error ? error : new Error(String(error)));
}
}
maybeDrainWorker(worker) {
if (!worker.drainRequested ||
worker.pendingTransfers > 0 ||
!this.draining.has(worker.generation)) {
return;
}
worker.drainRequested = false;
try {
worker.handle.sendControl({
type: "proxy-worker:drain",
generation: worker.generation,
});
}
catch {
worker.handle.terminate("SIGTERM");
}
}
/**
* Cancel the affected handoff and request bounded replacement without
* killing unrelated streams.
*/
handleTransferFailure(worker, admission, error) {
const { socket } = admission;
this.failedTransfers += 1;
const detail = this.describeTransferError(error);
this.recordEvent({
type: "failed_transfer",
generation: worker.generation,
version: worker.version,
phase: "transfer",
reason: detail,
});
const lifecycle = this.extractLifecycleFailureDetails(error, worker.handle.pid);
const cancelledOffer = error instanceof Error &&
error.code === PROXY_SOCKET_OFFER_TIMEOUT;
const commitTimeout = error instanceof Error &&
error.code === PROXY_SOCKET_COMMIT_TIMEOUT;
if (!lifecycle.observedExit &&
this.active?.generation === worker.generation) {
this.consecutiveOfferTimeouts += 1;
// Persistent stalls need recovery, but keep serving existing streams
// until a replacement activates. Avoid accumulating draining workers or
// spawning repeatedly when the whole host is under pressure.
if ((this.consecutiveOfferTimeouts >= 3 || !cancelledOffer) &&
!this.candidate &&
this.draining.size === 0 &&
Date.now() - this.lastStallReplacementAt >= 60_000 &&
!this.closed) {
this.lastStallReplacementAt = Date.now();
this.options.onReplacementRequested?.({
generation: worker.generation,
pid: worker.handle.pid,
reason: commitTimeout
? "socket_commit_timeout"
: cancelledOffer
? "socket_offer_timeout"
: "socket_transfer_failure",
});
}
}
this.recordFailure(worker.generation, worker.version, "transfer", `worker ${worker.handle.pid} socket transfer failed: ${detail}`, {
...lifecycle.details,
// If the error already records an exit, the supervisor did not cause
// that exit. Otherwise this captures the deliberate cleanup following
// the failed transfer, not a claimed root cause for the failure.
supervisorAction: cancelledOffer
? "cancel_uncommitted_socket"
: lifecycle.observedExit
? "none"
: "cancel_socket_replace_before_drain",
});
this.options.log?.(`[proxy-supervisor] socket transfer failed generation=${worker.generation} pid=${worker.handle.pid}: ${detail}`);
// A failed handoff is not evidence that every connection on this worker
// failed. Cancel only the affected socket and activate a replacement before
// draining existing streams. Actual exits are handled by onExit below.
const replacement = this.active;
if (cancelledOffer &&
error instanceof Error &&
"socketNeverTransferred" in error &&
error.socketNeverTransferred === true &&
!lifecycle.observedExit &&
!admission.retried &&
!this.closed &&
replacement &&
replacement.generation !== worker.generation &&
replacement.pendingTransfers < this.options.maxPendingTransfers &&
Date.now() < admission.deadlineAt) {
// Only negotiated control-only offers retain every byte on this parent.
// Legacy descriptor offers can buffer bytes before commit and never retry.
// The removed IPC entry also prevents a late acceptance issuing a commit.
// Every other failure is ambiguous and must never replay request bytes.
this.transferSocket(replacement, { ...admission, retried: true });
return;
}
this.rejectSocket(socket, worker.generation, worker.version, "transfer_failure");
}
/**
* Record a classified admission rejection before releasing the
* parent-owned socket.
*/
rejectSocket(socket, generation = this.active?.generation ?? null, version = this.active?.version ?? null, reason = "unavailable") {
this.rejectedSockets += 1;
this.recordEvent({
type: "rejected_socket",
generation,
version,
reason,
});
socket.destroy();
this.publishState();
}
describeTransferError(error) {
if (!(error instanceof Error)) {
return String(error ?? "unknown transfer failure");
}
const code = error.code;
return code ? `${code}: ${error.message}` : error.message;
}
extractLifecycleFailureDetails(error, fallbackPid) {
const context = error &&
typeof error === "object" &&
"context" in error &&
error.context &&
typeof error.context === "object"
? (error.context ?? {})
: {};
const workerPid = typeof context.workerPid === "number" ? context.workerPid : fallbackPid;
const hasExitCode = typeof context.exitCode === "number" || context.exitCode === null;
const hasExitSignal = typeof context.signal === "string" || context.signal === null;
return {
details: {
workerPid,
...(hasExitCode
? { workerExitCode: context.exitCode }
: {}),
...(hasExitSignal
? { workerExitSignal: context.signal }
: {}),
},
observedExit: hasExitCode || hasExitSignal,
};
}
/**
* Retain the latest failure and its observed process evidence in the
* incident journal.
*/
recordFailure(generation, version, phase, message, details = {}) {
this.lastFailure = {
at: new Date().toISOString(),
generation,
version,
phase,
message: message.slice(0, 1_000),
...details,
};
this.recordEvent({
type: "failure",
generation,
version,
phase,
reason: message,
...details,
});
}
/**
* Publish an independent incident record and retain only a bounded
* recent summary.
*/
recordEvent(event) {
const recorded = {
at: new Date().toISOString(),
...event,
...(event.reason ? { reason: event.reason.slice(0, 1_000) } : {}),
};
this.recentEvents.push(recorded);
try {
this.options.onEvent?.(recorded);
}
catch (error) {
this.options.log?.(`[proxy-supervisor] event journal failed: ${String(error)}`);
}
if (this.recentEvents.length > MAX_RECENT_SUPERVISOR_EVENTS) {
this.recentEvents.splice(0, this.recentEvents.length - MAX_RECENT_SUPERVISOR_EVENTS);
}
}
scheduleTransferState() {
if (this.closed || this.transferStateTimer || !this.options.onStateChange) {
return;
}
// The installed supervisor persists each notification. Keep diagnostics
// fresh without adding a synchronous disk write to every socket handoff.
this.transferStateTimer = setTimeout(() => this.publishState(), 250);
this.transferStateTimer.unref();
}
publishState() {
clearTimeout(this.transferStateTimer);
this.transferStateTimer = undefined;
try {
this.options.onStateChange?.(this.snapshot());
}
catch (error) {
this.options.log?.(`[proxy-supervisor] failed to persist supervisor state: ${error instanceof Error ? error.message : String(error)}`);
}
}
}