Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/producer/src/services/distributed/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { join, relative, sep } from "node:path";
import { type CanvasResolution } from "@hyperframes/core";
import { type EngineConfig, getEncoderPreset, resolveConfig } from "@hyperframes/engine";
import { defaultLogger, type ProducerLogger } from "../../logger.js";
import { closeFileServerSafely } from "../fileServer.js";
import { runAudioStage } from "../render/stages/audioStage.js";
import { runCompileStage } from "../render/stages/compileStage.js";
import { runExtractVideosStage } from "../render/stages/extractVideosStage.js";
Expand Down Expand Up @@ -833,7 +834,7 @@ export async function plan(
job.duration = probeResult.duration;
job.totalFrames = probeResult.totalFrames;
const totalFrames = probeResult.totalFrames;
if (probeResult.fileServer) probeResult.fileServer.close();
if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
if (probeResult.probeSession) {
// Close inside a try/catch — leaking a Chrome process here would mask
// the original plan() result on cancellation paths.
Expand Down
9 changes: 7 additions & 2 deletions packages/producer/src/services/distributed/renderChunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ import {
} from "../render/stages/freezePlan.js";
import { sha256Hex } from "../render/stages/planHash.js";
import { applyRuntimeEnvSnapshot } from "../render/runtimeEnvSnapshot.js";
import { buildVirtualTimeShim, createFileServer, type FileServerHandle } from "../fileServer.js";
import {
buildVirtualTimeShim,
closeFileServerSafely,
createFileServer,
type FileServerHandle,
} from "../fileServer.js";
import {
buildSyntheticRenderJob,
type DistributedFormat,
Expand Down Expand Up @@ -654,7 +659,7 @@ export async function renderChunk(
});
}
}
fileServer.close();
closeFileServerSafely(fileServer, "renderChunk", log);
// Leave the temp work dir on failure (helps debugging); remove it on
// success below.
}
Expand Down
40 changes: 40 additions & 0 deletions packages/producer/src/services/fileServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node
import path, { join } from "node:path";
import { tmpdir } from "node:os";
import {
closeFileServerSafely,
createFileServer,
HF_BRIDGE_SCRIPT,
HF_EARLY_STUB,
Expand All @@ -11,6 +12,45 @@ import {
VIRTUAL_TIME_SHIM,
} from "./fileServer.js";

function captureLogger() {
const warnings: { message: string; meta?: Record<string, unknown> }[] = [];
return {
warnings,
log: {
error() {},
warn(message: string, meta?: Record<string, unknown>) {
warnings.push({ message, meta });
},
info() {},
debug() {},
},
};
}

describe("closeFileServerSafely", () => {
it("swallows and logs a throwing close instead of propagating", () => {
const { log, warnings } = captureLogger();
const fileServer = {
close: () => {
// http.Server.close() throws ERR_SERVER_NOT_RUNNING on a second close.
throw new Error("Server is not running.");
},
};
expect(() => closeFileServerSafely(fileServer, "plan", log)).not.toThrow();
expect(warnings).toHaveLength(1);
expect(warnings[0].message).toContain("[plan]");
expect(warnings[0].meta?.error).toBe("Server is not running.");
});

it("closes once and stays quiet on the happy path", () => {
const { log, warnings } = captureLogger();
let closed = 0;
closeFileServerSafely({ close: () => closed++ }, "renderChunk", log);
expect(closed).toBe(1);
expect(warnings).toHaveLength(0);
});
});

describe("injectScriptsIntoHtml", () => {
it("injects the virtual time shim into head content before authored scripts", () => {
const html = `<!DOCTYPE html>
Expand Down
25 changes: 25 additions & 0 deletions packages/producer/src/services/fileServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { join, extname, resolve, sep } from "node:path";
import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
import { getVerifiedHyperframeRuntimeSource } from "./hyperframeRuntimeLoader.js";
import { getHfEarlyStub } from "../generated/hf-early-stub-inline.js";
import { defaultLogger, type ProducerLogger } from "../logger.js";

export { injectScriptsAtHeadStart };

Expand Down Expand Up @@ -558,6 +559,30 @@ export interface FileServerHandle {
addPreHeadScript: (script: string) => void;
}

/**
* Close a file server handle, swallowing and logging any error.
*
* `FileServerHandle.close` tears down the underlying http.Server, whose
* `close()` throws `ERR_SERVER_NOT_RUNNING` if the server is already torn down
* (for example a cancellation path that closed it once already). An unguarded
* throw inside a cleanup or `finally` block would mask the original render or
* plan result, so cleanup callers must go through this instead of calling
* `close()` directly.
*/
export function closeFileServerSafely(
fileServer: Pick<FileServerHandle, "close">,
label: string,
log: ProducerLogger = defaultLogger,
): void {
try {
fileServer.close();
} catch (err) {
log.warn(`[${label}] file server close failed`, {
error: err instanceof Error ? err.message : String(err),
});
}
}

export function createFileServer(options: FileServerOptions): Promise<FileServerHandle> {
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;

Expand Down
3 changes: 2 additions & 1 deletion packages/producer/src/services/renderOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { join, dirname, resolve } from "path";
import { randomUUID } from "crypto";
import { fileURLToPath } from "url";
import {
closeFileServerSafely,
createFileServer,
type FileServerHandle,
HF_PAGE_SIDE_COMPOSITING_STUB,
Expand Down Expand Up @@ -2274,7 +2275,7 @@ export async function executeRenderJob(
if (frameLookup) frameLookup.cleanup();

// Stop file server
fileServer.close();
closeFileServerSafely(fileServer, "renderOrchestrator", log);
fileServer = null;

// ── Stage 6: Assemble ───────────────────────────────────────────────
Expand Down
Loading