Spaces:
Sleeping
Sleeping
File size: 4,370 Bytes
d5c9d40 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 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);
});
|