mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(watchers): stop stat admission without listeners (#34870)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -285,34 +285,41 @@ describe('local filesystem watcher flush serialization', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('starts no further stats when a full inflight batch is cancelled', async () => {
|
||||
const eventCount = 5_000
|
||||
const pendingStats = deferred<{ isDirectory: () => boolean }>()
|
||||
statMock.mockReturnValue(pendingStats.promise)
|
||||
const root = await createLocalWatcher('/repo', '/repo')
|
||||
root.listeners.set(1, sender as never)
|
||||
it.each(['cancelled', 'unwatched'] as const)(
|
||||
'starts no further stats when a full inflight batch is %s',
|
||||
async (endState) => {
|
||||
const eventCount = 5_000
|
||||
const pendingStats = deferred<{ isDirectory: () => boolean }>()
|
||||
statMock.mockReturnValue(pendingStats.promise)
|
||||
const root = await createLocalWatcher('/repo', '/repo')
|
||||
root.listeners.set(1, sender as never)
|
||||
|
||||
watcherCallback?.(
|
||||
null,
|
||||
Array.from({ length: eventCount }, (_, index) => ({
|
||||
type: 'update' as const,
|
||||
path: `/repo/file-${index}.ts`
|
||||
}))
|
||||
)
|
||||
vi.advanceTimersByTime(WATCH_BATCH_INTERVAL_MS)
|
||||
await flushMicrotasks()
|
||||
expect(statMock).toHaveBeenCalledTimes(8)
|
||||
watcherCallback?.(
|
||||
null,
|
||||
Array.from({ length: eventCount }, (_, index) => ({
|
||||
type: 'update' as const,
|
||||
path: `/repo/file-${index}.ts`
|
||||
}))
|
||||
)
|
||||
vi.advanceTimersByTime(WATCH_BATCH_INTERVAL_MS)
|
||||
await flushMicrotasks()
|
||||
expect(statMock).toHaveBeenCalledTimes(8)
|
||||
|
||||
cancelLocalBatchFlush(root)
|
||||
pendingStats.resolve({ isDirectory: () => false })
|
||||
for (let i = 0; i < eventCount * 4 && root.batch.flushInFlight; i++) {
|
||||
await Promise.resolve()
|
||||
if (endState === 'cancelled') {
|
||||
cancelLocalBatchFlush(root)
|
||||
} else {
|
||||
root.listeners.clear()
|
||||
}
|
||||
pendingStats.resolve({ isDirectory: () => false })
|
||||
for (let i = 0; i < eventCount * 4 && root.batch.flushInFlight; i++) {
|
||||
await Promise.resolve()
|
||||
}
|
||||
|
||||
expect(root.batch.flushInFlight).toBe(false)
|
||||
expect(statMock).toHaveBeenCalledTimes(8)
|
||||
expect(sender.send).not.toHaveBeenCalled()
|
||||
}
|
||||
|
||||
expect(root.batch.flushInFlight).toBe(false)
|
||||
expect(statMock).toHaveBeenCalledTimes(8)
|
||||
expect(sender.send).not.toHaveBeenCalled()
|
||||
})
|
||||
)
|
||||
|
||||
it('leaves an open flush window to the armed timer instead of draining early', async () => {
|
||||
const firstStat = deferred<{ isDirectory: () => boolean }>()
|
||||
@@ -357,13 +364,18 @@ describe('local filesystem watcher flush serialization', () => {
|
||||
const listener = { ...sender, id: 7, removeListener: vi.fn(), on: vi.fn() }
|
||||
try {
|
||||
await subscribeLocalWatcher('/repo', listener as never)
|
||||
watcherCallback?.(null, [{ type: 'delete', path: '/repo/file.ts' }])
|
||||
watcherCallback?.(null, [{ type: 'create', path: '/repo/directory' }])
|
||||
unsubscribeLocalWatcher('/repo', listener.id)
|
||||
await subscribeLocalWatcher('/repo', listener as never)
|
||||
watcherCallback?.(null, [{ type: 'delete', path: '/repo/file.ts' }])
|
||||
watcherCallback?.(null, [{ type: 'create', path: '/repo/directory' }])
|
||||
await vi.waitFor(() => expect(sender.send).toHaveBeenCalledTimes(1), {
|
||||
timeout: WATCH_BATCH_INTERVAL_MS * 20
|
||||
})
|
||||
expect(subscribeMock).toHaveBeenCalledTimes(1)
|
||||
expect(sender.send).toHaveBeenCalledWith('fs:changed', {
|
||||
worktreePath: '/repo',
|
||||
events: [{ kind: 'create', absolutePath: '/repo/directory', isDirectory: true }]
|
||||
})
|
||||
} finally {
|
||||
for (const teardown of watcherLifecycleState.pendingTeardowns.values()) {
|
||||
clearTimeout(teardown)
|
||||
|
||||
@@ -131,7 +131,7 @@ async function flushBatch(root: WatchedRoot): Promise<void> {
|
||||
async (evt) => {
|
||||
// Deleted paths cannot be statted; let the renderer infer from dirCache.
|
||||
const isDirectory =
|
||||
root.batch.cancelled || evt.type === 'delete'
|
||||
root.batch.cancelled || root.listeners.size === 0 || evt.type === 'delete'
|
||||
? undefined
|
||||
: await tryStatIsDirectory(evt.path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user