test/devin-connect-deadline-ref.test.js
Rama master · réplica de lectura del repositorio original.
// WHY A CHILD PROCESS. The absolute-deadline timer in streamChat is unref'd by
// default and only enters the loop's ref set while a consumer is blocked in the
// wait (src/devin-connect.js, "consumer wait"). If that ref window is lost, a hung
// upstream can never be ended by the deadline — but the damage is version-gated:
// on Node 20/22 the runtime cancels the pending await ("Promise resolution is
// still pending but the event loop has already resolved") and the in-process test
// at devin-connect.test.js:1147 goes red, while on Node 24 the test runner's own
// handles keep the loop alive and the same defect is invisible. Node 24 is the
// release gate, so the in-process test alone cannot guard this fix.
//
// A standalone process has exactly two things that could keep it alive: the fake
// request (which never responds) and the deadline timer. If the timer is not ref'd
// while the consumer waits, the process exits with an unsettled top-level await
// and status 13 on EVERY version; with the ref window it settles with
// DEADLINE_EXCEEDED and exits 0. That makes this the version-independent guard.
import { spawnSync } from 'node:child_process';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { dirname, join } from 'node:path';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const devinConnect = pathToFileURL(join(root, 'src', 'devin-connect.js')).href;
const script = `
import { EventEmitter } from 'node:events';
const { streamChat, __setRequestImpl } = await import(${JSON.stringify(devinConnect)});
const res = new EventEmitter();
res.statusCode = 200;
// Hung-but-silent upstream: connects, never sends a frame, never errors.
__setRequestImpl((_opts, cb) => {
const req = new EventEmitter();
req.destroyed = false;
req.setTimeout = () => req; // the idle timer never fires
req.write = () => {};
req.destroy = () => { req.destroyed = true; };
req.end = () => { setTimeout(() => cb(res), 1); };
return req;
});
try {
for await (const _event of streamChat({
messages: [{ role: 'user', content: 'hi' }],
model: 'swe-1-6-slow',
token: 'devin-session-token$fake.jwt.sig',
timeoutMs: 60000,
deadlineMs: 50,
})) { /* drain */ }
console.log('SETTLED:no-error');
} catch (err) {
console.log('SETTLED:' + err.code);
}
`;
test('a standalone consumer of a hung stream is ended by the absolute deadline, not by loop drain', () => {
const out = spawnSync(process.execPath, ['--input-type=module', '--eval', script], {
encoding: 'utf8',
timeout: 30000,
});
assert.equal(
out.status, 0,
`child exited ${out.status} (a non-zero status here means the pending await was abandoned by the `
+ `event loop, i.e. the deadline timer was not ref'd while the consumer waited)\n`
+ `stdout: ${out.stdout}\nstderr: ${out.stderr}`,
);
assert.match(out.stdout, /SETTLED:DEADLINE_EXCEEDED/, `child did not settle on the deadline: ${out.stdout}`);
});