* Acquire an exclusive lockfile to prevent concurrent ensureServer() races (TOCTOU). * Returns a cleanup function that releases the lock.
()
| 377 | * Returns a cleanup function that releases the lock. |
| 378 | */ |
| 379 | function acquireServerLock(): (() => void) | null { |
| 380 | const lockPath = `${config.stateFile}.lock`; |
| 381 | try { |
| 382 | // 'wx' — create exclusively, fails if file already exists (atomic check-and-create) |
| 383 | // Using string flag instead of numeric constants for Bun Windows compatibility |
| 384 | const fd = fs.openSync(lockPath, 'wx'); |
| 385 | fs.writeSync(fd, `${process.pid}\n`); |
| 386 | fs.closeSync(fd); |
| 387 | return () => { safeUnlink(lockPath); }; |
| 388 | } catch { |
| 389 | // Lock already held — check if the holder is still alive |
| 390 | try { |
| 391 | const holderPid = parseInt(fs.readFileSync(lockPath, 'utf8').trim(), 10); |
| 392 | if (holderPid && isProcessAlive(holderPid)) { |
| 393 | return null; // Another live process holds the lock |
| 394 | } |
| 395 | // Stale lock — remove and retry |
| 396 | fs.unlinkSync(lockPath); |
| 397 | return acquireServerLock(); |
| 398 | } catch { |
| 399 | return null; |
| 400 | } |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | async function ensureServer(flags?: GlobalFlags): Promise<ServerState> { |
| 405 | const state = readState(); |
no test coverage detected