fix(worker): ensure reconciled boxes before acquire
This commit is contained in:
@@ -13,6 +13,7 @@ export type BoxHandle = { boxName: string; release: () => void };
|
||||
|
||||
type Box = {
|
||||
name: string;
|
||||
initialized: boolean;
|
||||
refs: Set<symbol>;
|
||||
idleTimer?: ReturnType<typeof setTimeout>;
|
||||
};
|
||||
@@ -71,21 +72,26 @@ export const acquireUserBox = (args: {
|
||||
}): Promise<BoxHandle> =>
|
||||
withLock(args.username, async () => {
|
||||
let box = boxes.get(args.username);
|
||||
let needsEnsure = false;
|
||||
if (box?.idleTimer) {
|
||||
clearTimeout(box.idleTimer);
|
||||
box.idleTimer = undefined;
|
||||
}
|
||||
if (!box) {
|
||||
box = { name: userContainerName(args.username), refs: new Set() };
|
||||
box = {
|
||||
name: userContainerName(args.username),
|
||||
initialized: false,
|
||||
refs: new Set(),
|
||||
};
|
||||
boxes.set(args.username, box);
|
||||
needsEnsure = true;
|
||||
}
|
||||
// Register the ref before the slow Docker call so a failed acquire can
|
||||
// release it without leaking registry state.
|
||||
const handle = makeHandle(args.username, box);
|
||||
try {
|
||||
if (needsEnsure) box.name = await ensureUserContainer(args);
|
||||
if (!box.initialized) {
|
||||
box.name = await ensureUserContainer(args);
|
||||
box.initialized = true;
|
||||
}
|
||||
return handle;
|
||||
} catch (error) {
|
||||
handle.release();
|
||||
@@ -103,7 +109,7 @@ export const reconcileExistingBoxes = async (): Promise<void> => {
|
||||
for (const name of names) {
|
||||
const username = name.replace(/^spoon-box-/, '');
|
||||
if (boxes.has(username)) continue;
|
||||
const box: Box = { name, refs: new Set() };
|
||||
const box: Box = { name, initialized: false, refs: new Set() };
|
||||
boxes.set(username, box);
|
||||
scheduleReapIfIdle(username);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,30 @@ describe('box registry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('ensures an adopted box once before returning acquire handles', async () => {
|
||||
const module = await load();
|
||||
const docker = await import('../../src/runtime/docker');
|
||||
vi.mocked(docker.listWorkspaceContainerNames).mockResolvedValueOnce([
|
||||
'spoon-box-casey',
|
||||
]);
|
||||
await module.reconcileExistingBoxes();
|
||||
|
||||
const first = await module.acquireUserBox({
|
||||
username: 'casey',
|
||||
workdir: '/w',
|
||||
containerHome: '/home/casey',
|
||||
});
|
||||
const second = await module.acquireUserBox({
|
||||
username: 'casey',
|
||||
workdir: '/w',
|
||||
containerHome: '/home/casey',
|
||||
});
|
||||
|
||||
expect(docker.ensureUserContainer).toHaveBeenCalledTimes(1);
|
||||
expect(first.boxName).toBe('spoon-box-casey');
|
||||
expect(second.boxName).toBe('spoon-box-casey');
|
||||
});
|
||||
|
||||
test('retries ensure after the first acquire fails', async () => {
|
||||
const module = await load();
|
||||
const docker = await import('../../src/runtime/docker');
|
||||
|
||||
Reference in New Issue
Block a user