Files
orca/src/main/ipc/runtime-watcher-process-pool.test.ts
T
Neil bfdec26352 fix(lint): enable anti-slop/no-object-parameters (#20781)
The rule rejects the broad `object` type on any function input (declarations,
expressions, arrows, methods, call/construct signatures, function types), plus
local aliases and unions that resolve to `object`. `object` accepts every
non-primitive while exposing no properties, so it documents nothing and pushes
callers into assertions at the boundary.

Fixes all 185 violations across src, config, tests and mobile, and flips the
rule from "off" to "error" in config/oxlint-anti-slop.json.

Approach: replace each `object` input with the type its owner already has.
Most sites took an existing domain type or a type-only import (36 added);
40 new aliases name shapes that had none. Where a value is genuinely only
compared by reference, it gets a named identity token instead of a shape --
`Record<string, never>`, the built-in `WeakKey`, or a `unique symbol` brand,
matching the branding already used in src/shared. Same treatment for WeakMap
and Map key parameters. Two `as unknown as` casts became unnecessary once the
parameter carried a real type and were removed; no new casts were added.

Suppressions added: none. No `oxlint-disable` for this rule anywhere, and no
max-lines disable or per-file bump.

Three files sat exactly at their max-lines cap, so the added type imports were
made line-neutral rather than suppressed:
- src/main/ipc/browser.ts exports the existing guest-registration args type
  (renamed BrowserGuestArgs) so browser.test.ts reuses it on one line.
- pane-scroll.ts takes TerminalScrollIntentTarget through the existing
  pane-manager-types import via a type-only re-export.
- direct-rpc-client.ts drops the identity parameter entirely: the session
  check moved into the sendProbe callback that owns the token.

Verified: anti-slop config reports zero violations over src config tests
mobile; run-typecheck-projects-in-parallel exits 0; 144 affected test files
pass (1749 tests); oxlint and oxfmt clean on all changed files. Mobile has no
runnable test/typecheck target in this worktree (expo is not installed), so
its 6 files were typechecked against a standalone config and diffed against
the base branch -- error sets are byte-identical, including test files.
2026-09-15 01:59:58 -07:00

626 lines
22 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import { WatcherProcessFailure } from './parcel-watcher-process-failure'
import type { WatcherProcessSubscribeOptions } from './parcel-watcher-process-protocol'
import type {
WatcherProcessCallback,
WatcherProcessHooks,
WatcherProcessSubscription
} from './parcel-watcher-process-subscription'
import { RuntimeWatcherProcessPool } from './runtime-watcher-process-pool'
type InstalledSubscription = {
dir: string
hooks: WatcherProcessHooks
unsubscribe: ReturnType<typeof vi.fn<() => Promise<void>>>
}
function deferred(): { promise: Promise<void>; resolve: () => void } {
let resolve = (): void => undefined
const promise = new Promise<void>((nextResolve) => {
resolve = nextResolve
})
return { promise, resolve }
}
class FakeSupervisor {
readonly subscriptions: InstalledSubscription[] = []
readonly dispose = vi.fn()
subscribeError?: Error
async subscribe(
dir: string,
_callback: WatcherProcessCallback,
_opts: WatcherProcessSubscribeOptions,
hooks: WatcherProcessHooks
): Promise<WatcherProcessSubscription> {
if (this.subscribeError) {
throw this.subscribeError
}
const unsubscribe = vi.fn(async () => undefined)
this.subscriptions.push({ dir, hooks, unsubscribe })
return { unsubscribe }
}
}
describe('RuntimeWatcherProcessPool', () => {
let supervisors: FakeSupervisor[]
let pool: RuntimeWatcherProcessPool
beforeEach(() => {
supervisors = []
pool = new RuntimeWatcherProcessPool({
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
})
it('shares one healthy child until a fault requires quarantine isolation', async () => {
for (const dir of ['/a', '/b', '/c', '/d', '/e']) {
await pool.subscribe(dir, vi.fn(), {}, {})
}
expect(supervisors).toHaveLength(1)
expect(supervisors.map((supervisor) => supervisor.subscriptions.map(({ dir }) => dir))).toEqual(
[['/a', '/b', '/c', '/d', '/e']]
)
})
it('keeps a newer same-root lease assigned while an older lease tears down', async () => {
const first = await pool.subscribe('/same', vi.fn(), {}, {})
await pool.subscribe('/same', vi.fn(), {}, {})
await first.unsubscribe()
await pool.subscribe('/same', vi.fn(), {}, {})
expect(supervisors).toHaveLength(1)
expect(supervisors[0].subscriptions.map(({ dir }) => dir)).toEqual(['/same', '/same', '/same'])
})
it('isolates every root from a failed shard when callers recover', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const firstError = vi.fn()
const secondError = vi.fn()
await pool.subscribe('/a', vi.fn(), {}, { onTerminalError: firstError })
await pool.subscribe('/b', vi.fn(), {}, { onTerminalError: secondError })
const failedShard = supervisors[0]
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
for (const installed of failedShard.subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
await pool.subscribe('/a', vi.fn(), {}, {})
await pool.subscribe('/b', vi.fn(), {}, {})
expect(firstError).toHaveBeenCalledWith(failure)
expect(secondError).toHaveBeenCalledWith(failure)
expect(supervisors).toHaveLength(3)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/a'])
expect(supervisors[2].subscriptions.map(({ dir }) => dir)).toEqual(['/b'])
})
it('does not assign a replacement until the failed child physically exits', async () => {
const physicalExit = deferred()
await pool.subscribe('/held', vi.fn(), {}, {})
const failure = new WatcherProcessFailure(
'file watcher process did not exit after termination deadline',
'supervisor',
'process_unavailable',
physicalExit.promise
)
supervisors[0].subscriptions[0].hooks.onTerminalError?.(failure)
await expect(pool.subscribe('/held', vi.fn(), {}, {})).rejects.toBe(failure)
expect(supervisors).toHaveLength(1)
physicalExit.resolve()
await physicalExit.promise
await expect(pool.subscribe('/held', vi.fn(), {}, {})).resolves.toBeDefined()
expect(supervisors).toHaveLength(2)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/held'])
})
it('bounds single-root quarantine supervisors and their wait queue', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const roots = Array.from({ length: 9 }, (_, index) => `/root-${index}`)
for (const root of roots) {
await pool.subscribe(root, vi.fn(), {}, {})
}
const failedShard = supervisors[0]
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
for (const installed of failedShard.subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const recoveries = roots.map((root) => pool.subscribe(root, vi.fn(), {}, {}))
const queueOverflow = expect(recoveries[8]).rejects.toThrow('quarantine capacity exhausted')
const active = await Promise.all(recoveries.slice(0, 4))
expect(supervisors).toHaveLength(5)
expect(supervisors.slice(1).map(({ subscriptions }) => subscriptions.length)).toEqual([
1, 1, 1, 1
])
for (let index = 0; index < active.length; index++) {
await active[index].unsubscribe()
await expect(recoveries[index + 4]).resolves.toBeDefined()
}
await queueOverflow
expect(supervisors).toHaveLength(5)
expect(supervisors.slice(1).map(({ subscriptions }) => subscriptions.length)).toEqual([
2, 2, 2, 2
])
})
it('does not fail a healthy queued root when a quarantine root fuses again', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/poison', vi.fn(), {}, {})
await pool.subscribe('/healthy', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
await pool.subscribe('/poison', vi.fn(), {}, {})
const healthyRecovery = pool.subscribe('/healthy', vi.fn(), {}, {})
supervisors[1].subscriptions[0].hooks.onTerminalError?.(failure)
await expect(healthyRecovery).resolves.toBeDefined()
await expect(pool.subscribe('/poison', vi.fn(), {}, {})).rejects.toThrow(
'failed again in quarantine'
)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/poison'])
expect(supervisors[2].subscriptions.map(({ dir }) => dir)).toEqual(['/healthy'])
})
it('bounds how long a root can wait for quarantine capacity', async () => {
vi.useFakeTimers()
try {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/occupied', vi.fn(), {}, {})
await pool.subscribe('/waiting', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
await pool.subscribe('/occupied', vi.fn(), {}, {})
const waiting = pool.subscribe('/waiting', vi.fn(), {}, { subscribeTimeoutMs: 25 })
const timedOut = expect(waiting).rejects.toThrow('timed out waiting for quarantine capacity')
await vi.advanceTimersByTimeAsync(25)
await timedOut
expect(supervisors).toHaveLength(2)
} finally {
pool.dispose()
expect(vi.getTimerCount()).toBe(0)
vi.useRealTimers()
}
})
it('dedupes concurrent same-root assignments waiting for quarantine capacity', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/occupied', vi.fn(), {}, {})
await pool.subscribe('/same', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const occupied = await pool.subscribe('/occupied', vi.fn(), {}, {})
const first = pool.subscribe('/same', vi.fn(), {}, {})
const second = pool.subscribe('/same', vi.fn(), {}, {})
await occupied.unsubscribe()
const [firstSubscription, secondSubscription] = await Promise.all([first, second])
expect(supervisors).toHaveLength(2)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual([
'/occupied',
'/same',
'/same'
])
await firstSubscription.unsubscribe()
await secondSubscription.unsubscribe()
})
it('keeps a healthy same-root quarantine waiter when the first waiter aborts', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/occupied', vi.fn(), {}, {})
await pool.subscribe('/same', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const occupied = await pool.subscribe('/occupied', vi.fn(), {}, {})
const firstController = new AbortController()
const first = pool.subscribe('/same', vi.fn(), {}, { signal: firstController.signal })
const second = pool.subscribe('/same', vi.fn(), {}, {})
firstController.abort()
await expect(first).rejects.toMatchObject({ code: 'subscribe_aborted' })
await occupied.unsubscribe()
const secondSubscription = await second
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/occupied', '/same'])
await secondSubscription.unsubscribe()
})
it('does not let an aborted zero-waiter assignment poison a same-turn replacement', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/occupied', vi.fn(), {}, {})
await pool.subscribe('/same', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const occupied = await pool.subscribe('/occupied', vi.fn(), {}, {})
const controller = new AbortController()
const abandoned = pool.subscribe('/same', vi.fn(), {}, { signal: controller.signal })
controller.abort()
const replacement = pool.subscribe('/same', vi.fn(), {}, {})
await expect(abandoned).rejects.toMatchObject({ code: 'subscribe_aborted' })
await occupied.unsubscribe()
const replacementSubscription = await replacement
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/occupied', '/same'])
await replacementSubscription.unsubscribe()
})
it('rejects an aborted later same-root waiter without cancelling the first', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/occupied', vi.fn(), {}, {})
await pool.subscribe('/same', vi.fn(), {}, {})
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const occupied = await pool.subscribe('/occupied', vi.fn(), {}, {})
const first = pool.subscribe('/same', vi.fn(), {}, {})
const secondController = new AbortController()
const second = pool.subscribe('/same', vi.fn(), {}, { signal: secondController.signal })
secondController.abort()
await expect(second).rejects.toMatchObject({ code: 'subscribe_aborted' })
let firstSettled = false
void first.finally(() => {
firstSettled = true
})
await Promise.resolve()
expect(firstSettled).toBe(false)
await occupied.unsubscribe()
const firstSubscription = await first
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/occupied', '/same'])
await firstSubscription.unsubscribe()
})
it('releases a granted assignment when its last waiter aborts before lease publication', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
maxQuarantineSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
for (const root of ['/occupied', '/aborted', '/following']) {
await pool.subscribe(root, vi.fn(), {}, {})
}
for (const installed of supervisors[0].subscriptions) {
installed.hooks.onTerminalError?.(failure)
}
const occupied = await pool.subscribe('/occupied', vi.fn(), {}, {})
const controller = new AbortController()
const aborted = pool.subscribe('/aborted', vi.fn(), {}, { signal: controller.signal })
const release = occupied.unsubscribe()
queueMicrotask(() => controller.abort())
await release
await expect(aborted).rejects.toMatchObject({ code: 'subscribe_aborted' })
const following = pool.subscribe('/following', vi.fn(), {}, { subscribeTimeoutMs: 25 })
const followingSubscription = await following
expect(supervisors).toHaveLength(3)
expect(supervisors[2].subscriptions.map(({ dir }) => dir)).toEqual(['/following'])
expect(supervisors[1].dispose).toHaveBeenCalledTimes(1)
await followingSubscription.unsubscribe()
})
it('moves an explicitly timed-out root into quarantine for its retry', async () => {
const timeout = new WatcherProcessFailure(
'file watcher subscription timed out',
'subscription',
'subscribe_timeout'
)
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
if (supervisors.length === 0) {
supervisor.subscribeError = timeout
}
supervisors.push(supervisor)
return supervisor
}
})
await expect(pool.subscribe('/slow', vi.fn(), {}, {})).rejects.toBe(timeout)
await expect(pool.subscribe('/slow', vi.fn(), {}, {})).resolves.toBeDefined()
expect(supervisors).toHaveLength(2)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/slow'])
})
it('allows only one quarantine generation when isolated setup also times out', async () => {
const timeout = new WatcherProcessFailure(
'file watcher subscription timed out',
'subscription',
'subscribe_timeout'
)
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisor.subscribeError = timeout
supervisors.push(supervisor)
return supervisor
}
})
await expect(pool.subscribe('/slow', vi.fn(), {}, {})).rejects.toBe(timeout)
await expect(pool.subscribe('/slow', vi.fn(), {}, {})).rejects.toBe(timeout)
await expect(pool.subscribe('/slow', vi.fn(), {}, {})).rejects.toMatchObject({
code: 'supervisor_crash_fuse'
})
expect(supervisors).toHaveLength(2)
})
it('moves a live root into quarantine when crash resubscription times out', async () => {
const timeout = new WatcherProcessFailure(
'file watcher resubscription timed out',
'subscription',
'subscribe_timeout'
)
const onTerminalError = vi.fn()
await pool.subscribe('/slow-recovery', vi.fn(), {}, { onTerminalError })
supervisors[0].subscriptions[0].hooks.onTerminalError?.(timeout)
await pool.subscribe('/slow-recovery', vi.fn(), {}, {})
expect(onTerminalError).toHaveBeenCalledWith(timeout)
expect(supervisors).toHaveLength(2)
expect(supervisors[1].subscriptions.map(({ dir }) => dir)).toEqual(['/slow-recovery'])
})
it('does not replace an isolated live root after its resubscription times out', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const fused = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
const timeout = new WatcherProcessFailure(
'file watcher resubscription timed out',
'subscription',
'subscribe_timeout'
)
await pool.subscribe('/slow-recovery', vi.fn(), {}, {})
supervisors[0].subscriptions[0].hooks.onTerminalError?.(fused)
await pool.subscribe('/slow-recovery', vi.fn(), {}, {})
supervisors[1].subscriptions[0].hooks.onTerminalError?.(timeout)
await expect(pool.subscribe('/slow-recovery', vi.fn(), {}, {})).rejects.toMatchObject({
code: 'supervisor_crash_fuse'
})
expect(supervisors).toHaveLength(2)
})
it('keeps healthy shard assignments after a root-specific failure', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
await pool.subscribe('/healthy', vi.fn(), {}, {})
const onTerminalError = vi.fn()
await pool.subscribe('/gone', vi.fn(), {}, { onTerminalError })
const shared = supervisors[0]
const failed = shared.subscriptions.find(({ dir }) => dir === '/gone')
const failure = new WatcherProcessFailure(
'root unavailable',
'subscription',
'subscribe_failed'
)
failed?.hooks.onTerminalError?.(failure)
await pool.subscribe('/gone', vi.fn(), {}, {})
expect(onTerminalError).toHaveBeenCalledWith(failure)
expect(supervisors).toHaveLength(1)
expect(shared.subscriptions.map(({ dir }) => dir)).toEqual(['/healthy', '/gone', '/gone'])
expect(shared.dispose).not.toHaveBeenCalled()
})
it('stops replacing a root after its bounded quarantine shard also fuses', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/unstable', vi.fn(), {}, {})
supervisors[0].subscriptions[0].hooks.onTerminalError?.(failure)
await pool.subscribe('/unstable', vi.fn(), {}, {})
supervisors[1].subscriptions[0].hooks.onTerminalError?.(failure)
await expect(pool.subscribe('/unstable', vi.fn(), {}, {})).rejects.toThrow(
'failed again in quarantine'
)
expect(supervisors).toHaveLength(2)
})
it('clears fused quarantine history when forgetRoot runs without an assignment', async () => {
pool = new RuntimeWatcherProcessPool({
maxSharedSupervisors: 1,
createSupervisor: () => {
const supervisor = new FakeSupervisor()
supervisors.push(supervisor)
return supervisor
}
})
const failure = new WatcherProcessFailure(
'file watcher process crashed repeatedly',
'supervisor',
'supervisor_crash_fuse'
)
await pool.subscribe('/unstable', vi.fn(), {}, {})
supervisors[0].subscriptions[0].hooks.onTerminalError?.(failure)
await pool.subscribe('/unstable', vi.fn(), {}, {})
supervisors[1].subscriptions[0].hooks.onTerminalError?.(failure)
await expect(pool.subscribe('/unstable', vi.fn(), {}, {})).rejects.toThrow(
'failed again in quarantine'
)
pool.forgetRoot('/unstable')
await expect(pool.subscribe('/unstable', vi.fn(), {}, {})).resolves.toBeDefined()
expect(supervisors).toHaveLength(3)
})
it('disposes every supervisor and clears isolation on reset', async () => {
await pool.subscribe('/a', vi.fn(), {}, {})
await pool.subscribe('/b', vi.fn(), {}, {})
pool.resetForTest()
expect(supervisors.every((supervisor) => supervisor.dispose.mock.calls.length === 1)).toBe(true)
await expect(pool.subscribe('/after-reset', vi.fn(), {}, {})).resolves.toBeDefined()
expect(supervisors).toHaveLength(2)
})
})