Skip to content

Commit af9bad9

Browse files
committed
fix(gateway): avoid sync Control UI asset reads
1 parent 3995d57 commit af9bad9

3 files changed

Lines changed: 45 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
4545

4646
### Fixes
4747

48+
- Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.
4849
- Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.
4950
- Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.
5051
- Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.

src/gateway/control-ui.http.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createHash } from "node:crypto";
2+
import fsSync from "node:fs";
23
import fs from "node:fs/promises";
34
import type { IncomingMessage } from "node:http";
45
import os from "node:os";
@@ -1100,6 +1101,30 @@ describe("handleControlUiHttpRequest", () => {
11001101
});
11011102
});
11021103

1104+
it("serves static assets without synchronous file reads", async () => {
1105+
await withControlUiRoot({
1106+
fn: async (tmp) => {
1107+
await writeAssetFile(tmp, "actual.txt", "inside-ok\n");
1108+
const readFileSync = vi.spyOn(fsSync, "readFileSync").mockImplementation(() => {
1109+
throw new Error("readFileSync should not run on Control UI request path");
1110+
});
1111+
try {
1112+
const { res, end, handled } = await runControlUiRequest({
1113+
url: "/assets/actual.txt",
1114+
method: "GET",
1115+
rootPath: tmp,
1116+
});
1117+
1118+
expect(handled).toBe(true);
1119+
expect(res.statusCode).toBe(200);
1120+
expect(responseBody(end)).toBe("inside-ok\n");
1121+
} finally {
1122+
readFileSync.mockRestore();
1123+
}
1124+
},
1125+
});
1126+
});
1127+
11031128
it("serves HEAD for in-root assets without writing a body", async () => {
11041129
await withControlUiRoot({
11051130
fn: async (tmp) => {

src/gateway/control-ui.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,22 @@ function serveResolvedIndexHtml(res: ServerResponse, body: string) {
754754
res.end(body);
755755
}
756756

757+
function readOpenedFile(fd: number): Promise<Buffer> {
758+
return new Promise((resolve, reject) => {
759+
fs.readFile(fd, (error, data) => {
760+
if (error) {
761+
reject(error);
762+
return;
763+
}
764+
resolve(data);
765+
});
766+
});
767+
}
768+
769+
async function readOpenedFileText(fd: number): Promise<string> {
770+
return (await readOpenedFile(fd)).toString("utf8");
771+
}
772+
757773
function isExpectedSafePathError(error: unknown): boolean {
758774
const code =
759775
typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
@@ -995,10 +1011,10 @@ export async function handleControlUiHttpRequest(
9951011
return true;
9961012
}
9971013
if (path.basename(safeFile.path) === "index.html") {
998-
serveResolvedIndexHtml(res, fs.readFileSync(safeFile.fd, "utf8"));
1014+
serveResolvedIndexHtml(res, await readOpenedFileText(safeFile.fd));
9991015
return true;
10001016
}
1001-
serveResolvedFile(res, safeFile.path, fs.readFileSync(safeFile.fd));
1017+
serveResolvedFile(res, safeFile.path, await readOpenedFile(safeFile.fd));
10021018
return true;
10031019
} finally {
10041020
fs.closeSync(safeFile.fd);
@@ -1023,7 +1039,7 @@ export async function handleControlUiHttpRequest(
10231039
if (respondHeadForFile(req, res, safeIndex.path)) {
10241040
return true;
10251041
}
1026-
serveResolvedIndexHtml(res, fs.readFileSync(safeIndex.fd, "utf8"));
1042+
serveResolvedIndexHtml(res, await readOpenedFileText(safeIndex.fd));
10271043
return true;
10281044
} finally {
10291045
fs.closeSync(safeIndex.fd);

0 commit comments

Comments
 (0)