()
| 1390 | |
| 1391 | // Emergency cleanup for crashes (OOM, uncaught exceptions, browser disconnect) |
| 1392 | function emergencyCleanup() { |
| 1393 | if (isShuttingDown) return; |
| 1394 | isShuttingDown = true; |
| 1395 | // Xvfb cleanup MUST happen before state-file deletion. spawnXvfb detaches |
| 1396 | // the child, so without this, an uncaught exception leaves the Xvfb |
| 1397 | // running with no PID record — orphan accumulates and eventually |
| 1398 | // exhausts the :99-:120 display range. Read the state file FIRST, |
| 1399 | // call cleanupXvfb (validates cmdline + start-time before kill), THEN |
| 1400 | // delete the state file. |
| 1401 | try { |
| 1402 | if (fs.existsSync(config.stateFile)) { |
| 1403 | const raw = fs.readFileSync(config.stateFile, 'utf-8'); |
| 1404 | const state = JSON.parse(raw); |
| 1405 | if (state.xvfbPid && state.xvfbStartTime) { |
| 1406 | // Lazy import — emergencyCleanup may run on platforms where |
| 1407 | // ./xvfb's Linux-specific helpers fail to load. Best effort. |
| 1408 | try { |
| 1409 | const { cleanupXvfb } = require('./xvfb'); |
| 1410 | cleanupXvfb({ |
| 1411 | pid: state.xvfbPid, |
| 1412 | startTime: state.xvfbStartTime, |
| 1413 | display: state.xvfbDisplay || ':99', |
| 1414 | }); |
| 1415 | } catch { /* best effort */ } |
| 1416 | } |
| 1417 | } |
| 1418 | } catch { /* state file unparseable — fall through to lock + state cleanup */ } |
| 1419 | |
| 1420 | // Clean Chromium profile locks via the shared helper (defensive guard |
| 1421 | // refuses to operate on unrecognized profile dirs). |
| 1422 | cleanSingletonLocks(resolveChromiumProfile()); |
| 1423 | safeUnlinkQuiet(config.stateFile); |
| 1424 | } |
| 1425 | // Same import.meta.main gate as SIGINT/SIGTERM — embedders register their |
| 1426 | // own crash handlers. |
| 1427 | if (import.meta.main) { |
no test coverage detected