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,25 +8,46 @@ 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; return false;
} }
const registry = await readRegistry();
for (const entry of registry.entries) {
const idleMs = now - entry.lastUsedAtMs; const idleMs = now - entry.lastUsedAtMs;
const ageMs = now - entry.createdAtMs; const ageMs = now - entry.createdAtMs;
if ( return (
(idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) || (idleHours > 0 && idleMs > idleHours * 60 * 60 * 1000) ||
(maxAgeDays > 0 && ageMs > maxAgeDays * 24 * 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;
}
const registry = await params.read();
for (const entry of registry.entries) {
if (!shouldPruneSandboxEntry(params.cfg, now, entry)) {
continue;
}
try { try {
await execDocker(["rm", "-f", entry.containerName], { await execDocker(["rm", "-f", entry.containerName], {
allowFailure: true, allowFailure: true,
@@ -34,43 +55,33 @@ async function pruneSandboxContainers(cfg: SandboxConfig) {
} catch { } catch {
// ignore prune failures // ignore prune failures
} finally { } finally {
await removeRegistryEntry(entry.containerName); await params.remove(entry.containerName);
} await params.onRemoved?.(entry);
} }
} }
} }
async function pruneSandboxBrowsers(cfg: SandboxConfig) { async function pruneSandboxContainers(cfg: SandboxConfig) {
const now = Date.now(); await pruneSandboxRegistryEntries<SandboxRegistryEntry>({
const idleHours = cfg.prune.idleHours; cfg,
const maxAgeDays = cfg.prune.maxAgeDays; read: readRegistry,
if (idleHours === 0 && maxAgeDays === 0) { remove: removeRegistryEntry,
return;
}
const registry = await readBrowserRegistry();
for (const entry of registry.entries) {
const idleMs = now - entry.lastUsedAtMs;
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 { async function pruneSandboxBrowsers(cfg: SandboxConfig) {
await removeBrowserRegistryEntry(entry.containerName); await pruneSandboxRegistryEntries<SandboxBrowserRegistryEntry>({
cfg,
read: readBrowserRegistry,
remove: removeBrowserRegistryEntry,
onRemoved: async (entry) => {
const bridge = BROWSER_BRIDGES.get(entry.sessionKey); const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
if (bridge?.containerName === entry.containerName) { if (bridge?.containerName === entry.containerName) {
await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined); await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(entry.sessionKey); BROWSER_BRIDGES.delete(entry.sessionKey);
} }
} },
} });
}
} }
export async function maybePruneSandboxes(cfg: SandboxConfig) { export async function maybePruneSandboxes(cfg: SandboxConfig) {