Archilyzer · Source

archilyzer

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

commit c661ab224d6fe5d7596ff72042b58caf358afc2b
parent 869c914abe607a9dea7664be75951dec9fdd9e13
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 22 Sep 2026 16:17:59 -0400

operations: the four-lane status poll reads the corpus once

buildAutoQueueStatusPayload already reads the channel listing and the
auto-queue state document for the rest of its payload, then asked four lanes
for their pending work — and each computeLeafPending listed every channel's
config off disk again (a readdir plus a read per channel, thirty on Jeralyzer)
and re-parsed the state. Nothing about either read is per-lane, and this runs
on a ~3 s poll.

computeLeafPending takes an optional `shared: { configs, state }`; omitted, it
reads both exactly as before, so every other caller is untouched. The page
passes the briefs it already has and the state it already parsed.

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

Diffstat:
Mcommon/controller/autoRunner.test.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/controller/autoRunner.ts | 21+++++++++++++++++++--
Meditor/app/operations/status.ts | 11+++++++++--
3 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/common/controller/autoRunner.test.ts b/common/controller/autoRunner.test.ts @@ -4,6 +4,7 @@ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { + computeLeafPending, focusHoldLine, laneDispatchRoot, makeFocusHoldReporter, @@ -19,6 +20,11 @@ import { type FocusSummary, } from "../lib/channelPriority"; import { LANES } from "../lib/autoQueueTypes"; +import { + emptyAutoQueueKindState, + emptyAutoQueueState, + type AutoQueueState, +} from "../jobs/autoQueueState"; import type { AutoQueueGroup, AutoQueuePolicy, @@ -479,3 +485,64 @@ test("a focus holds the scan the same way it holds every download pick", () => { ); }); + +// --- computeLeafPending's `shared`: the four-lane poll reads once ----------- +// +// /operations asks all four lanes for their pending work on a ~3 s poll, and +// each call used to list every channel's config off disk and re-parse the +// auto-queue state — both of which the caller had already read for the rest of +// the payload, and neither of which is per-lane. + +function pendingFixture(): Paths { + const dir = mkdtempSync(path.join(tmpdir(), "leaf-pending-")); + const channelsDir = path.join(dir, "channels"); + // A channel that IS on disk. An injected listing has to be believed over it. + mkdirSync(path.join(channelsDir, "on-disk"), { recursive: true }); + writeFileSync( + path.join(channelsDir, "on-disk", "config.json"), + JSON.stringify({ url: "https://www.youtube.com/@ondisk" }), + ); + return { + channelsDir, + sitesDir: path.join(dir, "sites"), + autoQueueStateFile: path.join(dir, "auto-queue.json"), + } as Paths; +} + +test("computeLeafPending takes its channel listing from `shared`", async () => { + const paths = pendingFixture(); + const result = await computeLeafPending(LANES[0], paths, { + configs: [], + state: emptyAutoQueueState(), + }); + // The leaves the compiled root defines, all at zero: an empty listing is no + // channels, so there is no work to attribute and nothing to pick. + assert.deepEqual(Object.values(result.counts), [0]); + assert.deepEqual(Object.values(result.head), [[]]); + assert.deepEqual(result.owner, {}); + assert.equal(result.nextUp, null); +}); + +test("computeLeafPending reads the injected state, not the file", async () => { + const paths = pendingFixture(); + // readAutoQueueState returns a FULLY POPULATED state even when the file is + // absent (emptyAutoQueueState), so a state missing this lane can only be the + // injected one. Dereferencing it is the proof that no second read happened. + const missingLane = Object.fromEntries( + LANES.filter((lane) => lane !== LANES[0]).map((lane) => [ + lane, + emptyAutoQueueKindState(), + ]), + ) as unknown as AutoQueueState; + await assert.rejects( + computeLeafPending(LANES[0], paths, { configs: [], state: missingLane }), + TypeError, + ); + // And with the lane present it goes through, off the same absent file. + await assert.doesNotReject( + computeLeafPending(LANES[0], paths, { + configs: [], + state: emptyAutoQueueState(), + }), + ); +}); diff --git a/common/controller/autoRunner.ts b/common/controller/autoRunner.ts @@ -354,8 +354,12 @@ async function listChannelMeta( paths: Paths, kind: AutoQueueKind, priority: ChannelPriority, + // A listing the CALLER already has. The status poll reads every channel's + // config once and then asked four lanes for their pending work, each of which + // re-read the whole directory — see computeLeafPending's `shared`. + sharedConfigs?: readonly { slug: string; config: ChannelConfig }[], ): Promise<{ meta: ChannelMeta[]; slugs: string[] }> { - const configs = await listChannelConfigs(paths); + const configs = sharedConfigs ?? (await listChannelConfigs(paths)); return { meta: configs .filter(({ slug }) => !isChannelPaused(priority, slug, kind)) @@ -904,9 +908,21 @@ export type LeafPending = { // operator sees as "cornbreadman: 12 pending". Uses the same matching AND the // same ordering as the runner, so the numbers and the drill-down line up with // what would actually be picked. +// +// `shared` IS THE FOUR-LANE POLL'S WAY OUT OF READING EVERYTHING FOUR TIMES. +// /operations asks all four lanes on a ~3 s poll, and each call listed every +// channel's config (a readdir plus a read per channel — 30 on Jeralyzer) and +// parsed the auto-queue state document again, having already read both itself +// for the rest of the payload. Nothing about either read is per-lane, so a +// caller that holds them passes them in and the poll pays once. Omitted, each +// is read here exactly as before, so every other caller is unchanged. export async function computeLeafPending( kind: AutoQueueKind, paths: Paths = getPaths(), + shared?: { + configs?: readonly { slug: string; config: ChannelConfig }[]; + state?: AutoQueueState; + }, ): Promise<LeafPending> { const settings = getSettings(); const policy = settings.autoQueue[kind]; @@ -920,6 +936,7 @@ export async function computeLeafPending( paths, kind, settings.channelPriority, + shared?.configs, ); const ctx = priorityContextFor(paths, settings, slugs); const root = laneDispatchRoot(kind, policy, ctx, meta.map((m) => m.slug)); @@ -974,7 +991,7 @@ export async function computeLeafPending( // runtime.currentWeights in place. This runs on a 3-second status poll, so // asking the live runtime would let merely HAVING the page open skew a // round-robin group's rotation. Deep-clone first; the clone is discarded. - const state = await readAutoQueueState(paths); + const state = shared?.state ?? (await readAutoQueueState(paths)); const runtime = { currentWeights: { ...state[kind].runtime.currentWeights }, }; diff --git a/editor/app/operations/status.ts b/editor/app/operations/status.ts @@ -27,7 +27,8 @@ import { readPriorityView } from "./channelPriorityView"; // set costs a channel listing and, for a site focus, a sites read; the // auto-queue state document is one JSON parse — it was read once PER LANE, four // times per poll, because `buildKind` did its own reading; and the channel -// briefs are shared with the lanes builder through the per-request cache. +// briefs are shared with the lanes builder through the per-request cache, and +// with the four computeLeafPending calls through their `shared` argument. export async function buildAutoQueueStatusPayload(): Promise<AutoQueueStatusPayload> { const paths = getPaths(); const settings = getSettings(); @@ -37,8 +38,14 @@ export async function buildAutoQueueStatusPayload(): Promise<AutoQueueStatusPayl readAutoQueueState(paths), getChannelBriefs(paths), ]); + // …and the four lanes' pending work re-reads NEITHER. `briefs` is the channel + // listing and `state` the auto-queue document, both already in hand one line + // up; without them each of the four calls listed every channel's config off + // disk again and re-parsed the state, on a ~3 s poll. const pendingByKind = await Promise.all( - LANES.map((lane) => computeLeafPending(lane, paths)), + LANES.map((lane) => + computeLeafPending(lane, paths, { configs: briefs, state }), + ), ); const byLane = <T>(values: readonly T[]): Record<AutoQueueKind, T> => Object.fromEntries(LANES.map((lane, i) => [lane, values[i]])) as Record<