fix(desktop): coalesce pending live status refreshes
This commit is contained in:
@@ -9,7 +9,12 @@ import { useBackgroundSync } from './use-background-sync'
|
||||
const noop = () => undefined
|
||||
const requestGateway = async () => ({ sessions: [] })
|
||||
|
||||
function render(activeGatewayProfile: string, activeConnectionId: string, refreshSessions: () => Promise<void>) {
|
||||
function render(
|
||||
activeGatewayProfile: string,
|
||||
activeConnectionId: string,
|
||||
refreshSessions: () => Promise<void>,
|
||||
gatewayRequest = requestGateway
|
||||
) {
|
||||
return renderHook(
|
||||
({ connectionId, profile }: { connectionId: string; profile: string }) => {
|
||||
useBackgroundSync({
|
||||
@@ -26,7 +31,7 @@ function render(activeGatewayProfile: string, activeConnectionId: string, refres
|
||||
refreshHermesConfig: noop,
|
||||
refreshMessagingSessions: noop,
|
||||
refreshSessions,
|
||||
requestGateway
|
||||
requestGateway: gatewayRequest
|
||||
})
|
||||
},
|
||||
{ initialProps: { connectionId: activeConnectionId, profile: activeGatewayProfile } }
|
||||
@@ -47,6 +52,39 @@ describe('useBackgroundSync profile-scoped session refresh', () => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('coalesces change ticks while the live status request is pending', async () => {
|
||||
$changeEventsAvailable.set(true)
|
||||
let release!: (value: { sessions: [] }) => void
|
||||
const pending = new Promise<{ sessions: [] }>(resolve => { release = resolve })
|
||||
const request = vi.fn(() => pending)
|
||||
render('default', 'local', async () => undefined, request)
|
||||
await act(async () => undefined)
|
||||
|
||||
for (let tick = 1; tick <= 8; tick += 1) {
|
||||
await act(async () => { $sessionsChangeTick.set(tick) })
|
||||
}
|
||||
|
||||
expect(request).toHaveBeenCalledTimes(1)
|
||||
await act(async () => { release({ sessions: [] }) })
|
||||
expect(request).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('discards a queued old-connection refresh when the connection changes', async () => {
|
||||
let rejectOld!: (reason: Error) => void
|
||||
const oldRequest = new Promise<{ sessions: [] }>((_, reject) => { rejectOld = reject })
|
||||
const request = vi.fn().mockReturnValueOnce(oldRequest).mockResolvedValue({ sessions: [] })
|
||||
const hook = render('default', 'first', async () => undefined, request)
|
||||
await act(async () => { $sessionsChangeTick.set(1) })
|
||||
hook.rerender({ connectionId: 'second', profile: 'default' })
|
||||
await act(async () => undefined)
|
||||
expect(request).toHaveBeenCalledTimes(2)
|
||||
await act(async () => { rejectOld(new Error('old connection closed')) })
|
||||
expect(request).toHaveBeenCalledTimes(2)
|
||||
hook.unmount()
|
||||
await act(async () => { $sessionsChangeTick.set(2) })
|
||||
expect(request).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('refreshes the session list after the active gateway profile changes', async () => {
|
||||
const refreshSessions = vi.fn(async () => undefined)
|
||||
const hook = render('default', 'local', refreshSessions)
|
||||
|
||||
@@ -575,7 +575,6 @@ export function useBackgroundSync({
|
||||
}: BackgroundSyncParams): void {
|
||||
const changeEventsAvailable = useStore($changeEventsAvailable)
|
||||
const cronChangeTick = useStore($cronChangeTick)
|
||||
const sessionsChangeTick = useStore($sessionsChangeTick)
|
||||
const activeTranscriptBusy = useStore($busy)
|
||||
const activeTranscriptRefreshPendingRef = useRef<string | null>(null)
|
||||
// Tile reconcile state (#93942 slice 1): shared sequence guard + per-tile
|
||||
@@ -682,9 +681,16 @@ export function useBackgroundSync({
|
||||
|
||||
let cancelled = false
|
||||
let inFlight = false
|
||||
let refreshPending = false
|
||||
|
||||
const refreshLiveStatuses = async () => {
|
||||
if (cancelled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (inFlight) {
|
||||
refreshPending = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -701,9 +707,16 @@ export function useBackgroundSync({
|
||||
// still work as before; leave the current sidebar state untouched.
|
||||
} finally {
|
||||
inFlight = false
|
||||
|
||||
if (refreshPending && !cancelled) {
|
||||
refreshPending = false
|
||||
void refreshLiveStatuses()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unsubscribe = $sessionsChangeTick.listen(() => void refreshLiveStatuses())
|
||||
|
||||
const dispose = visiblePoll(
|
||||
changeEventsAvailable ? LIVE_SESSION_STATUS_BACKSTOP_INTERVAL_MS : LIVE_SESSION_STATUS_POLL_INTERVAL_MS,
|
||||
() => void refreshLiveStatuses()
|
||||
@@ -713,11 +726,12 @@ export function useBackgroundSync({
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
unsubscribe()
|
||||
dispose()
|
||||
}
|
||||
// sessionsChangeTick: each sessions.changed broadcast re-seeds immediately
|
||||
// via the effect re-run (already coalesced to 2s server-side).
|
||||
}, [activeGatewayProfile, changeEventsAvailable, gatewayState, requestGateway, sessionsChangeTick])
|
||||
// Keep the in-flight guard alive across change ticks; a slow response must
|
||||
// not create a new request (and invalidate the old result) on every tick.
|
||||
}, [activeConnectionId, activeGatewayProfile, changeEventsAvailable, gatewayState, requestGateway])
|
||||
|
||||
// sessions.changed also means the *stored* list may have new rows (a cron
|
||||
// run's session, an inbound messaging turn creating a thread). The full list
|
||||
|
||||
Reference in New Issue
Block a user