Export: the redundant guard after Blender is gone; tests for a lying exit code, a malformed result with an FBX, output after SIGTERM, and a hung request
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
62c0217d1f
commit
a65cc60113
3 changed files with 37 additions and 6 deletions
|
|
@ -4,8 +4,9 @@
|
|||
// like Blender, unless the copy is named blender-broken, which names itself and exits 1. An export
|
||||
// (`... -- <bvh> <fbx> <clip>`) acts on the clip name:
|
||||
// fail* prints a line to stderr, exits 1 silent* exits 0 with no result line
|
||||
// garbage* exits 0 with a malformed result line nofile* exits 0 with a result line and no FBX
|
||||
// hang* a grandchild, then never exits slow* runs 6 s, then behaves
|
||||
// garbage* an FBX, then a malformed result line nofile* exits 0 with a result line and no FBX
|
||||
// lies* an FBX and a result line, exits 3 slow* runs 6 s, then behaves
|
||||
// hang* a grandchild, then never exits; after SIGTERM it prints a line every 100 ms
|
||||
// orphan* behaves, but leaves a grandchild running when it exits
|
||||
// other writes "FBX-STUB:" + the BVH's bytes to <fbx> and prints the VILI-FBX result line
|
||||
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
|
|
@ -33,17 +34,24 @@ const [bvh, fbx, clip] = argv.slice(argv.indexOf("--") + 1);
|
|||
console.error(`stub-blender-stderr ${clip}`);
|
||||
if (clip.startsWith("fail")) process.exit(1);
|
||||
if (clip.startsWith("silent")) process.exit(0);
|
||||
const writeFbx = () => writeFileSync(fbx, Buffer.concat([Buffer.from("FBX-STUB:"), readFileSync(bvh)]));
|
||||
if (clip.startsWith("garbage")) {
|
||||
writeFbx();
|
||||
console.log('VILI-FBX {"bones":"many","frames":[1],"fps":30}');
|
||||
process.exit(0);
|
||||
}
|
||||
if (clip.startsWith("lies")) {
|
||||
writeFbx();
|
||||
console.log(result);
|
||||
process.exit(3);
|
||||
}
|
||||
if (clip.startsWith("nofile")) {
|
||||
console.log(result);
|
||||
process.exit(0);
|
||||
}
|
||||
if (clip.startsWith("hang")) {
|
||||
spawnGrandchild();
|
||||
process.on("SIGTERM", () => {});
|
||||
process.on("SIGTERM", () => setInterval(() => console.error("after-sigterm-line"), 100));
|
||||
setInterval(() => {}, 1000);
|
||||
} else {
|
||||
if (clip.startsWith("slow")) await new Promise((resolve) => setTimeout(resolve, 6000));
|
||||
|
|
@ -52,6 +60,6 @@ if (clip.startsWith("hang")) {
|
|||
spawnGrandchild().unref(); // a live child would keep this process, and so the export, alive
|
||||
for (let waited = 0; grandchildren() === before && waited < 3000; waited += 25) await new Promise((resolve) => setTimeout(resolve, 25));
|
||||
}
|
||||
writeFileSync(fbx, Buffer.concat([Buffer.from("FBX-STUB:"), readFileSync(bvh)]));
|
||||
writeFbx();
|
||||
console.log(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1464,6 +1464,9 @@ test("a failing or hung Blender fails the job, leaks nothing, and leaves nothing
|
|||
assert.equal(odd.status, 502, clip);
|
||||
assert.equal(odd.body.error, "fbx-export-failed (no result)", clip);
|
||||
}
|
||||
const lied = await generate(daemon, exportRequest({ clipName: "lies" }));
|
||||
assert.equal(lied.status, 502, "an export that exits non-zero is no export, whatever it printed");
|
||||
assert.equal(lied.body.error, "fbx-export-failed (exit 3)");
|
||||
|
||||
const known = new Set(daemon.blenderGrandchildren());
|
||||
const orphaned = await generate(daemon, exportRequest({ clipName: "orphan" }));
|
||||
|
|
@ -1473,7 +1476,10 @@ test("a failing or hung Blender fails the job, leaks nothing, and leaves nothing
|
|||
|
||||
const before = daemon.starts().length;
|
||||
known.add(left);
|
||||
const hung = await generate(daemon, exportRequest({ clipName: "hang", exportTimeoutMs: 2500 }));
|
||||
const hung = await Promise.race([
|
||||
generate(daemon, exportRequest({ clipName: "hang", exportTimeoutMs: 2500 })),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("the hung export was never stopped")), 20000)),
|
||||
]);
|
||||
assert.equal(hung.status, 502);
|
||||
assert.equal(hung.body.error, "fbx-export-failed (signal SIGKILL)");
|
||||
const [run] = daemon.starts().slice(before);
|
||||
|
|
@ -1503,6 +1509,24 @@ test("SIGTERM during an export stops its group, writes nothing more, and exits 0
|
|||
}
|
||||
});
|
||||
|
||||
test("what a stopping export still prints is not logged, and it is killed when it ignores SIGTERM", async () => {
|
||||
const daemon = await startDaemon();
|
||||
try {
|
||||
const pending = generate(daemon, exportRequest({ clipName: "hang", exportTimeoutMs: 60000 }));
|
||||
pending.catch(() => {});
|
||||
const run = await until(() => exportStarts(daemon)[0], "the export to start");
|
||||
daemon.proc.kill("SIGTERM");
|
||||
await until(() => daemon.proc.exitCode !== null, "the daemon to exit", 30);
|
||||
assert.equal(daemon.proc.exitCode, 0);
|
||||
await until(() => !isAlive(run.pid), "the export that ignored SIGTERM to be killed");
|
||||
const log = readFileSync(path.join(daemon.stateRoot, "logs", "export.log"), "utf8");
|
||||
assert.match(log, /stub-blender-stderr hang/);
|
||||
assert.ok(!log.includes("after-sigterm-line"), "a line printed after the signal reached the log");
|
||||
} finally {
|
||||
await daemon.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test("losing the lease kills a running export within a second and records nothing", async () => {
|
||||
const daemon = await idunnDaemon({ leased: true });
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -571,7 +571,6 @@ async function exportClip(bvh, fbx, clipName, timeoutMs) {
|
|||
"-b", "--factory-startup", "--python-use-system-env", "--python-exit-code", "1",
|
||||
"--python", exportScript, "--", bvh, fbx, clipName,
|
||||
], timeoutMs);
|
||||
guard(); // the writer may have been lost while Blender ran
|
||||
let info = null;
|
||||
if (run.code === 0 && run.result !== null) {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue