mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
fix(beehive): drain all owner requests before controller exit
Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
import { OwnerService } from './owner-service.ts';
|
||||
const service = new OwnerService(process.env.BEEHIVE_HOME);
|
||||
service.subscribe(snapshot => { if (process.connected) process.send?.({ type: 'snapshot', snapshot }); });
|
||||
let pending: Promise<boolean> | undefined;
|
||||
const pending = new Set<Promise<boolean>>();
|
||||
let exiting = false;
|
||||
process.on('message', async (message: any) => {
|
||||
if (message.type === 'secret') service.secret(message.action, message.value);
|
||||
else if (message.type === 'cancel') service.cancel();
|
||||
else if (message.type === 'request') {
|
||||
pending = service.request(message.request);
|
||||
const ok = await pending;
|
||||
const work = service.request(message.request);
|
||||
pending.add(work);
|
||||
const ok = await work.finally(() => pending.delete(work));
|
||||
if (process.connected && !exiting) process.send?.({ type: 'result', id: message.id, ok });
|
||||
}
|
||||
});
|
||||
const stop = () => {
|
||||
if (exiting) return; exiting = true; service.dispose();
|
||||
void Promise.resolve(pending).finally(() => process.exit());
|
||||
void Promise.allSettled([...pending]).finally(() => process.exit());
|
||||
};
|
||||
process.on('disconnect', stop);
|
||||
process.on('SIGTERM', stop);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { once } from 'node:events';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, writeFileSync, rmSync, existsSync } from 'node:fs';
|
||||
@@ -27,3 +28,23 @@ test('real private IPC binds public snapshots to session, cancel, signout and re
|
||||
assert.equal(existsSync(join(home, '.beehive/owner/controller.json')), true);
|
||||
} finally { client.dispose(); rmSync(home, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test('owner child disconnect drains earlier operations after a later signout resolves', async () => {
|
||||
const { fork } = await import('node:child_process');
|
||||
const { fileURLToPath } = await import('node:url');
|
||||
const home=mkdtempSync(join(tmpdir(),'owner-disconnect-'));
|
||||
const loader=join(home,'isolate.mjs'), marker=join(home,'drained');
|
||||
writeFileSync(loader,`import {registerHooks} from 'node:module';
|
||||
registerHooks({load(url,ctx,next){if(!url.endsWith('/src/owner-service.ts'))return next(url,ctx);return {format:'module',shortCircuit:true,source:${JSON.stringify(`import {writeFileSync} from 'node:fs';
|
||||
export class OwnerService {
|
||||
subscribe() {} secret() {} cancel() {} dispose() {}
|
||||
async request(r) { if(r.action==='host-reset') { process.send({started:true});await new Promise(resolve=>setTimeout(resolve,200));writeFileSync(${JSON.stringify(marker)},'drained'); } return true; }
|
||||
}`)}};}});`);
|
||||
const child=fork(fileURLToPath(new URL('../src/owner-child.ts',import.meta.url)),[],{execPath:process.execPath,execArgv:['--import',loader],silent:true,env:{HOME:home,PATH:'/usr/bin:/bin'}});
|
||||
try {
|
||||
const started=once(child,'message');child.send({type:'request',id:1,request:{action:'host-reset'}});await started;
|
||||
const signedout=once(child,'message');child.send({type:'request',id:2,request:{action:'signout'}});assert.equal((await signedout)[0].id,2);
|
||||
const exited=once(child,'exit');child.disconnect();await exited;
|
||||
assert.equal(existsSync(marker),true,'earlier cleanup completed before process exit');
|
||||
} finally {child.kill();rmSync(home,{recursive:true,force:true});}
|
||||
});
|
||||
|
||||
@@ -10,13 +10,15 @@ const service = new OwnerService(home, async (probe, signal) => {
|
||||
signal.throwIfAborted();
|
||||
return mode === 'missing' || mode === 'denied' ? { reason: mode === 'missing' ? 'missing' : 'access' } : probe ? { available: true } : { secret: mode === 'mismatch' ? '2'.repeat(64) : '1'.repeat(64) };
|
||||
}, fixtureHostDependencies(home));
|
||||
const pending = new Set<Promise<boolean>>();
|
||||
service.subscribe(snapshot => { if (process.connected) process.send?.({ type: 'snapshot', snapshot }); });
|
||||
process.on('message', async (message: any) => {
|
||||
if (message.type === 'secret') service.secret(message.action, message.value);
|
||||
else if (message.type === 'cancel') service.cancel();
|
||||
else if (message.type === 'request') {
|
||||
const ok = await service.request(message.request);
|
||||
const work = service.request(message.request); pending.add(work);
|
||||
const ok = await work.finally(() => pending.delete(work));
|
||||
if (process.connected) process.send?.({ type: 'result', id: message.id, ok });
|
||||
}
|
||||
});
|
||||
process.on('disconnect', () => { service.dispose(); process.exit(); });
|
||||
process.on('disconnect', () => { service.dispose(); void Promise.allSettled([...pending]).finally(() => process.exit()); });
|
||||
|
||||
Reference in New Issue
Block a user