Files
usenet-indexer/commands/coordinator_run.ts
T
Daan MeijerandClaude Opus 5.5 d5f168d8d2
Test and deploy / test (push) Successful in 21s
Test and deploy / deploy (push) Skipped
Rebuild: coordinator (planner, NNTP pool, spool, prefetcher, leases, rule sets), node API and the edge:node worker
- groups:add / ranges:plan / nodes:create / coordinator:run commands
- the node API: lease, the range's chunks as one framed stream from the
  spool, batches, heartbeat, finish, release (node guard, token per node)
- edge:node: needs COORDINATOR_URL and NODE_TOKEN only (placeholders for
  the rest), posts a batch every 10 chunks, gives its range back on SIGTERM
- releases are gathered per batch: 1.04 chunk rows per release at 10
  chunks per batch (1.35 when built per XOVER chunk)
- partitions are checked per batch instead of remembered

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:31:06 +02:00

52 lines
1.8 KiB
TypeScript

import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
import { setTimeout as sleep } from 'node:timers/promises'
import { Prefetcher } from '#services/prefetcher'
import Planner from '#services/planner'
import NntpPool from '#services/nntp_pool'
const TICK_MS = 2000
const PLAN_EVERY_MS = 60 * 60 * 1000
/**
* The coordinator's background work (REBUILD.md, part 1): the prefetcher,
* and planning ranges for new articles every hour. The API (leases, stream,
* batches) runs in the web server.
*/
export default class CoordinatorRun extends BaseCommand {
static commandName = 'coordinator:run'
static description = 'Prefetches ranges into the spool and plans new ones'
static options: CommandOptions = { startApp: true, staysAlive: true }
async run() {
const prefetcher = new Prefetcher()
let stopping = false
let stopped: () => void
const done = new Promise<void>((resolve) => (stopped = resolve))
// bin/console turns SIGTERM into app.terminate: wait for the spooling in flight first.
this.app.terminating(async () => {
stopping = true
prefetcher.stop()
await done
})
let plannedAt = 0
this.logger.info('Coordinator started.')
while (!stopping) {
try {
if (Date.now() - plannedAt >= PLAN_EVERY_MS) {
plannedAt = Date.now()
const { groups, ranges } = await Planner.planAll()
if (ranges > 0) this.logger.info(`Planned ${ranges} ranges over ${groups} groups.`)
}
await prefetcher.tick()
} catch (error: any) {
this.logger.error(`Coordinator round failed: ${error.message}`)
}
await sleep(TICK_MS)
}
while (prefetcher.active > 0) await sleep(200)
await NntpPool.close()
stopped!()
}
}