Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit ea672ea2cb7ffe32b1e047508356e4925c8806f7
parent fe16bc67fdcf6b346f57a7ccba513848d0753ec2
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 22:01:49 -0400

editor: UV_THREADPOOL_SIZE=16 for the editor's `start` and the container

Node runs every filesystem call on libuv's pool, four threads by default; a
call on a stalled drive holds one until the drive answers, so four such calls
stop the editor answering. `pnpm run start` in editor/ (which the rollout
restart script runs) and docker/entrypoint.sh now start it with 16, unless
the variable is already set. Declared in envVars.ts (runtime) and
ENVIRONMENT.md regenerated. It buys time for calls already in flight and
isolates nothing; the health probe and its gate are what keep new calls off a
stalled drive.

ports.test.ts read every `${NAME:-N}` in a package.json script as a port;
it now names UV_THREADPOOL_SIZE (the editor's `start`) as the one numeric
default that is not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
MENVIRONMENT.md | 1+
Mcommon/lib/envVars.ts | 1+
Mcommon/lib/ports.test.ts | 6++++++
Mdocker/entrypoint.sh | 6++++++
Meditor/package.json | 2+-
5 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md @@ -77,6 +77,7 @@ Tokens, credentials and knobs a running process reads. Most configuration is not | `AUDIO_CHECK_BACKOFF_FACTOR` | the built-in factor | The audio check's interval backoff factor, in (0, 1], for a one-off run. | common/ytdlp/audioCheckedDownload.ts | | `ARCHILYZER_STATS_ALLOW_DOWNGRADE` | off | `1` lets a stats build clear a stats cache that a NEWER build wrote, for a deliberate rollback. Unset, such a build refuses and names both versions. | common/controller/buildStats.ts | | `ARCHILYZER_INDEX_ALLOW_HELD` | off | `1` lets a FULL index rebuild (a schema change, or no index yet) proceed while a channel's media cannot be read; that channel stays out of the index until its media is back and the index is built again. Unset, such a build refuses and names each channel. | common/controller/buildIndex.ts | +| `UV_THREADPOOL_SIZE` | `16` for the editor (`4` is Node's own) | Threads in Node's pool for filesystem calls. A call on a stalled drive holds one until the drive answers, so the editor starts with 16. It buys time for calls already in flight and isolates nothing: the storage health probe and its gate keep new calls off a stalled drive. | Node's libuv (set by editor/package.json `start` and docker/entrypoint.sh) | | `MCP_IO_STATS` | off | `1` turns on per-call I/O accounting, for `mcp/bench`. | common/lib/archive/io-stats.ts | | `ARCHILYZER_EDITOR_URL` | `http://localhost:3001` | Which editor `pnpm ops` and the MCP's `fetch_clip` talk to. | scripts/archilyzer-ops.mjs, mcp/src/fetchClip.ts, umtool | | `ARCHILYZER_AGENT` | `cli` | Who is asking, recorded as the provenance of a curated-tag write through `pnpm ops`. | scripts/archilyzer-ops.mjs | diff --git a/common/lib/envVars.ts b/common/lib/envVars.ts @@ -113,6 +113,7 @@ const DECLARED: EnvVarDecl[] = [ { name: "AUDIO_CHECK_BACKOFF_FACTOR", audience: "runtime", default: "the built-in factor", readBy: "common/ytdlp/audioCheckedDownload.ts", doc: "The audio check's interval backoff factor, in (0, 1], for a one-off run." }, { name: "ARCHILYZER_STATS_ALLOW_DOWNGRADE", audience: "runtime", default: "off", readBy: "common/controller/buildStats.ts", doc: "`1` lets a stats build clear a stats cache that a NEWER build wrote, for a deliberate rollback. Unset, such a build refuses and names both versions." }, { name: "ARCHILYZER_INDEX_ALLOW_HELD", audience: "runtime", default: "off", readBy: "common/controller/buildIndex.ts", doc: "`1` lets a FULL index rebuild (a schema change, or no index yet) proceed while a channel's media cannot be read; that channel stays out of the index until its media is back and the index is built again. Unset, such a build refuses and names each channel." }, + { name: "UV_THREADPOOL_SIZE", audience: "runtime", default: "`16` for the editor (`4` is Node's own)", readBy: "Node's libuv (set by editor/package.json `start` and docker/entrypoint.sh)", doc: "Threads in Node's pool for filesystem calls. A call on a stalled drive holds one until the drive answers, so the editor starts with 16. It buys time for calls already in flight and isolates nothing: the storage health probe and its gate keep new calls off a stalled drive." }, { name: "MCP_IO_STATS", audience: "runtime", default: "off", readBy: "common/lib/archive/io-stats.ts", doc: "`1` turns on per-call I/O accounting, for `mcp/bench`." }, { name: "ARCHILYZER_EDITOR_URL", audience: "runtime", default: "`http://localhost:3001`", readBy: "scripts/archilyzer-ops.mjs, mcp/src/fetchClip.ts, umtool", doc: "Which editor `pnpm ops` and the MCP's `fetch_clip` talk to." }, { name: "ARCHILYZER_AGENT", audience: "runtime", default: "`cli`", readBy: "scripts/archilyzer-ops.mjs", doc: "Who is asking, recorded as the provenance of a curated-tag write through `pnpm ops`." }, diff --git a/common/lib/ports.test.ts b/common/lib/ports.test.ts @@ -23,6 +23,11 @@ import { const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); const PACKAGES = ["", "common", "editor", "export", "homepage", "mcp", "umtool", "umtool/report-to-video"]; +// A script's `${NAME:-N}` that is a number and NOT a port, named one by one so +// every other spelled default is still read as a port: the editor's `start` +// gives Node's thread pool 16 threads (envVars.ts, UV_THREADPOOL_SIZE). +const NUMERIC_NOT_PORTS = new Set(["UV_THREADPOOL_SIZE"]); + function scripts(dir: string): Record<string, string> { const file = path.join(ROOT, dir, "package.json"); return (JSON.parse(readFileSync(file, "utf8")).scripts ?? {}) as Record<string, string>; @@ -63,6 +68,7 @@ function found(): Found[] { for (const [script, line] of Object.entries(scripts(pkg))) { const where = `${pkg || "."}/package.json "${script}"`; for (const m of line.matchAll(/\$\{([A-Z0-9_]+):-(\d+)\}/g)) { + if (NUMERIC_NOT_PORTS.has(m[1])) continue; hits.push({ where, name: m[1], port: Number(m[2]) }); } for (const m of line.matchAll(/--ports\s+(\S+)/g)) { diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh @@ -222,6 +222,12 @@ editor) log "idle boot: schedulers, auto-queue runners and sweeps stay STOPPED" fi cd /repo/editor + # Sixteen threads for Node's filesystem pool instead of four. A call on a + # drive that has stalled holds its thread until the drive answers; with four, + # four such calls stop the editor answering at all. More threads buy time for + # calls already in flight — they isolate nothing (the storage health probe + # and its gate are what keep new calls off a stalled drive). + export UV_THREADPOOL_SIZE="${UV_THREADPOOL_SIZE:-16}" exec "$(next_bin /repo/editor)" start --port "${EDITOR_PORT:-3001}" ;; diff --git a/editor/package.json b/editor/package.json @@ -8,7 +8,7 @@ "dev:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md EXPORT_CHANGELOG_FILE=$(pwd)/test-export-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs FINDMNT_BIN=$(pwd)/e2e/fixtures/bin/fake-findmnt.mjs UDISKSCTL_BIN=$(pwd)/e2e/fixtures/bin/fake-udisksctl.mjs next dev --port ${PORT:-3011}", "start:test": "WORKER_TOKEN=test-worker-token TRANSCRIPTS_DIR=$(pwd)/test-transcripts EXPORT_PUBLIC_DIR=$(pwd)/test-transcripts/.export-public SETTINGS_FILE=$(pwd)/test-settings.json EDITOR_CHANGELOG_FILE=$(pwd)/test-changelog.md EXPORT_CHANGELOG_FILE=$(pwd)/test-export-changelog.md YTDLP_BIN=$(pwd)/e2e/fixtures/bin/fake-ytdlp.mjs GALLERY_DL_BIN=$(pwd)/e2e/fixtures/bin/fake-gallery-dl.mjs WHISPER_BIN=$(pwd)/e2e/fixtures/bin/fake-whisper.mjs WHISPER_MODEL=/dev/null CHOUGH_BIN=$(pwd)/e2e/fixtures/bin/fake-chough.mjs CHOUGH_MODEL=/dev/null PARAKEET_STITCH_BIN=$(pwd)/e2e/fixtures/bin/fake-parakeet-stitch.mjs PARAKEET_CLI=/dev/null PARAKEET_MODEL=/dev/null DIARIZE_BIN=$(pwd)/e2e/fixtures/bin/fake-diarize.mjs FFMPEG_BIN=$(pwd)/e2e/fixtures/bin/fake-ffmpeg.mjs FFPROBE_BIN=$(pwd)/e2e/fixtures/bin/fake-ffprobe.mjs OLLAMA_URL=http://127.0.0.1:${OLLAMA_STUB_PORT:-11435} CLAUDE_BIN=$(pwd)/e2e/fixtures/bin/fake-claude.mjs FINDMNT_BIN=$(pwd)/e2e/fixtures/bin/fake-findmnt.mjs UDISKSCTL_BIN=$(pwd)/e2e/fixtures/bin/fake-udisksctl.mjs next start --port ${PORT:-3011}", "build": "next build", - "start": "next start --port ${EDITOR_PORT:-3001}", + "start": "UV_THREADPOOL_SIZE=${UV_THREADPOOL_SIZE:-16} next start --port ${EDITOR_PORT:-3001}", "lint": "eslint", "e2e": "node ../scripts/queue-lock.mjs --ports PORT:3011,EXPORT_PORT:3010,OLLAMA_STUB_PORT:11435 -- playwright test", "e2e:ui": "playwright test --ui"