refactor(sandbox): dedupe prune loops

This commit is contained in:
Peter Steinberger
2026-02-15 16:33:57 +00:00
parent d4476c6899
commit 5457f6e7e4

View File

@@ -8,69 +8,80 @@ import {
readRegistry, readRegistry,
removeBrowserRegistryEntry, removeBrowserRegistryEntry,
removeRegistryEntry, removeRegistryEntry,
type SandboxBrowserRegistryEntry,
type SandboxRegistryEntry,
} from "./registry.js"; } from "./registry.js";
let lastPruneAtMs = 0; let lastPruneAtMs = 0;
async function pruneSandboxContainers(cfg: SandboxConfig) { type PruneableRegistryEntry = Pick<
const now = Date.now(); SandboxRegistryEntry,
"containerName" | "createdAtMs" | "lastUsedAtMs"
>;
function shouldPruneSandboxEntry(cfg: SandboxConfig, now: number, entry: PruneableRegistryEntry) {
const idleHours = cfg.prune.idleHours; const idleHours = cfg.prune.idleHours;
const maxAgeDays = cfg.prune.maxAgeDays; const maxAgeDays = cfg.prune.maxAgeDays;
if (idleHours === 0 && maxAgeDays === 0) { if (idleHours === 0 && maxAgeDays === 0) {
return false;
}
const idleMs = now - entry.lastUsedAtMs;
const ageMs = now - entry.createdAtMs;
return (
(idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) ||
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1000)
);
}
async function pruneSandboxRegistryEntries<TEntry extends PruneableRegistryEntry>(params: {
cfg: SandboxConfig;
read: () => Promise<{ entries: TEntry[] }>;
remove: (containerName: string) => Promise<void>;
onRemoved?: (entry: TEntry) => Promise<void>;
}) {
const now = Date.now();
if (params.cfg.prune.idleHours === 0 && params.cfg.prune.maxAgeDays === 0) {
return; return;
} }
const registry = await readRegistry(); const registry = await params.read();
for (const entry of registry.entries) { for (const entry of registry.entries) {
const idleMs = now - entry.lastUsedAtMs; if (!shouldPruneSandboxEntry(params.cfg, now, entry)) {
const ageMs = now - entry.createdAtMs; continue;
if ( }
(idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) || try {
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1000) await execDocker(["rm", "-f", entry.containerName], {
) { allowFailure: true,
try { });
await execDocker(["rm", "-f", entry.containerName], { } catch {
allowFailure: true, // ignore prune failures
}); } finally {
} catch { await params.remove(entry.containerName);
// ignore prune failures await params.onRemoved?.(entry);
} finally {
await removeRegistryEntry(entry.containerName);
}
} }
} }
} }
async function pruneSandboxContainers(cfg: SandboxConfig) {
await pruneSandboxRegistryEntries<SandboxRegistryEntry>({
cfg,
read: readRegistry,
remove: removeRegistryEntry,
});
}
async function pruneSandboxBrowsers(cfg: SandboxConfig) { async function pruneSandboxBrowsers(cfg: SandboxConfig) {
const now = Date.now(); await pruneSandboxRegistryEntries<SandboxBrowserRegistryEntry>({
const idleHours = cfg.prune.idleHours; cfg,
const maxAgeDays = cfg.prune.maxAgeDays; read: readBrowserRegistry,
if (idleHours === 0 && maxAgeDays === 0) { remove: removeBrowserRegistryEntry,
return; onRemoved: async (entry) => {
} const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
const registry = await readBrowserRegistry(); if (bridge?.containerName === entry.containerName) {
for (const entry of registry.entries) { await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
const idleMs = now - entry.lastUsedAtMs; BROWSER_BRIDGES.delete(entry.sessionKey);
const ageMs = now - entry.createdAtMs;
if (
(idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) ||
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 60 * 60 * 1000)
) {
try {
await execDocker(["rm", "-f", entry.containerName], {
allowFailure: true,
});
} catch {
// ignore prune failures
} finally {
await removeBrowserRegistryEntry(entry.containerName);
const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
if (bridge?.containerName === entry.containerName) {
await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(entry.sessionKey);
}
} }
} },
} });
} }
export async function maybePruneSandboxes(cfg: SandboxConfig) { export async function maybePruneSandboxes(cfg: SandboxConfig) {