Coalesce stale conflict polling (#2081)
This commit is contained in:
parent
b4a5c142a7
commit
53f03255dd
|
|
@ -0,0 +1,58 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createCoalescedPollRunner } from './coalesced-poll-runner'
|
||||
|
||||
function deferred(): {
|
||||
promise: Promise<void>
|
||||
resolve: () => void
|
||||
} {
|
||||
let resolve: () => void = () => {}
|
||||
const promise = new Promise<void>((r) => {
|
||||
resolve = r
|
||||
})
|
||||
return { promise, resolve }
|
||||
}
|
||||
|
||||
async function flushMicrotasks(): Promise<void> {
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
}
|
||||
|
||||
describe('createCoalescedPollRunner', () => {
|
||||
it('keeps one task in flight and runs one trailing task after skipped triggers', async () => {
|
||||
const calls: ReturnType<typeof deferred>[] = []
|
||||
const task = vi.fn(() => {
|
||||
const call = deferred()
|
||||
calls.push(call)
|
||||
return call.promise
|
||||
})
|
||||
const runner = createCoalescedPollRunner(task)
|
||||
|
||||
runner.run()
|
||||
runner.run()
|
||||
runner.run()
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(task).toHaveBeenCalledTimes(1)
|
||||
calls[0]?.resolve()
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(task).toHaveBeenCalledTimes(2)
|
||||
calls[1]?.resolve()
|
||||
await flushMicrotasks()
|
||||
expect(task).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('drops queued trailing work after disposal', async () => {
|
||||
const call = deferred()
|
||||
const task = vi.fn(() => call.promise)
|
||||
const runner = createCoalescedPollRunner(task)
|
||||
|
||||
runner.run()
|
||||
runner.run()
|
||||
runner.dispose()
|
||||
call.resolve()
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(task).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
export type CoalescedPollRunner = {
|
||||
run: () => void
|
||||
dispose: () => void
|
||||
}
|
||||
|
||||
export function createCoalescedPollRunner(task: () => Promise<void>): CoalescedPollRunner {
|
||||
let disposed = false
|
||||
let inFlight = false
|
||||
let rerun = false
|
||||
|
||||
const run = (): void => {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
if (inFlight) {
|
||||
rerun = true
|
||||
return
|
||||
}
|
||||
inFlight = true
|
||||
void task()
|
||||
.catch(() => {
|
||||
// Poll callers handle their own expected transient errors. A rejected
|
||||
// task must still release the in-flight latch and optional trailing run.
|
||||
})
|
||||
.finally(() => {
|
||||
inFlight = false
|
||||
if (rerun && !disposed) {
|
||||
rerun = false
|
||||
run()
|
||||
return
|
||||
}
|
||||
rerun = false
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
run,
|
||||
dispose: () => {
|
||||
disposed = true
|
||||
rerun = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { isGitRepoKind } from '../../../../shared/repo-kind'
|
|||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRuntimeGitConflictOperation } from '@/runtime/runtime-git-client'
|
||||
import { refreshGitStatusForWorktree } from './git-status-refresh'
|
||||
import { createCoalescedPollRunner } from './coalesced-poll-runner'
|
||||
|
||||
const POLL_INTERVAL_MS = 3000
|
||||
|
||||
|
|
@ -164,15 +165,20 @@ export function useGitStatusPolling(): void {
|
|||
}
|
||||
}
|
||||
|
||||
void pollStale()
|
||||
// Why: remote conflict probes can exceed the 3s interval. Keep one poll in
|
||||
// flight and coalesce skipped ticks into one trailing pass so stale badges
|
||||
// catch up without stacking SSH/RPC work.
|
||||
const pollRunner = createCoalescedPollRunner(pollStale)
|
||||
pollRunner.run()
|
||||
const intervalId = setInterval(() => {
|
||||
if (document.hasFocus()) {
|
||||
void pollStale()
|
||||
pollRunner.run()
|
||||
}
|
||||
}, POLL_INTERVAL_MS)
|
||||
const onFocus = (): void => void pollStale()
|
||||
const onFocus = (): void => pollRunner.run()
|
||||
window.addEventListener('focus', onFocus)
|
||||
return () => {
|
||||
pollRunner.dispose()
|
||||
clearInterval(intervalId)
|
||||
window.removeEventListener('focus', onFocus)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue