import { spawn, type ChildProcess } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { setTimeout as delay } from 'node:timers/promises'; import { nextRunAt } from './schedule.ts'; import { kstFields } from '../../src/ui/time.ts'; import { THREADS } from '../../src/constants/threads.ts'; const stop = new AbortController(); let child: ChildProcess | undefined; for (const signal of ['SIGINT', 'SIGTERM'] as const) { process.once(signal, () => { stop.abort(); child?.kill('SIGTERM'); }); } async function runJob() { return new Promise((resolve, reject) => { const job = spawn(process.execPath, [fileURLToPath(new URL('./run.ts', import.meta.url))], { stdio: 'inherit' }); child = job; let timedOut = false; let forceStop: ReturnType | undefined; const deadline = setTimeout(() => { timedOut = true; console.error('Daily job exceeded eight hours; terminating before retry'); job.kill('SIGTERM'); forceStop = setTimeout(() => job.kill('SIGKILL'), THREADS.jobStopGraceMs); }, THREADS.jobTimeoutMs); const clear = () => { clearTimeout(deadline); clearTimeout(forceStop); child = undefined; }; job.once('error', error => { clear(); reject(error); }); job.once('exit', code => { clear(); resolve(code === 0 && !timedOut); }); }); } async function main() { let completedDate: string | undefined; while (!stop.signal.aborted) { const next = nextRunAt(new Date(), completedDate); console.log(`Next daily preparation: ${next.toISOString()} (schedule: 03:00 Asia/Seoul)`); while (+next > Date.now()) await delay(Math.min(+next - Date.now(), 60_000), undefined, { signal: stop.signal }); if (stop.signal.aborted) break; const date = kstFields(new Date()).date; for (let attempt = 1; attempt <= THREADS.retryAttempts && !stop.signal.aborted; attempt++) { if (await runJob()) break; console.error(`Daily job failed (${attempt}/${THREADS.retryAttempts})`); if (attempt < THREADS.retryAttempts) await delay(THREADS.retryMs, undefined, { signal: stop.signal }); } completedDate = date; } } void main().catch(error => { if (stop.signal.aborted) return; console.error(error instanceof Error ? error.message : 'Threads worker failed'); process.exitCode = 1; });