mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
fix(runtime): retry watches after teardown deadlines
This commit is contained in:
@@ -28,7 +28,9 @@ export class WatcherTerminationQueue {
|
||||
private current: Promise<void> | null = null
|
||||
|
||||
waitFor<T>(operation: () => Promise<T>): Promise<T> | null {
|
||||
return this.current?.then(operation) ?? null
|
||||
// Why: an idle child may miss its exit deadline without poisoning the
|
||||
// reusable supervisor; queued work must re-check state after either result.
|
||||
return this.current?.then(operation, operation) ?? null
|
||||
}
|
||||
|
||||
getCurrent(): Promise<void> | null {
|
||||
|
||||
@@ -120,6 +120,38 @@ describe('watcher native unsubscribe timeout', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('spawns a fresh child for a subscribe queued before an idle exit deadline miss', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const promise = subscribeViaWatcherProcess('/repo', vi.fn(), {})
|
||||
const first = currentChild()
|
||||
ackSubscribe(first, 0)
|
||||
const subscription = await promise
|
||||
|
||||
const unsubscribe = subscription.unsubscribe().catch((error: unknown) => error)
|
||||
const queued = subscribeViaWatcherProcess('/repo2', vi.fn(), {})
|
||||
|
||||
await vi.advanceTimersByTimeAsync(WATCHER_PROCESS_EXIT_DEADLINE_MS)
|
||||
const error = (await unsubscribe) as Error & { physicalExit?: Promise<void> }
|
||||
expect(error).toMatchObject({
|
||||
message: 'file watcher process did not exit after termination deadline',
|
||||
physicalExit: expect.any(Promise)
|
||||
})
|
||||
|
||||
expect(forkMock).toHaveBeenCalledTimes(2)
|
||||
const second = currentChild()
|
||||
expect(second.sent[0]).toMatchObject({ op: 'subscribe', dir: '/repo2' })
|
||||
ackSubscribe(second)
|
||||
await expect(queued).resolves.toMatchObject({ unsubscribe: expect.any(Function) })
|
||||
|
||||
first.emit('exit', null, 'SIGKILL')
|
||||
await error.physicalExit
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects only after an unkillable child reaches its exit deadline', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('cross-platform path containment', () => {
|
||||
expect(normalizeRuntimePathForComparison('/srv/team\\repo')).toBe('/srv/team\\repo')
|
||||
expect(normalizeRuntimePathForComparison('/srv/team/repo')).toBe('/srv/team/repo')
|
||||
expect(isPathInsideOrEqual('/srv/team\\repo', '/srv/team/repo/file.ts')).toBe(false)
|
||||
expect(relativePathInsideRoot('/srv/repo', '/srv/repo/a\\b.txt')).toBe('a\\b.txt')
|
||||
})
|
||||
|
||||
it('handles Windows drive roots and sibling drives case-insensitively', () => {
|
||||
|
||||
@@ -69,7 +69,9 @@ export function isPathInsideOrEqual(rootPath: string, candidatePath: string): bo
|
||||
|
||||
export function relativePathInsideRoot(rootPath: string, candidatePath: string): string | null {
|
||||
const normalizedCandidate = trimRuntimePathTrailingSlash(
|
||||
normalizeRuntimePathSeparators(candidatePath)
|
||||
isWindowsAbsolutePathLike(candidatePath)
|
||||
? normalizeRuntimePathSeparators(candidatePath)
|
||||
: candidatePath.replace(/\/+/g, '/')
|
||||
)
|
||||
const comparisonRoot = normalizeRuntimePathForComparison(rootPath)
|
||||
const comparisonCandidate = normalizeRuntimePathForComparison(candidatePath)
|
||||
|
||||
Reference in New Issue
Block a user