mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix: a stalled remote asset download no longer hangs a Cloud Run render (#4343)
* fix(gcp-cloud-run): run the Cloud Run image on Bun 1.4.2 so remote downloads cannot hang * fix(engine): make the download deadline abort the body pipeline
This commit is contained in:
@@ -1129,6 +1129,55 @@ describe("downloadToTemp atomic publication and bounded retry", () => {
|
||||
expect(temporaryDownloadEntries(dir)).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps the deadline active when a stalled body never observes the abort", async () => {
|
||||
// Bun 1.3.9's Readable.fromWeb can stop reading mid-body; the fetch abort then never
|
||||
// reaches the pipeline. Model that with a body that neither ends nor errors on abort.
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockImplementationOnce(async () => {
|
||||
const stalledBody = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("partial"));
|
||||
},
|
||||
});
|
||||
return new Response(stalledBody);
|
||||
})
|
||||
.mockResolvedValueOnce(new Response("complete"));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
const dir = makeTempDir();
|
||||
|
||||
const path = await downloadToTemp("https://cdn.example/unresponsive-body.mp4", dir, 20);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(readFileSync(path, "utf8")).toBe("complete");
|
||||
expect(temporaryDownloadEntries(dir)).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not publish a chunked body that ended because the deadline aborted it", async () => {
|
||||
// Bun 1.4.2 ends an aborted fetch body as if it were complete. With no Content-Length
|
||||
// there is no length check to catch the truncation.
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockImplementationOnce(async (_url: string, init: RequestInit) => {
|
||||
const truncatedBody = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("partial"));
|
||||
init.signal?.addEventListener("abort", () => controller.close(), { once: true });
|
||||
},
|
||||
});
|
||||
return new Response(truncatedBody);
|
||||
})
|
||||
.mockResolvedValueOnce(new Response("complete"));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
const dir = makeTempDir();
|
||||
|
||||
const path = await downloadToTemp("https://cdn.example/aborted-chunked-body.mp4", dir, 20);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
expect(readFileSync(path, "utf8")).toBe("complete");
|
||||
expect(temporaryDownloadEntries(dir)).toEqual([]);
|
||||
});
|
||||
|
||||
it("retries a zero-byte 200 response without publishing it", async () => {
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
|
||||
@@ -868,7 +868,11 @@ async function fetchToPartial(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const readableStream = Readable.fromWeb(response.body as any);
|
||||
const fileStream = createWriteStream(partialPath, { flags: "wx" });
|
||||
await pipeline(readableStream, inspector, fileStream);
|
||||
// The signal makes the attempt's deadline authoritative. Without it, the abort only
|
||||
// reaches the fetch, and the pipeline depends on the web-to-Node bridge to pass it on:
|
||||
// Bun 1.3.9's Readable.fromWeb can stop reading and never settle, and Bun 1.4.2's ends an
|
||||
// aborted body as if it were complete, which a chunked response would publish truncated.
|
||||
await pipeline(readableStream, inspector, fileStream, { signal: controller.signal });
|
||||
const localSize = statSync(partialPath).size;
|
||||
const sha256Bytes = sha256.digest();
|
||||
const localSha256 = sha256Bytes.toString("hex");
|
||||
|
||||
@@ -78,7 +78,7 @@ WORKDIR /app
|
||||
# the container runs `bun dist/server.js` and relies on bun's ESM `require`
|
||||
# interop to load the producer bundle, so a future bun release changing that
|
||||
# behaviour shouldn't silently break the next image rebuild. Bump deliberately.
|
||||
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.9"
|
||||
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v1.4.2"
|
||||
ENV PATH="/root/.bun/bin:$PATH"
|
||||
|
||||
# Install workspace dependencies. Copy manifests first for layer caching.
|
||||
|
||||
Reference in New Issue
Block a user