mirror of
https://github.com/stablyai/orca.git
synced 2026-09-27 08:02:35 +00:00
fix: cancel watcher batches after terminal errors
This commit is contained in:
@@ -91,7 +91,7 @@ describe('local filesystem watcher flush serialization', () => {
|
||||
expect(root.batch.timer).toBeNull()
|
||||
})
|
||||
|
||||
it('can schedule a late event after an error clears the pending timer', async () => {
|
||||
it('discards queued and late events after a terminal watcher error', async () => {
|
||||
const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
try {
|
||||
const root = await createLocalWatcher('/repo', '/repo')
|
||||
@@ -102,7 +102,35 @@ describe('local filesystem watcher flush serialization', () => {
|
||||
watcherCallback?.(null, [{ type: 'delete', path: '/repo/file.ts' }])
|
||||
vi.advanceTimersByTime(WATCH_BATCH_TRAILING_MS)
|
||||
await flushMicrotasks()
|
||||
expect(sender.send).toHaveBeenCalledTimes(2)
|
||||
expect(sender.send).toHaveBeenCalledTimes(1)
|
||||
expect(root.batch.cancelled).toBe(true)
|
||||
expect(root.batch.events).toEqual([])
|
||||
expect(root.batch.timer).toBeNull()
|
||||
} finally {
|
||||
errorLog.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('suppresses an inflight batch and its queued drain after a terminal watcher error', async () => {
|
||||
const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const pendingStat = deferred<{ isDirectory: () => boolean }>()
|
||||
statMock.mockReturnValueOnce(pendingStat.promise)
|
||||
try {
|
||||
const root = await createLocalWatcher('/repo', '/repo')
|
||||
root.listeners.set(1, sender as never)
|
||||
watcherCallback?.(null, [{ type: 'update', path: '/repo/first.ts' }])
|
||||
vi.advanceTimersByTime(WATCH_BATCH_TRAILING_MS)
|
||||
await flushMicrotasks()
|
||||
expect(statMock).toHaveBeenCalledTimes(1)
|
||||
watcherCallback?.(null, [{ type: 'update', path: '/repo/queued.ts' }])
|
||||
watcherCallback?.(new Error('watcher interrupted'), [])
|
||||
pendingStat.resolve({ isDirectory: () => false })
|
||||
vi.advanceTimersByTime(WATCH_BATCH_MAX_WAIT_MS)
|
||||
await flushMicrotasks()
|
||||
expect(sender.send).toHaveBeenCalledTimes(1)
|
||||
expect(statMock).toHaveBeenCalledTimes(1)
|
||||
expect(root.batch.events).toEqual([])
|
||||
expect(root.batch.timer).toBeNull()
|
||||
} finally {
|
||||
errorLog.mockRestore()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
retainLocalWatcherPhysicalFailure,
|
||||
trackDetachedLocalUnsubscribe
|
||||
} from './filesystem-watcher-listener-lifecycle'
|
||||
import { createDebouncedBatch } from './filesystem-watcher-batch-control'
|
||||
import { cancelLocalBatchFlush, createDebouncedBatch } from './filesystem-watcher-batch-control'
|
||||
import { mapWithConcurrency } from '../../shared/map-with-concurrency'
|
||||
|
||||
// Why: matches the watcher subprocess budget in parcel-watcher-event-delivery.ts.
|
||||
@@ -258,10 +258,7 @@ export async function createLocalWatcher(
|
||||
console.error(`[filesystem-watcher] error for ${rootKey}:`, err)
|
||||
emitOverflowPayload(root)
|
||||
// Why: after an error the native subscription may be invalid (deleted root); tear down the dead watcher so it doesn't dangle (§7.3).
|
||||
if (root.batch.timer) {
|
||||
clearTimeout(root.batch.timer)
|
||||
root.batch.timer = null
|
||||
}
|
||||
cancelLocalBatchFlush(root)
|
||||
// Why: error callback can fire before subscribe() assigns root.subscription; guard against null so cleanup doesn't crash.
|
||||
if (root.subscription) {
|
||||
retainLocalWatcherPhysicalFailure(rootKey, err)
|
||||
|
||||
Reference in New Issue
Block a user