Fix late cleanup-failure error line overwriting an already-successful job done
The 'line' handler applied a t:'error' event immediately and unconditionally, even when a t:'done' had already arrived (deferred as pendingDone until close). A job whose real work finished successfully (io.done() called, e.g. leads already committed by the child) could still end up persisted and reported as 'error' if something in the child's finally-block cleanup (closing the lease/db) threw afterward, since main().catch() calls io.fail() + process.exit(1) in that case — discarding a real success summary for a cosmetic cleanup failure. Fix distinguishes this from a genuinely unexplained nonzero exit (process killed/crashed with no chance to report anything), which two existing tests deliberately require to still override a done: a new lateErrorAfterDone flag is only set when an explicit error line follows a done, and close only trusts the done over a nonzero exit code when that flag is set. Found via a full-project audit.
This commit is contained in:
@@ -165,6 +165,41 @@ test('linha done drenada entre exit e close resulta em done', async () => {
|
||||
assert.equal(jobs.getJob(jobId).lastEvent.summary.late, true);
|
||||
});
|
||||
|
||||
// Cenario real do capture-job.mjs: main() chama io.done() apos o trabalho
|
||||
// terminar (leads ja gravados pelo proprio filho), mas o finally (fechar
|
||||
// lease/db) falha depois disso — main().catch escreve uma linha "error"
|
||||
// explicita e sai com codigo != 0. O done ja reportado nao pode virar erro
|
||||
// so por causa da falha de limpeza que veio depois dele.
|
||||
test('erro explicito apos done (falha de limpeza) nao sobrescreve o done', async () => {
|
||||
const db = openDb(':memory:');
|
||||
const runId = Number(db.prepare("INSERT INTO capture_runs (params_json, status) VALUES ('{}', 'running')").run().lastInsertRowid);
|
||||
const child = new EventEmitter();
|
||||
child.stdout = new PassThrough();
|
||||
child.stderr = new PassThrough();
|
||||
child.stdin = { write() {} };
|
||||
child.kill = () => {};
|
||||
const jobs = createJobManager({ spawn: () => child, db });
|
||||
|
||||
const jobId = jobs.startJob({ type: 'captura', script: 'fake', meta: { runId } });
|
||||
const events = [];
|
||||
jobs.subscribe(jobId, (event) => events.push(event));
|
||||
|
||||
child.stdout.write('{"t":"done","summary":{"new":42}}\n');
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
child.stdout.write('{"t":"error","message":"falha ao fechar conexao do banco"}\n');
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert.equal(jobs.getJob(jobId).status, 'running');
|
||||
|
||||
child.emit('exit', 1, null);
|
||||
child.emit('close', 1, null);
|
||||
|
||||
assert.equal(jobs.getJob(jobId).status, 'done');
|
||||
assert.equal(events.at(-1).t, 'done');
|
||||
assert.equal(events.at(-1).summary.new, 42);
|
||||
assert.equal(db.prepare('SELECT status FROM capture_runs WHERE id = ?').get(runId).status, 'done');
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('manager trata erro do stream stdout como falha terminal persistida', async () => {
|
||||
const db = openDb(':memory:');
|
||||
const exportId = Number(db.prepare("INSERT INTO exports (tipo, status) VALUES ('excel', 'running')").run().lastInsertRowid);
|
||||
|
||||
Reference in New Issue
Block a user