fix(desktop): skip cold process probes for dead backend-ownership PIDs

Windows Get-Process and macOS ps exit 1 on a missing PID, so reapOrphans
kept stale records and the next launch paid another 2-8s spawn each.
Throw ESRCH from the existing isPidAlive helper before any shell-out.

Closes #92875

Co-authored-by: Jackal991 <139240222+Jackal991@users.noreply.github.com>
Co-authored-by: jonotonfoto <126111813+jonotonfoto@users.noreply.github.com>
Co-authored-by: foras910521-lab <268267187+foras910521-lab@users.noreply.github.com>
This commit is contained in:
Brooklyn Nicholson
2026-08-31 19:26:36 -05:00
committed by brooklyn!
parent e22f8a7fbd
commit a2907a8bcd
2 changed files with 18 additions and 2 deletions
+8 -2
View File
@@ -66,9 +66,15 @@ test('processStartMarker resolves a real marker for the current process', async
assert.match(marker, /^(linux|win|winms|ps):.+/)
})
test('processStartMarker rejects for a PID that does not exist', async () => {
test('a missing PID is classified as ESRCH so reapOrphans can drop the record', async () => {
// Largest PIDs are bounded well below this on every supported platform.
await assert.rejects(processStartMarker(2 ** 30 + 12345))
// Windows Get-Process / macOS `ps -p` used to surface exit code 1, which
// the identity matchers treated as "unknown" and kept forever. The native
// gate throws ESRCH — the errno those catch blocks already map to gone.
await assert.rejects(
processStartMarker(2 ** 30 + 12345),
(error: NodeJS.ErrnoException) => error?.code === 'ESRCH'
)
})
// --- PID-only marker helpers --------------------------------------------------
+10
View File
@@ -21,6 +21,7 @@ import { execFile } from 'node:child_process'
import fs from 'node:fs'
import { electronProcessStartMarker } from './parent-process-identity'
import { isPidAlive } from './update-marker'
import { hiddenWindowsChildOptions } from './windows-child-options'
export function execText(command: string, args: string[], { timeout = 3000 } = {}): Promise<string> {
@@ -42,6 +43,15 @@ export function execText(command: string, args: string[], { timeout = 3000 } = {
* `claimDecision` / `probeStartMarker`).
*/
export async function processStartMarker(pid: number): Promise<string> {
// Cheap native dead-PID gate. Windows Get-Process / macOS `ps -p` exit 1
// on a missing PID (not ESRCH), so the identity matchers used to keep the
// orphan and re-probe it every launch (#92875). ESRCH is the code those
// catch blocks already map to "gone". Alive or uninspectable (EPERM) PIDs
// still fall through to the platform probe.
if (!isPidAlive(pid)) {
throw Object.assign(new Error(`PID ${pid} no longer exists`), { code: 'ESRCH' })
}
if (process.platform === 'linux') {
const stat = await fs.promises.readFile(`/proc/${pid}/stat`, 'utf8')