Reuse restricted engines with fresh invocation state and revocable ports
This commit is contained in:
@@ -77,6 +77,7 @@ async function fixture(source, options = {}) {
|
||||
send,
|
||||
start,
|
||||
usage,
|
||||
stats: service.stats,
|
||||
async close() {
|
||||
await service.close();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
@@ -424,3 +425,149 @@ test("queue wait consumes the deadline and cancelled work never reaches its port
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("warm engines create fresh transitive module state, closures and built-ins", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "qx-transitive-"));
|
||||
const helper = join(directory, "helper.mjs");
|
||||
await writeFile(
|
||||
helper,
|
||||
"let value=0, retained; export function touch(channel){const old=retained; retained=channel; return {count:++value, captured:!!old};}",
|
||||
);
|
||||
const f = await fixture(
|
||||
`import {touch} from 'helper.mjs'; let count=0;
|
||||
export const registry={exports:['test'],async invoke(request,channel){
|
||||
const before={object:Object.prototype.retained??null,array:Array.prototype.retained??null,
|
||||
map:Map.prototype.retained??null, global:globalThis.retained??null};
|
||||
Object.prototype.retained=request.input; Array.prototype.retained=request.input;
|
||||
Map.prototype.retained=request.input; globalThis.retained=request.input;
|
||||
return {count:++count, helper:touch(channel), before};
|
||||
}};`,
|
||||
{ sdk: [{ name: "helper.mjs", path: helper }] },
|
||||
);
|
||||
try {
|
||||
for (const value of ["private graph A", "private graph B", "private graph C"]) {
|
||||
f.start(value);
|
||||
assert.deepEqual((await f.receive()).output, {
|
||||
count: 1,
|
||||
helper: { count: 1, captured: false },
|
||||
before: { object: null, array: null, map: null, global: null },
|
||||
});
|
||||
}
|
||||
assert.equal(f.stats().starts, 1, "Qualification must exercise actual engine reuse");
|
||||
assert.equal(f.stats().reused, 2);
|
||||
assert.equal(f.stats().idle, 1);
|
||||
} finally {
|
||||
await f.close();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("an expired frame token cannot borrow a later invocation's broker", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "qx-token-"));
|
||||
const worker = join(directory, "token-test.mjs");
|
||||
// Trusted test entrypoint deliberately replays the old token. Authored
|
||||
// registries never receive CONFIG or BROKER directly.
|
||||
await writeFile(
|
||||
worker,
|
||||
`export default {async fetch(request,env) {
|
||||
const message=await request.json();
|
||||
const output=message.input ? (await env.BROKER.fetch('http://frame/port/'+message.input,{
|
||||
method:'POST',body:JSON.stringify({portId:'read',action:{kind:'state',primitive:'read'},input:null})})).status
|
||||
: env.CONFIG.frameToken;
|
||||
return Response.json({status:'returned',output});
|
||||
}};`,
|
||||
);
|
||||
const f = await fixture(registry("return null;"), { worker });
|
||||
try {
|
||||
f.start();
|
||||
const token = (await f.receive()).output;
|
||||
assert.match(token, /^[a-f0-9-]{36}$/);
|
||||
f.start(token);
|
||||
assert.equal((await f.receive()).output, 409);
|
||||
assert.equal(f.stats().starts, 1);
|
||||
assert.equal(f.stats().reused, 1);
|
||||
} finally {
|
||||
await f.close();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("idle engines evict without a graph poll and restart with fresh state", async () => {
|
||||
const f = await fixture(registry("return request.input;"), { engineIdleMs: 30 });
|
||||
try {
|
||||
f.start("first");
|
||||
assert.equal((await f.receive()).output, "first");
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
assert.equal(f.stats().engines, 0);
|
||||
f.start("second");
|
||||
assert.equal((await f.receive()).output, "second");
|
||||
assert.equal(f.stats().starts, 2);
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
for (const loop of [
|
||||
"while(true) await Promise.resolve();",
|
||||
"const memory=[]; for(let n=0;n<128;n++) memory.push(new Uint8Array(256*1024)); while(true) await Promise.resolve(memory);",
|
||||
])
|
||||
test("await/allocation pressure is fenced without delaying a neighboring engine: " + loop.slice(0, 28), async () => {
|
||||
const f = await fixture(registry(`if(request.input==='spin'){${loop}} return request.input;`), {
|
||||
deadlineMs: 1500,
|
||||
});
|
||||
try {
|
||||
const stuck = f.start("spin", { root: "stuck", depth: 1, deadlineMs: 350, calls: 10 });
|
||||
const before = performance.now();
|
||||
const healthy = f.start("healthy");
|
||||
const replies = [await f.receive(), await f.receive()];
|
||||
assert.equal(replies.find((r) => r.invocation === healthy.invocation).output, "healthy");
|
||||
assert.equal(replies.find((r) => r.invocation === stuck.invocation).error.code, "RESTRICTED_DEADLINE");
|
||||
assert.equal(replies[0].invocation, healthy.invocation, "Healthy work must finish before the stuck deadline");
|
||||
assert.ok(performance.now() - before < 1200);
|
||||
f.start("after fault");
|
||||
assert.equal((await f.receive()).output, "after fault");
|
||||
assert.equal(f.stats().starts, 2, "The healthy engine survives its neighbor's termination");
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("detached microtask loops cannot escape the invocation wall deadline", async () => {
|
||||
const f = await fixture(
|
||||
registry("void(async()=>{while(true)await Promise.resolve();})(); return 'nominal return';"),
|
||||
{ deadlineMs: 200 },
|
||||
);
|
||||
try {
|
||||
const before = performance.now();
|
||||
f.start();
|
||||
assert.equal((await f.receive()).error.code, "RESTRICTED_DEADLINE");
|
||||
assert.ok(performance.now() - before < 1000);
|
||||
assert.equal(f.stats().engines, 0);
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("detached digest callbacks cannot use a completed frame or contaminate reuse", async () => {
|
||||
const f = await fixture(
|
||||
registry(`
|
||||
void(async()=>{
|
||||
for(let n=0;n<100;n++)await crypto.subtle.digest('SHA-256',new Uint8Array(32768));
|
||||
await channel({portId:'late',action:{kind:'state',primitive:'read'},input:request.input});
|
||||
})().catch(()=>{});
|
||||
return request.input;
|
||||
`),
|
||||
);
|
||||
try {
|
||||
f.start("private A");
|
||||
assert.equal((await f.receive()).output, "private A");
|
||||
await new Promise((resolve) => setTimeout(resolve, 75));
|
||||
f.start("private B");
|
||||
const reply = await f.receive();
|
||||
assert.equal(reply.status, "returned", "No late graph port may appear after completion");
|
||||
assert.equal(reply.output, "private B");
|
||||
assert.equal(f.stats().starts, 1);
|
||||
} finally {
|
||||
await f.close();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user