Add editable weather captions and manage Threads scheduling with PM2
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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<boolean>((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<typeof setTimeout> | 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;
|
||||
});
|
||||
Reference in New Issue
Block a user