mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 00:03:15 +00:00
fix(wsl): tolerate exited processes during inventory
This commit is contained in:
@@ -24,8 +24,10 @@ export function parseWslGuestProcessInventoryPayload(
|
||||
let bootId: string | null = null
|
||||
let expectedCount: number | null = null
|
||||
let seenCount: number | null = null
|
||||
let skippedCount: number | null = null
|
||||
const rows: WslGuestProcessRow[] = []
|
||||
const pids = new Set<number>()
|
||||
const skippedPids = new Set<number>()
|
||||
for (const rawLine of payload.split(/\r?\n/)) {
|
||||
// Remove only the transport CR; trailing spaces belong to the command
|
||||
// remainder and must not be normalized away.
|
||||
@@ -41,13 +43,23 @@ export function parseWslGuestProcessInventoryPayload(
|
||||
bootId = boot[1]!
|
||||
continue
|
||||
}
|
||||
const count = line.match(/^count (\d+) (\d+)$/)
|
||||
const count = line.match(/^count (\d+) (\d+)(?: (\d+))?$/)
|
||||
if (count) {
|
||||
if (seenCount !== null) {
|
||||
throw new Error('duplicate_count')
|
||||
}
|
||||
seenCount = Number(count[1])
|
||||
expectedCount = Number(count[2])
|
||||
skippedCount = count[3] === undefined ? 0 : Number(count[3])
|
||||
continue
|
||||
}
|
||||
const skipped = line.match(/^skip (\d+)$/)
|
||||
if (skipped) {
|
||||
const pid = Number(skipped[1])
|
||||
if (!Number.isSafeInteger(pid) || pid <= 0 || pids.has(pid) || skippedPids.has(pid)) {
|
||||
throw new Error('invalid_row')
|
||||
}
|
||||
skippedPids.add(pid)
|
||||
continue
|
||||
}
|
||||
const row = line.match(/^row (\d+) (\d+) (\d+) (-?\d+) (-?\d+) (\S+) (\S+) (\d+)(?: (.*))?$/)
|
||||
@@ -92,8 +104,10 @@ export function parseWslGuestProcessInventoryPayload(
|
||||
if (
|
||||
seenCount === null ||
|
||||
expectedCount === null ||
|
||||
seenCount !== expectedCount ||
|
||||
seenCount !== rows.length
|
||||
skippedCount === null ||
|
||||
seenCount !== rows.length ||
|
||||
skippedCount !== skippedPids.size ||
|
||||
seenCount + skippedCount !== expectedCount
|
||||
) {
|
||||
throw new Error('row_count_mismatch')
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { runProcessMock } = vi.hoisted(() => ({ runProcessMock: vi.fn() }))
|
||||
@@ -37,6 +40,54 @@ describe('WSL guest process inventory', () => {
|
||||
expect(WSL_GUEST_INVENTORY_SCRIPT).not.toContain('cat "/proc/$_orca_pid/stat"')
|
||||
})
|
||||
|
||||
it('skips a process whose proc stat disappears and keeps the remaining agent row', () => {
|
||||
const fixtureRoot = mkdtempSync(join(tmpdir(), 'wsl-guest-inventory-'))
|
||||
try {
|
||||
mkdirSync(join(fixtureRoot, 'sys/kernel/random'), { recursive: true })
|
||||
writeFileSync(join(fixtureRoot, 'sys/kernel/random/boot_id'), `${bootId}\n`)
|
||||
mkdirSync(join(fixtureRoot, '100'))
|
||||
mkdirSync(join(fixtureRoot, '101'))
|
||||
// Linux /proc/<pid>/stat-shaped output: field 22 is the start-time tick.
|
||||
writeFileSync(
|
||||
join(fixtureRoot, '100/stat'),
|
||||
`100 (bash) S ${Array.from({ length: 18 }, () => '0').join(' ')} 12345 0\n`
|
||||
)
|
||||
writeFileSync(
|
||||
join(fixtureRoot, '101/stat'),
|
||||
`101 (codex) S ${Array.from({ length: 18 }, () => '0').join(' ')} 54321 0\n`
|
||||
)
|
||||
const binRoot = join(fixtureRoot, 'bin')
|
||||
mkdirSync(binRoot)
|
||||
const fakePs = join(binRoot, 'ps')
|
||||
writeFileSync(
|
||||
fakePs,
|
||||
'#!/bin/sh\nprintf "%s\\n" "999999 0 100 100 100 pts/0 S short-lived" "100 0 100 100 101 pts/0 Ss+ bash" "101 100 100 101 101 pts/0 Sl+ codex"\n'
|
||||
)
|
||||
chmodSync(fakePs, 0o755)
|
||||
const script = WSL_GUEST_INVENTORY_SCRIPT.replaceAll(
|
||||
'/proc',
|
||||
fixtureRoot.replaceAll('\\', '/')
|
||||
)
|
||||
const output = execFileSync('sh', ['-c', script], {
|
||||
encoding: 'utf8',
|
||||
env: { ...process.env, PATH: `${binRoot}:${process.env.PATH ?? ''}` }
|
||||
})
|
||||
expect(output).toContain('skip 999999')
|
||||
const inventory = parseWslGuestProcessInventoryPayload(output, 'Ubuntu')
|
||||
expect(inventory.rows).toHaveLength(2)
|
||||
const resolved = resolveWslGuestForegroundProcess(inventory, {
|
||||
distro: 'Ubuntu',
|
||||
bootId,
|
||||
shellPid: 100,
|
||||
shellStartTime: 12345,
|
||||
tty: '/dev/pts/0'
|
||||
})
|
||||
expect(resolved).toMatchObject({ status: 'live', processName: 'codex' })
|
||||
} finally {
|
||||
rmSync(fixtureRoot, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('parses fixed fields and preserves whitespace in args', () => {
|
||||
const inventory = parseWslGuestProcessInventoryPayload(
|
||||
payload('row 100 90 90 100 100 pts/0 Sl+ 12345 /usr/bin/node --name "a b" --x'),
|
||||
|
||||
@@ -53,12 +53,22 @@ export const WSL_GUEST_INVENTORY_SCRIPT = [
|
||||
'$_orca_ps',
|
||||
'EOF',
|
||||
'_orca_seen=0',
|
||||
'_orca_skipped=0',
|
||||
'while IFS= read -r _orca_line; do',
|
||||
' [ -n "$_orca_line" ] || { continue; }',
|
||||
' IFS=" " read -r _orca_pid _orca_ppid _orca_sid _orca_pgid _orca_tpgid _orca_tty _orca_stat _orca_args <<EOF',
|
||||
'$_orca_line',
|
||||
'EOF',
|
||||
' { IFS= read -r _orca_procstat < "/proc/$_orca_pid/stat"; } 2>/dev/null || { printf "error start_time\\n"; exit 1; }',
|
||||
' if { IFS= read -r _orca_procstat < "/proc/$_orca_pid/stat"; } 2>/dev/null; then',
|
||||
' :',
|
||||
' elif [ ! -e "/proc/$_orca_pid/stat" ]; then',
|
||||
' printf "skip %s\\n" "$_orca_pid"',
|
||||
' _orca_skipped=$((_orca_skipped + 1))',
|
||||
' continue',
|
||||
' else',
|
||||
' printf "error start_time\\n"',
|
||||
' exit 1',
|
||||
' fi',
|
||||
' _orca_after=${_orca_procstat##*) }',
|
||||
' IFS=" " read -r _orca_dummy1 _orca_dummy2 _orca_dummy3 _orca_dummy4 _orca_dummy5 _orca_dummy6 _orca_dummy7 _orca_dummy8 _orca_dummy9 _orca_dummy10 _orca_dummy11 _orca_dummy12 _orca_dummy13 _orca_dummy14 _orca_dummy15 _orca_dummy16 _orca_dummy17 _orca_dummy18 _orca_dummy19 _orca_start _orca_rest <<EOF',
|
||||
'$_orca_after',
|
||||
@@ -69,7 +79,7 @@ export const WSL_GUEST_INVENTORY_SCRIPT = [
|
||||
'done <<EOF',
|
||||
'$_orca_ps',
|
||||
'EOF',
|
||||
'printf "count %s %s\\n" "$_orca_seen" "$_orca_expected"'
|
||||
'printf "count %s %s %s\\n" "$_orca_seen" "$_orca_expected" "$_orca_skipped"'
|
||||
].join('\n')
|
||||
|
||||
type ReaderDeps = {
|
||||
|
||||
Reference in New Issue
Block a user