mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-19 12:10:37 +09:00
Persist heartbeat child pid before stdin handoff
This commit is contained in:
parent
3baebee2df
commit
26d4cabb2e
2 changed files with 53 additions and 10 deletions
38
packages/adapter-utils/src/server-utils.test.ts
Normal file
38
packages/adapter-utils/src/server-utils.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { runChildProcess } from "./server-utils.js";
|
||||||
|
|
||||||
|
describe("runChildProcess", () => {
|
||||||
|
it("waits for onSpawn before sending stdin to the child", async () => {
|
||||||
|
const spawnDelayMs = 150;
|
||||||
|
const startedAt = Date.now();
|
||||||
|
let onSpawnCompletedAt = 0;
|
||||||
|
|
||||||
|
const result = await runChildProcess(
|
||||||
|
randomUUID(),
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"-e",
|
||||||
|
"let data='';process.stdin.setEncoding('utf8');process.stdin.on('data',chunk=>data+=chunk);process.stdin.on('end',()=>process.stdout.write(data));",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cwd: process.cwd(),
|
||||||
|
env: {},
|
||||||
|
stdin: "hello from stdin",
|
||||||
|
timeoutSec: 5,
|
||||||
|
graceSec: 1,
|
||||||
|
onLog: async () => {},
|
||||||
|
onSpawn: async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, spawnDelayMs));
|
||||||
|
onSpawnCompletedAt = Date.now();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const finishedAt = Date.now();
|
||||||
|
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.stdout).toBe("hello from stdin");
|
||||||
|
expect(onSpawnCompletedAt).toBeGreaterThanOrEqual(startedAt + spawnDelayMs);
|
||||||
|
expect(finishedAt - startedAt).toBeGreaterThanOrEqual(spawnDelayMs);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1069,16 +1069,12 @@ export async function runChildProcess(
|
||||||
}) as ChildProcessWithEvents;
|
}) as ChildProcessWithEvents;
|
||||||
const startedAt = new Date().toISOString();
|
const startedAt = new Date().toISOString();
|
||||||
|
|
||||||
if (opts.stdin != null && child.stdin) {
|
const spawnPersistPromise =
|
||||||
child.stdin.write(opts.stdin);
|
typeof child.pid === "number" && child.pid > 0 && opts.onSpawn
|
||||||
child.stdin.end();
|
? opts.onSpawn({ pid: child.pid, startedAt }).catch((err) => {
|
||||||
}
|
onLogError(err, runId, "failed to record child process metadata");
|
||||||
|
})
|
||||||
if (typeof child.pid === "number" && child.pid > 0 && opts.onSpawn) {
|
: Promise.resolve();
|
||||||
void opts.onSpawn({ pid: child.pid, startedAt }).catch((err) => {
|
|
||||||
onLogError(err, runId, "failed to record child process metadata");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
runningProcesses.set(runId, { child, graceSec: opts.graceSec });
|
runningProcesses.set(runId, { child, graceSec: opts.graceSec });
|
||||||
|
|
||||||
|
|
@ -1116,6 +1112,15 @@ export async function runChildProcess(
|
||||||
.catch((err) => onLogError(err, runId, "failed to append stderr log chunk"));
|
.catch((err) => onLogError(err, runId, "failed to append stderr log chunk"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stdin = child.stdin;
|
||||||
|
if (opts.stdin != null && stdin) {
|
||||||
|
void spawnPersistPromise.finally(() => {
|
||||||
|
if (child.killed || stdin.destroyed) return;
|
||||||
|
stdin.write(opts.stdin as string);
|
||||||
|
stdin.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
child.on("error", (err: Error) => {
|
child.on("error", (err: Error) => {
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
runningProcesses.delete(runId);
|
runningProcesses.delete(runId);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue