import { config, validateConfig } from './config'; import { logger, setWorkerId } from './utils/logger'; import { createApp } from './server/app'; import { startWorkerManager, stopWorkerManager } from './worker/manager'; import { closeBrowser } from './worker/browser-pool'; import { runQuotaCheckCron } from './cron/quota-check'; import { runCleanupReportsCron } from './cron/cleanup-reports'; import { runStaleRecoveryCron } from './cron/stale-recovery'; import * as cron from 'node-cron'; async function main(): Promise { // Validate environment try { validateConfig(); } catch (err) { console.error('Configuration error:', err instanceof Error ? err.message : err); process.exit(1); } setWorkerId(config.workerId); logger.info('Turnitin Worker starting', { workerId: config.workerId, maxWorkers: config.maxWorkers, port: config.port, enableWorker: config.enableWorker, enableCron: config.enableCron, }); // Start Express server const app = createApp(); const server = app.listen(config.port, () => { logger.info(`HTTP server listening on port ${config.port}`); }); if (config.enableWorker) { // Start worker manager (polls for jobs) startWorkerManager().catch((err) => { logger.error('Worker manager fatal error', { error: err instanceof Error ? err.message : String(err), }); }); } else { logger.info('Worker manager disabled by ENABLE_WORKER=false'); } if (config.enableCron) { // Schedule cron jobs // Quota check: every N minutes const quotaCronExpr = `*/${config.quotaCheckInterval} * * * *`; cron.schedule(quotaCronExpr, () => { runQuotaCheckCron().catch((err) => { logger.error('Quota check cron error', { error: err instanceof Error ? err.message : String(err), }); }); }); logger.info(`Quota check cron scheduled: ${quotaCronExpr}`); // Report cleanup: every N minutes const cleanupCronExpr = `*/${config.cleanupInterval} * * * *`; cron.schedule(cleanupCronExpr, () => { runCleanupReportsCron().catch((err) => { logger.error('Report cleanup cron error', { error: err instanceof Error ? err.message : String(err), }); }); }); logger.info(`Report cleanup cron scheduled: ${cleanupCronExpr}`); // Stale recovery: every N minutes const staleCronExpr = `*/${config.staleRecoveryInterval} * * * *`; cron.schedule(staleCronExpr, () => { runStaleRecoveryCron().catch((err) => { logger.error('Stale recovery cron error', { error: err instanceof Error ? err.message : String(err), }); }); }); logger.info(`Stale recovery cron scheduled: ${staleCronExpr}`); } else { logger.info('Cron jobs disabled by ENABLE_CRON=false'); } logger.info('All systems started successfully'); // Graceful shutdown const shutdown = async (signal: string) => { logger.info(`Received ${signal}, shutting down gracefully...`); // Stop accepting new jobs stopWorkerManager(); // Close HTTP server server.close(() => { logger.info('HTTP server closed'); }); // Wait for active workers to finish (max 60 seconds) const maxWait = 60000; const start = Date.now(); const { getActiveWorkerCount } = await import('./worker/manager'); while (getActiveWorkerCount() > 0 && Date.now() - start < maxWait) { logger.info(`Waiting for ${getActiveWorkerCount()} active workers to finish...`); await new Promise((r) => setTimeout(r, 2000)); } // Close browser await closeBrowser(); logger.info('Shutdown complete'); process.exit(0); }; process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // Handle uncaught errors process.on('uncaughtException', (err) => { logger.error('Uncaught exception', { error: err.message, stack: err.stack, }); // Don't exit on uncaught exceptions in worker service // The worker loop has its own error handling }); process.on('unhandledRejection', (reason) => { logger.error('Unhandled promise rejection', { reason: reason instanceof Error ? reason.message : String(reason), }); }); } main().catch((err) => { console.error('Fatal startup error:', err); process.exit(1); });