import { BaseCommand, flags } from '@adonisjs/core/ace' import { CommandOptions } from '@adonisjs/core/types/ace' import env from '#start/env' import Shutdown from '#services/Shutdown' import NntpService from '#services/NntpService' import QueueService from '#services/QueueService' import DeferredService from '#services/DeferredService' const IDLE_MS = 60_000 /** * Works through `deferred_files` (CONCEPT.md D28): one body per file, and * small files completely. Not started by default (compose profile * `deferred`); run it when there is bandwidth to spare. */ export default class DeferredWorker extends BaseCommand { public static commandName = 'worker:deferred' public static description = 'Samples deferred files (e.g. ngPost uploads): one article body per file.' public static options: CommandOptions = { startApp: true, } @flags.number({ description: 'Stop after this many files (default: keep running)' }) declare limit?: number public async run() { const concurrency = env.get('DEFERRED_CONCURRENCY', 4) this.logger.info(`Deferred worker started (concurrency ${concurrency}).`) const counts: Record = {} let done = 0 try { while (!Shutdown.requested && (!this.limit || done < this.limit)) { try { const batch = await DeferredService.claim(Math.min(concurrency * 4, this.limit ? this.limit - done : Infinity)) if (batch.length === 0) { if (this.limit) break await Shutdown.sleep(IDLE_MS) continue } let next = 0 await Promise.all( Array.from({ length: concurrency }, async () => { while (next < batch.length) { const status = await DeferredService.process(batch[next++]) counts[status] = (counts[status] ?? 0) + 1 } }) ) done += batch.length this.logger.info(`${done} deferred files processed: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(', ')}.`) } catch (err: any) { // E.g. Postgres restarting: wait and claim again, instead of ending the loop (LOG.md 2026-10-07 (3)). this.logger.error(`Round failed, retrying in 30 s: ${err.message}`) await Shutdown.sleep(30_000) } } } finally { await NntpService.shutdown() await QueueService.closeAll() } } }