refactor(core): migrate to Bun and converge acquisition policies and Agent boundaries
This commit is contained in:
1 parent
3f1c6d02f6
commit
27c0f27434
268 files changed
+7468
-7852
No files matched your search
+37
@@ -0,0 +1,37 @@
|
||||
import { decodeWorkerRequest, readWorkerRequestText, writeWorkerMessage } from "../../src/worker.ts";
|
||||
|
||||
const req = decodeWorkerRequest(await readWorkerRequestText(4096));
|
||||
const send = (type: "result", result: unknown) => writeWorkerMessage({ id: req.id, type, result }, 4096);
|
||||
switch (req.task) {
|
||||
case "normal":
|
||||
writeWorkerMessage({ id: req.id, type: "progress", value: { bytes: 2 } }, 4096);
|
||||
send("result", req.input);
|
||||
break;
|
||||
case "large":
|
||||
writeWorkerMessage({ id: req.id, type: "result", result: "中文".repeat(200_000) }, 2 * 1024 * 1024);
|
||||
break;
|
||||
case "wrong-id":
|
||||
writeWorkerMessage({ id: "other", type: "result", result: true }, 4096);
|
||||
break;
|
||||
case "truncated":
|
||||
process.stdout.write('{"id":"1"');
|
||||
break;
|
||||
case "oversized":
|
||||
process.stdout.write("x".repeat(8192));
|
||||
break;
|
||||
case "after-result":
|
||||
send("result", true);
|
||||
send("result", false);
|
||||
break;
|
||||
case "failed-exit":
|
||||
send("result", true);
|
||||
process.exitCode = 1;
|
||||
break;
|
||||
case "hang":
|
||||
setInterval(() => {}, 1000);
|
||||
break;
|
||||
case "stderr":
|
||||
process.stderr.write("x".repeat(65536));
|
||||
send("result", true);
|
||||
break;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { WorkerConnection, workerLines } from "../src/worker.ts";
|
||||
|
||||
const fixture = fileURLToPath(new URL("./fixtures/worker.ts", import.meta.url));
|
||||
const limits = { maxRequestBytes: 4096, maxResponseBytes: 4096 };
|
||||
function connection() {
|
||||
return new WorkerConnection({ cmd: process.execPath, args: [fixture] });
|
||||
}
|
||||
const request = (task: string) => ({ id: "1", task, input: "中文" });
|
||||
|
||||
describe("worker 管道与回收", () => {
|
||||
it("拆开的 UTF-8 和多行消息仍能正确分帧", async () => {
|
||||
const bytes = new TextEncoder().encode("中文\n第二行\n");
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(c) {
|
||||
for (const b of bytes) c.enqueue(Uint8Array.of(b));
|
||||
c.close();
|
||||
},
|
||||
});
|
||||
const lines = [];
|
||||
for await (const line of workerLines(stream.getReader(), 64)) lines.push(line);
|
||||
expect(lines).toEqual(["中文", "第二行"]);
|
||||
});
|
||||
|
||||
it("转发进度、返回结果,并持续排空大量 stderr", async () => {
|
||||
const seen: unknown[] = [];
|
||||
expect(await connection().request(request("normal"), { ...limits, onProgress: (p) => seen.push(p) })).toEqual({
|
||||
id: "1",
|
||||
type: "result",
|
||||
result: "中文",
|
||||
});
|
||||
expect(seen).toEqual([{ bytes: 2 }]);
|
||||
expect(await connection().request(request("stderr"), limits)).toMatchObject({ result: true });
|
||||
});
|
||||
|
||||
it("大结果跨多个管道分块也完整返回", async () => {
|
||||
const reply = await connection().request(request("large"), { ...limits, maxResponseBytes: 2 * 1024 * 1024 });
|
||||
expect(reply).toEqual({ id: "1", type: "result", result: "中文".repeat(200_000) });
|
||||
});
|
||||
|
||||
it.each([
|
||||
["wrong-id", "protocol"],
|
||||
["truncated", "protocol"],
|
||||
["oversized", "size"],
|
||||
["after-result", "protocol"],
|
||||
["failed-exit", "exit"],
|
||||
])("拒绝 %s,而不接受部分或错误响应", async (task, kind) => {
|
||||
await expect(connection().request(request(task), limits)).rejects.toMatchObject({ kind });
|
||||
});
|
||||
|
||||
it("请求大小超限也回收已启动的进程", async () => {
|
||||
const c = connection();
|
||||
await expect(c.request({ ...request("hang"), input: "x".repeat(8192) }, limits)).rejects.toMatchObject({
|
||||
kind: "size",
|
||||
});
|
||||
});
|
||||
|
||||
it("启动失败保留 spawn 分类", async () => {
|
||||
const c = new WorkerConnection({ cmd: "/nonexistent/umt-worker", args: [] });
|
||||
await expect(c.request(request("normal"), limits)).rejects.toMatchObject({ kind: "spawn" });
|
||||
});
|
||||
|
||||
it("执行中的请求可终止,返回调用方的原因", async () => {
|
||||
const c = connection();
|
||||
const pending = c.request(request("hang"), limits);
|
||||
const reason = new Error("调用方取消");
|
||||
c.terminate(reason, 50);
|
||||
await expect(pending).rejects.toBe(reason);
|
||||
});
|
||||
|
||||
it("进度消费者抛错时回收连接,不让任务继续等待", async () => {
|
||||
await expect(
|
||||
connection().request(request("normal"), {
|
||||
...limits,
|
||||
onProgress: () => {
|
||||
throw new Error("进度无效");
|
||||
},
|
||||
}),
|
||||
).rejects.toMatchObject({ kind: "protocol", message: expect.stringContaining("进度无效") });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user