(options: { workspaceRoots?: string[] } = {})
| 28 | import { scheduleCursorModelsPrewarm } from '@/modules/common/cursorModelsPrewarm'; |
| 29 | |
| 30 | export async function startRunner(options: { workspaceRoots?: string[] } = {}): Promise<void> { |
| 31 | // We don't have cleanup function at the time of server construction |
| 32 | // Control flow is: |
| 33 | // 1. Create promise that will resolve when shutdown is requested |
| 34 | // 2. Setup signal handlers to resolve this promise with the source of the shutdown |
| 35 | // 3. Once our setup is complete - if all goes well - we await this promise |
| 36 | // 4. When it resolves we can cleanup and exit |
| 37 | // |
| 38 | // In case the setup malfunctions - our signal handlers will not properly |
| 39 | // shut down. We will force exit the process with code 1. |
| 40 | let requestShutdown: (source: 'hapi-app' | 'hapi-cli' | 'os-signal' | 'exception', errorMessage?: string) => void; |
| 41 | let resolvesWhenShutdownRequested = new Promise<({ source: 'hapi-app' | 'hapi-cli' | 'os-signal' | 'exception', errorMessage?: string })>((resolve) => { |
| 42 | requestShutdown = (source, errorMessage) => { |
| 43 | logger.debug(`[RUNNER RUN] Requesting shutdown (source: ${source}, errorMessage: ${errorMessage})`); |
| 44 | |
| 45 | // Fallback - in case startup malfunctions - we will force exit the process with code 1 |
| 46 | setTimeout(async () => { |
| 47 | logger.debug('[RUNNER RUN] Startup malfunctioned, forcing exit with code 1'); |
| 48 | |
| 49 | // Give time for logs to be flushed |
| 50 | await new Promise(resolve => setTimeout(resolve, 100)) |
| 51 | |
| 52 | process.exit(1); |
| 53 | }, 1_000); |
| 54 | |
| 55 | // Start graceful shutdown |
| 56 | resolve({ source, errorMessage }); |
| 57 | }; |
| 58 | }); |
| 59 | |
| 60 | // Setup signal handlers |
| 61 | process.on('SIGINT', () => { |
| 62 | logger.debug('[RUNNER RUN] Received SIGINT'); |
| 63 | requestShutdown('os-signal'); |
| 64 | }); |
| 65 | |
| 66 | process.on('SIGTERM', () => { |
| 67 | logger.debug('[RUNNER RUN] Received SIGTERM'); |
| 68 | requestShutdown('os-signal'); |
| 69 | }); |
| 70 | |
| 71 | if (isWindows()) { |
| 72 | process.on('SIGBREAK', () => { |
| 73 | logger.debug('[RUNNER RUN] Received SIGBREAK'); |
| 74 | requestShutdown('os-signal'); |
| 75 | }); |
| 76 | } |
| 77 | |
| 78 | process.on('uncaughtException', (error) => { |
| 79 | logger.debug('[RUNNER RUN] FATAL: Uncaught exception', error); |
| 80 | logger.debug(`[RUNNER RUN] Stack trace: ${error.stack}`); |
| 81 | requestShutdown('exception', error.message); |
| 82 | }); |
| 83 | |
| 84 | process.on('unhandledRejection', (reason, promise) => { |
| 85 | logger.debug('[RUNNER RUN] FATAL: Unhandled promise rejection', reason); |
| 86 | logger.debug(`[RUNNER RUN] Rejected promise:`, promise); |
| 87 | const error = reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${reason}`); |
no test coverage detected