fix(subprocess-local): fence descendant adoption on the shell's start identity
A recycled shell pid could donate an unrelated process's children to the terminal session's cleanup signalling: post-exit rescans queried processTree/processSession by numeric pid alone. The handle now captures the spawned shell's start identity at construction and adopts newly scanned members only while the root pid still carries it; already-adopted members keep their own identities, which every signal already rechecks. Regressions cover a recycled root donating an imposter child and a shell whose identity was never observable; the terminal fakes now model the root row the real /proc and ps scans include. Also from the review round: tool-pty's dependency list is re-sorted, and the LSP renderer documents the deliberate drive-letter reading of ambiguous file: URIs (display-only blast radius).
This commit is contained in:
@@ -43,6 +43,8 @@ export class LocalTerminalHandle implements SubprocessTerminalHandle {
|
||||
private cleanup: Promise<void> | undefined
|
||||
private exited = false
|
||||
private trackedDescendants: ProcessIdentity[] = []
|
||||
/** The spawned shell's start identity; scans stop adopting members once the root pid no longer carries it. */
|
||||
private readonly rootIdentity: ProcessIdentity | undefined
|
||||
|
||||
/**
|
||||
* @param terminal - allocated node-pty process.
|
||||
@@ -55,6 +57,7 @@ export class LocalTerminalHandle implements SubprocessTerminalHandle {
|
||||
private readonly graceMs: number,
|
||||
) {
|
||||
this.pid = terminal.pid
|
||||
this.rootIdentity = inspector.processTree(this.pid).find(member => member.pid === this.pid)
|
||||
this.done = this.outcome.promise
|
||||
this.dataDisposable = terminal.onData((data) => { this.output.write(Buffer.from(data, 'utf8')) })
|
||||
this.exitDisposable = terminal.onExit(({ exitCode, signal: exitSignal }) => {
|
||||
@@ -112,10 +115,19 @@ export class LocalTerminalHandle implements SubprocessTerminalHandle {
|
||||
}
|
||||
|
||||
private descendants(): ProcessIdentity[] {
|
||||
// Adopt newly scanned members only while the numeric root pid provably
|
||||
// still carries the spawned shell's start identity: after the shell dies,
|
||||
// a recycled pid's tree and session must not donate an unrelated
|
||||
// process's children to this session's signalling. Already-adopted
|
||||
// members keep their own start identities, which every signal rechecks.
|
||||
const tree = this.inspector.processTree(this.pid)
|
||||
const root = tree.find(member => member.pid === this.pid)
|
||||
const rootVerified = this.rootIdentity !== undefined
|
||||
&& root !== undefined
|
||||
&& root.started === this.rootIdentity.started
|
||||
this.trackedDescendants = this.survivors(this.unionMembers(
|
||||
this.trackedDescendants,
|
||||
this.inspector.processTree(this.pid),
|
||||
this.inspector.processSession(this.pid),
|
||||
...rootVerified ? [tree, this.inspector.processSession(this.pid)] : [],
|
||||
).filter(member => member.pid !== this.pid))
|
||||
return this.trackedDescendants
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user