-
-
Notifications
You must be signed in to change notification settings - Fork 632
Fix infinite fork loop when node renderer worker fails to bind port #2881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a48cb3d
a861a6e
4f82dd7
a090fee
5440452
c4ce2ea
1be836d
49c0ff0
9c6aaf0
05851ed
7bd1ee9
fd704f0
11cbe99
e68d937
08b9f0b
07c1f14
a867ec9
9d48d81
97784b9
ca6ac01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { buildConfig, Config, logSanitizedConfig } from './shared/configBuilder. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import restartWorkers from './master/restartWorkers.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as errorReporter from './shared/errorReporter.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getLicenseStatus } from './shared/licenseValidator.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { isWorkerStartupFailureMessage, type WorkerStartupFailureMessage } from './shared/workerMessages.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MILLISECONDS_IN_MINUTE = 60000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // How often to scan for orphaned upload directories. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -77,22 +78,81 @@ export default function masterRun(runningConfig?: Partial<Config>) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, ORPHAN_CLEANUP_INTERVAL_MS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let isAbortingForStartupFailure = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let fatalStartupFailure: { workerId: number; failure: WorkerStartupFailureMessage } | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let hasInitiatedShutdown = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const abortForStartupFailure = (): boolean => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!(isAbortingForStartupFailure && fatalStartupFailure)) return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!hasInitiatedShutdown) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hasInitiatedShutdown = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: the exiting worker may differ from the one that sent the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // failure message if multiple workers exit in rapid succession. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // We always report the first failure received. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { failure, workerId: failedWorkerId } = fatalStartupFailure; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const msg = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failure.code === 'EADDRINUSE' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? `Node renderer startup failed: ${failure.host}:${failure.port} is already in use` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : `Node renderer startup failed in worker ${failedWorkerId}: ${failure.message}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorReporter.message(msg); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Disconnect all live workers so they release their ports before the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // master exits. cluster.disconnect() is async — the callback fires | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // once every worker has disconnected. A hard-deadline timer guarantees | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the master still exits if a worker is stuck (leaked handle, blocking | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // syscall, etc.), following the same pattern as restartWorkers.ts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const MASTER_SHUTDOWN_TIMEOUT_MS = 5000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const shutdownTimer = setTimeout(() => process.exit(1), MASTER_SHUTDOWN_TIMEOUT_MS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
justin808 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof shutdownTimer.unref === 'function') shutdownTimer.unref(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.disconnect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clearTimeout(shutdownTimer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.on('message', (worker, message) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check the abort flag first to short-circuit the type-guard on every | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ordinary IPC message once we are already aborting. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isAbortingForStartupFailure || !isWorkerStartupFailureMessage(message)) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isAbortingForStartupFailure = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fatalStartupFailure = { workerId: worker.id, failure: message }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < workersCount; i += 1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
justin808 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.fork(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Listen for dying workers: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.on('exit', (worker) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
Comment on lines
126
to
131
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor ordering note: the |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Once a startup failure has been detected, abort regardless of whether | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // this particular exit was from the failing worker, a scheduled restart, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // or an unrelated crash. Don't fork any more workers. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (abortForStartupFailure()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
cursor[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (worker.isScheduledRestart) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info('Restarting worker #%d on schedule', worker.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.fork(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a narrow timing gap here: the In that window, this branch forks a new replacement worker even though the master is about to abort. The new worker will fail with the same EADDRINUSE, send another If this edge case matters, wrapping the scheduled-restart fork in a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
139
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The The new worker will likely fail with the same error and exit, triggering the abort on its exit — so this isn't a correctness failure (the master will still eventually abort), but it does transiently over-fork by one worker during the startup-failure shutdown sequence. To close the window consistently, apply the same
Suggested change
The existing test
Comment on lines
139
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The In practice this race is almost impossible — scheduled restarts fire minutes after startup — but it's worth a comment so future readers don't wonder why the parity is missing:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Give in-flight startup-failure IPC messages one event-loop turn to be | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // processed before classifying this as an ordinary runtime crash. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setImmediate(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
justin808 marked this conversation as resolved.
justin808 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (abortForStartupFailure()) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: Track last rendering request per worker.id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: Consider blocking a given rendering request if it kills a worker more than X times | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const msg = `Worker ${worker.id} died UNEXPECTEDLY :(, restarting`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorReporter.message(msg); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Replace the dead worker: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.fork(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster.fork(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Schedule regular restarts of workers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -142,6 +142,13 @@ function logLevel(level: string): LevelWithSilent { | |||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| function validatePort(port: number): string | null { | ||||||||||||||
| if (!Number.isInteger(port) || !Number.isFinite(port) || port < 0 || port > 65535) { | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead condition in port validation functionLow Severity The Reviewed by Cursor Bugbot for commit 97784b9. Configure here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| return `RENDERER_PORT must be an integer between 0 and 65535. Received: ${String(port)}`; | ||||||||||||||
| } | ||||||||||||||
| return null; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| function normalizedRuntimeEnvs() { | ||||||||||||||
| return [env.RAILS_ENV, env.NODE_ENV] | ||||||||||||||
| .filter((value): value is string => Boolean(value)) | ||||||||||||||
|
|
@@ -380,6 +387,17 @@ export function buildConfig(providedUserConfig?: Partial<Config>): Config { | |||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| // Coerce port to a number — user configs frequently pass env-derived strings | ||||||||||||||
| // (e.g. `port: env.RENDERER_PORT || 3800` yields the string "3800"). | ||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- runtime value may be string despite the type | ||||||||||||||
| config.port = Number(config.port); | ||||||||||||||
|
|
||||||||||||||
| const portValidationError = validatePort(config.port); | ||||||||||||||
| if (portValidationError) { | ||||||||||||||
| log.error(portValidationError); | ||||||||||||||
| process.exit(1); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if ( | ||||||||||||||
| 'honeybadgerApiKey' in config || | ||||||||||||||
| 'sentryDsn' in config || | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| export const WORKER_STARTUP_FAILURE = 'NODE_RENDERER_WORKER_STARTUP_FAILURE' as const; | ||
|
|
||
| export interface WorkerStartupFailureMessage { | ||
| type: typeof WORKER_STARTUP_FAILURE; | ||
| stage: 'listen'; | ||
| code?: string; | ||
| errno?: number; | ||
| syscall?: string; | ||
| host: string; | ||
| port: number; | ||
| message: string; | ||
| } | ||
|
|
||
| export function isWorkerStartupFailureMessage(value: unknown): value is WorkerStartupFailureMessage { | ||
| if (typeof value !== 'object' || value === null) { | ||
| return false; | ||
| } | ||
|
|
||
| const message = value as Partial<WorkerStartupFailureMessage>; | ||
|
|
||
| // stage: 'listen' is the only supported stage today. To handle pre-listen | ||
| // failures (e.g. plugin registration), add a new stage value here and | ||
| // update the master handler accordingly. | ||
| return ( | ||
| message.type === WORKER_STARTUP_FAILURE && | ||
| message.stage === 'listen' && | ||
|
justin808 marked this conversation as resolved.
|
||
| typeof message.host === 'string' && | ||
|
justin808 marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This guard drops startup-failure IPC messages unless Useful? React with 👍 / 👎. |
||
| typeof message.port === 'number' && | ||
| Number.isInteger(message.port) && | ||
| message.port >= 0 && | ||
| message.port <= 65535 && | ||
|
justin808 marked this conversation as resolved.
|
||
| typeof message.message === 'string' | ||
| ); | ||
|
justin808 marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import cluster from 'cluster'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import log from '../shared/log.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { WORKER_STARTUP_FAILURE, type WorkerStartupFailureMessage } from '../shared/workerMessages.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export type StartupListenErrorHandlerOptions = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err: Error; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| host: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isWorker?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| send?: NodeJS.Process['send']; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit?: NodeJS.Process['exit']; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function handleStartupListenError({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| err, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| host, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isWorker = cluster.isWorker, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| send, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: StartupListenErrorHandlerOptions) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sendFn = send ?? process.send?.bind(process); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const exitFn = exit ?? ((code?: number) => process.exit(code)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error({ err, host, port }, 'Node renderer failed to start'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isWorker) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!sendFn) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error('Cluster worker has no IPC channel; cannot notify master of startup failure'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exitFn(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const startupFailure: WorkerStartupFailureMessage = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: WORKER_STARTUP_FAILURE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stage: 'listen', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: (err as NodeJS.ErrnoException).code, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errno: (err as NodeJS.ErrnoException).errno, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| syscall: (err as NodeJS.ErrnoException).syscall, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| host, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| port, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: err.message, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let exited = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const doExit = (sendErr?: Error | null) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (exited) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exited = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (sendErr) log.error({ err: sendErr }, 'Failed to send startup failure message to master'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exitFn(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendFn(startupFailure, undefined, undefined, doExit); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Safety net: if the IPC channel is half-broken the callback may never | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // fire, leaving this worker alive indefinitely. Force exit after a timeout. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const IPC_SEND_TIMEOUT_MS = 2000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const timer = setTimeout(() => doExit(), IPC_SEND_TIMEOUT_MS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof timer.unref === 'function') timer.unref(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The timeout
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (sendErr) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error({ err: sendErr as Error }, 'Failed to send startup failure message to master'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exitFn(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exitFn(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||


Uh oh!
There was an error while loading. Please reload this page.