mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* feat(docker): enable Redis RDB persistence, add seed-orchestrator to supervisord, copy scripts into image
* feat(orchestrator): add prefixed logger utility
* feat(orchestrator): add seed-meta read/write helpers
* feat(orchestrator): add child process runner with timeout support
* feat(orchestrator): add central seed catalog with 42 seeders
* feat(orchestrator): implement seed orchestrator with tiered scheduling
Adds scripts/seed-orchestrator.mjs with:
- classifySeeders: classify seeders into active/skipped by env vars
- buildStartupSummary: human-readable startup report
- Tiered cold start (hot/warm/cold/frozen with per-tier concurrency)
- Freshness check via seed-meta keys before running stale seeders
- Steady-state scheduling with setTimeout-based recurring timers
- Overlap protection, retry-after-60s, consecutive failure demotion
- Global concurrency cap of 5 with queue-based overflow
- Graceful shutdown on SIGTERM/SIGINT (15s drain timeout)
- Meta writing for null-metaKey seeders to seed-meta:orchestrator:{name}
* fix(seeds): use local API for warm-ping seeders in Docker mode
* fix(orchestrator): allow ACLED email+password as alternative to access token
* feat(wmsm): add seed manager CLI scaffold with help, catalog, and checks
* feat(wmsm): implement status command with freshness display
* feat(wmsm): implement schedule command with next-run estimates
* feat(wmsm): implement refresh command with single and --all modes
* feat(wmsm): implement flush and logs commands
* fix(wmsm): auto-detect docker vs podman runtime
* feat(orchestrator): extract pure scheduling functions and add test harness
* feat(orchestrator): add SEED_TURBO=real|dry mode with compressed intervals
* feat(orchestrator): add SEED_TURBO env passthrough and fix retry log message
65 lines
1.9 KiB
JavaScript
65 lines
1.9 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
|
|
/**
|
|
* Fork a seed script as a child process and track its execution.
|
|
*
|
|
* @param {string} name — seeder name for logging
|
|
* @param {object} opts
|
|
* @param {string} opts.scriptPath — path to the script (or process.execPath for tests)
|
|
* @param {string[]} [opts.args] — arguments (default: none)
|
|
* @param {number} [opts.timeoutMs=120000] — kill after this many ms
|
|
* @param {object} [opts.env] — additional env vars (merged with process.env)
|
|
* @returns {Promise<{ name: string, exitCode: number|null, status: 'ok'|'error'|'timeout', durationMs: number }>}
|
|
*/
|
|
export function forkSeeder(name, opts) {
|
|
const { scriptPath, args = [], timeoutMs = 120_000, env } = opts;
|
|
|
|
return new Promise((resolve) => {
|
|
const start = Date.now();
|
|
const child = spawn(scriptPath, args, {
|
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
env: { ...process.env, ...env },
|
|
});
|
|
|
|
let settled = false;
|
|
const timer = setTimeout(() => {
|
|
if (!settled) {
|
|
settled = true;
|
|
child.kill('SIGTERM');
|
|
// Give 3s for graceful shutdown, then SIGKILL
|
|
setTimeout(() => {
|
|
try { child.kill('SIGKILL'); } catch {}
|
|
}, 3000);
|
|
resolve({ name, exitCode: null, status: 'timeout', durationMs: Date.now() - start });
|
|
}
|
|
}, timeoutMs);
|
|
|
|
child.on('close', (code) => {
|
|
if (!settled) {
|
|
settled = true;
|
|
clearTimeout(timer);
|
|
resolve({
|
|
name,
|
|
exitCode: code,
|
|
status: code === 0 ? 'ok' : 'error',
|
|
durationMs: Date.now() - start,
|
|
});
|
|
}
|
|
});
|
|
|
|
child.on('error', (err) => {
|
|
if (!settled) {
|
|
settled = true;
|
|
clearTimeout(timer);
|
|
resolve({
|
|
name,
|
|
exitCode: null,
|
|
status: 'error',
|
|
durationMs: Date.now() - start,
|
|
error: err.message,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|