fix(runtime): close lifecycle teardown races
This commit is contained in:
@@ -111,6 +111,7 @@ describe('LocalSubprocessService', () => {
|
||||
foregroundPgid: () => undefined,
|
||||
isStdinWaiting: () => false,
|
||||
processTree: () => [],
|
||||
processSession: () => [],
|
||||
isAlive: () => false,
|
||||
signalGroup: () => {},
|
||||
signalProcess: () => {},
|
||||
@@ -175,6 +176,7 @@ describe('LocalSubprocessService', () => {
|
||||
foregroundPgid: () => 123,
|
||||
isStdinWaiting: () => false,
|
||||
processTree: () => [{ pid: 124, started: 'child' }],
|
||||
processSession: () => [],
|
||||
isAlive: identity => alive.has(identity.pid),
|
||||
signalGroup: () => {},
|
||||
signalProcess: () => {},
|
||||
|
||||
@@ -86,6 +86,13 @@ describe('Linux process inspector', () => {
|
||||
{ pid: 10, started: '500' },
|
||||
])
|
||||
expect(inspector.processTree(99)).toEqual([])
|
||||
expect(inspector.processSession(30)).toEqual([
|
||||
{ pid: 10, started: '500' },
|
||||
{ pid: 11, started: '501' },
|
||||
{ pid: 12, started: '502' },
|
||||
{ pid: 13, started: '503' },
|
||||
])
|
||||
expect(inspector.processSession(99)).toEqual([])
|
||||
expect(inspector.isAlive({ pid: 10, started: '500' })).toBe(true)
|
||||
expect(inspector.isAlive({ pid: 10, started: 'old' })).toBe(false)
|
||||
inspector.signalGroup(40, 'SIGINT')
|
||||
@@ -197,6 +204,7 @@ describe('macOS process inspector', () => {
|
||||
{ pid: 10, started: 'Mon Jul 21 10:00:00 2026' },
|
||||
])
|
||||
expect(inspector.processTree(99)).toEqual([])
|
||||
expect(inspector.processSession(10)).toEqual([])
|
||||
expect(inspector.isAlive({ pid: 11, started: 'Mon Jul 21 10:00:01 2026' })).toBe(true)
|
||||
inspector.signalGroup(55, 'SIGTSTP')
|
||||
inspector.signalProcess({ pid: 11, started: 'Mon Jul 21 10:00:01 2026' }, 'SIGKILL')
|
||||
|
||||
@@ -51,6 +51,7 @@ class FakeInspector implements ProcessInspector {
|
||||
pgid: number | undefined = 456
|
||||
waiting = false
|
||||
members: ProcessIdentity[] = []
|
||||
sessionMembers: ProcessIdentity[] = []
|
||||
readonly alive = new Set<number>()
|
||||
readonly groups: Array<[number, SubprocessTerminalSignal]> = []
|
||||
readonly processes: Array<[number, 'SIGTERM' | 'SIGKILL']> = []
|
||||
@@ -61,6 +62,7 @@ class FakeInspector implements ProcessInspector {
|
||||
foregroundPgid() { return this.pgid }
|
||||
isStdinWaiting() { return this.waiting }
|
||||
processTree() { return this.members }
|
||||
processSession() { return this.sessionMembers }
|
||||
isAlive(identity: ProcessIdentity) { return this.alive.has(identity.pid) }
|
||||
signalGroup(pgid: number, signal: SubprocessTerminalSignal) {
|
||||
if (this.throwGroup) throw new Error('group failed')
|
||||
@@ -158,6 +160,36 @@ describe('LocalTerminalHandle', () => {
|
||||
expect(await waiting).toBe(true)
|
||||
})
|
||||
|
||||
it('cleans a same-session descendant after the top-level shell exits naturally', async () => {
|
||||
const pty = new FakePty()
|
||||
const inspector = new FakeInspector()
|
||||
const disowned = { pid: 124, started: 'disowned' }
|
||||
inspector.processSession = () => inspector.alive.has(disowned.pid) ? [disowned] : []
|
||||
inspector.alive.add(124)
|
||||
const handle = new LocalTerminalHandle(pty.asPty(), inspector, 20)
|
||||
|
||||
pty.emitExit()
|
||||
|
||||
expect(await handle.waitForExit()).toBe(true)
|
||||
expect(inspector.processes).toEqual([[124, 'SIGTERM']])
|
||||
})
|
||||
|
||||
it('retains an inspected descendant after it reparents away from the shell', async () => {
|
||||
const pty = new FakePty()
|
||||
const inspector = new FakeInspector()
|
||||
const descendant = { pid: 124, started: 'observed' }
|
||||
inspector.members = [descendant]
|
||||
inspector.alive.add(descendant.pid)
|
||||
const handle = new LocalTerminalHandle(pty.asPty(), inspector, 20)
|
||||
|
||||
await handle.inspectForeground()
|
||||
inspector.members = []
|
||||
pty.emitExit()
|
||||
|
||||
expect(await handle.waitForExit()).toBe(true)
|
||||
expect(inspector.processes).toEqual([[124, 'SIGTERM']])
|
||||
})
|
||||
|
||||
it('rescans for descendants forked during TERM', async () => {
|
||||
const pty = new FakePty()
|
||||
const inspector = new FakeInspector()
|
||||
|
||||
Reference in New Issue
Block a user