Skip to content

Commit be5c83e

Browse files
fix: bound recording processing transfers (#2227)
2 parents 24e3c74 + c538e9d commit be5c83e

28 files changed

Lines changed: 6086 additions & 64 deletions

.github/workflows/docker-build-media-server.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
build:
2828
name: Build Docker Image (${{ matrix.platform }})
2929
runs-on: ${{ matrix.runner }}
30+
timeout-minutes: 25
3031
strategy:
3132
matrix:
3233
include:
@@ -79,6 +80,8 @@ jobs:
7980
docker run --rm --network none --entrypoint bun "$MEDIA_IMAGE" test \
8081
src/__tests__/lib/recording-verification.integration.test.ts \
8182
src/__tests__/lib/job-manager.test.ts
83+
docker run --rm --network none --entrypoint bun "$MEDIA_IMAGE" test \
84+
src/__tests__/lib/media-transfer.test.ts
8285
docker run --rm --network none --entrypoint bun "$MEDIA_IMAGE" test \
8386
src/__tests__/routes/recording-verification.test.ts
8487
docker run --rm --network none --entrypoint bun "$MEDIA_IMAGE" test \
@@ -97,6 +100,10 @@ jobs:
97100
--entrypoint bun "$MEDIA_IMAGE" test \
98101
src/__tests__/lib/recording-verification.integration.test.ts \
99102
--test-name-pattern 'streams a complete long recording'
103+
docker run --rm --network none --cpus 2 --memory 2g \
104+
-e MEDIA_SERVER_TRANSFER_PERFORMANCE_TESTS=1 \
105+
--entrypoint bun "$MEDIA_IMAGE" test \
106+
src/__tests__/lib/media-transfer.integration.test.ts
100107
101108
- name: Export Digest
102109
if: github.event_name != 'pull_request'

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ These rules are enforced by CI (`cargo clippy -D warnings`, Biome). Fixing them
66

77
### Zero-tolerance rules
88
- **Default to no code comments. Add a comment only after solving a bug or working through a complex issue, and only when it captures non-obvious context that a future investigator or reviewer genuinely needs** — e.g. why the fix looks the way it does, the upstream/platform bug being worked around, a non-obvious invariant or trade-off chosen after investigation, or a link to the PR/issue that explains the decision. Bad cases that remain banned: narrating what the code does, restating types, JSDoc that paraphrases parameter names, "TODO: refactor" or "this should be cleaner" notes, and any comment that just describes the change you are currently making. When in doubt, prefer better naming/types over a comment. Applies to every language: Rust, TS, JS, Python, shell, SQL, TOML, etc.
9-
- **Never edit generated files**: `**/tauri.ts`, `apps/desktop/src-tauri/gen/**`, `packages/ui-solid/src/auto-imports.d.ts`, Drizzle migration SQL under `packages/database/migrations/`. These are regenerated (e.g. `tauri.ts` only on debug desktop runs) but stay committed because CI typecheck and fresh clones depend on them; commit binding changes alongside the Rust change that produced them. Note: `apps/desktop/src/utils/queries.ts` is hand-written, not generated — edit it normally.
9+
- **Never hand-edit generated files**: `**/tauri.ts`, `apps/desktop/src-tauri/gen/**`, `packages/ui-solid/src/auto-imports.d.ts`, Drizzle migration SQL under `packages/database/migrations/`. These are regenerated (e.g. `tauri.ts` only on debug desktop runs) but stay committed because CI typecheck and fresh clones depend on them; commit binding changes alongside the Rust change that produced them. For database schema changes, run `pnpm db:generate` and commit the generated SQL, snapshot, and journal changes alongside the schema change. Generating and committing these artifacts is required; modifying generated output by hand is prohibited. Note: `apps/desktop/src/utils/queries.ts` is hand-written, not generated — edit it normally.
1010
- **Never start additional dev servers** (`pnpm dev`, `pnpm dev:web`, `pnpm dev:desktop`, Docker services). Assume they are already running.
1111

1212
### Post-edit checks (run before you say "done")

apps/media-server/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ RUN bun install --frozen-lockfile --production
1212
COPY apps/media-server/src ./src
1313

1414
RUN bun test src/__tests__/lib/recording-verification.integration.test.ts src/__tests__/lib/job-manager.test.ts \
15+
&& bun test src/__tests__/lib/media-transfer.test.ts \
1516
&& bun test src/__tests__/routes/recording-verification.test.ts \
1617
&& bun test src/__tests__/routes/video.test.ts \
1718
&& bun test src/__tests__/lib/media-routes-real-world.integration.test.ts \
@@ -21,6 +22,9 @@ RUN MEDIA_SERVER_RECORDING_PERFORMANCE_TESTS=1 bun test \
2122
src/__tests__/lib/recording-verification.integration.test.ts \
2223
--test-name-pattern 'streams a complete long recording'
2324

25+
RUN MEDIA_SERVER_TRANSFER_PERFORMANCE_TESTS=1 bun test \
26+
src/__tests__/lib/media-transfer.integration.test.ts
27+
2428
ENV PORT=3456
2529
EXPOSE 3456
2630

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { expect, test } from "bun:test";
2+
import { createHash } from "node:crypto";
3+
import { mkdtemp, rm, stat } from "node:fs/promises";
4+
import { tmpdir } from "node:os";
5+
import { join } from "node:path";
6+
import { downloadDriveRevision } from "../../lib/media-transfer";
7+
8+
test.skipIf(process.env.MEDIA_SERVER_TRANSFER_PERFORMANCE_TESTS !== "1")(
9+
"streams and verifies a transfer larger than two GiB with bounded memory",
10+
async () => {
11+
const directory = await mkdtemp(join(tmpdir(), "cap-large-transfer-"));
12+
const path = join(directory, "input.bin");
13+
const chunk = Buffer.alloc(1024 ** 2, 0x5a);
14+
const size = 2 * 1024 ** 3 + chunk.length;
15+
const expected = createHash("sha256");
16+
for (let offset = 0; offset < size; offset += chunk.length)
17+
expected.update(chunk);
18+
const sha256 = expected.digest("hex");
19+
let requests = 0;
20+
let transferred = 0;
21+
let peakRss = process.memoryUsage().rss;
22+
const timer = setInterval(() => {
23+
peakRss = Math.max(peakRss, process.memoryUsage().rss);
24+
}, 25);
25+
const started = performance.now();
26+
try {
27+
const fetcher = Object.assign(
28+
async () => {
29+
requests++;
30+
let sent = 0;
31+
return new Response(
32+
new ReadableStream<Uint8Array>({
33+
pull(controller) {
34+
if (sent === size) controller.close();
35+
else {
36+
controller.enqueue(chunk);
37+
sent += chunk.length;
38+
}
39+
},
40+
}),
41+
{ headers: { "Content-Length": String(size) } },
42+
);
43+
},
44+
{ preconnect: fetch.preconnect },
45+
);
46+
await downloadDriveRevision(
47+
{
48+
version: 1,
49+
url: "https://www.googleapis.com/drive/v3/files/test/revisions/test?alt=media",
50+
authorization: "Bearer synthetic-test",
51+
objectIdentity: '"synthetic-large-revision"',
52+
size,
53+
sha256,
54+
},
55+
path,
56+
{
57+
fetcher,
58+
onBytes: (bytes) => {
59+
transferred += bytes;
60+
},
61+
},
62+
);
63+
const actual = createHash("sha256");
64+
for await (const bytes of Bun.file(path).stream()) actual.update(bytes);
65+
expect(actual.digest("hex")).toBe(sha256);
66+
expect((await stat(path)).size).toBe(size);
67+
expect(transferred).toBe(size);
68+
expect(requests).toBe(1);
69+
expect(peakRss).toBeLessThan(512 * 1024 ** 2);
70+
console.info("[large-transfer-verification]", {
71+
bytes: size,
72+
elapsedMs: Math.round(performance.now() - started),
73+
peakRssMB: Math.round(peakRss / 1024 ** 2),
74+
});
75+
} finally {
76+
clearInterval(timer);
77+
await rm(directory, { recursive: true, force: true });
78+
}
79+
},
80+
180_000,
81+
);

0 commit comments

Comments
 (0)