Spaces:
Sleeping
Sleeping
| 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<void> { | |
| // 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); | |
| }); | |