mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
fix(ports): coalesce advertised URL refresh bursts (#19150)
This commit is contained in:
@@ -602,3 +602,113 @@ describe('WorkspacePortScanner', () => {
|
||||
expect(getPublishedRemoteWorktreePorts()).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('advertised URL refresh bursts', () => {
|
||||
async function mountLocalScanner(): Promise<() => void> {
|
||||
useAppStore.setState({ settings: getDefaultSettings('/tmp/orca-workspaces') })
|
||||
await act(async () => {
|
||||
root?.render(<WorkspacePortScanner />)
|
||||
await flushPromises()
|
||||
})
|
||||
localScan.mockClear()
|
||||
return vi.mocked(window.api.workspacePorts.onAdvertisedUrlChanged).mock
|
||||
.calls[0][0] as () => void
|
||||
}
|
||||
|
||||
it('coalesces sequential URL changes into one immediate scan and one settled scan', async () => {
|
||||
const changed = await mountLocalScanner()
|
||||
for (let index = 0; index < 5; index++) {
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
})
|
||||
}
|
||||
expect(localScan).toHaveBeenCalledTimes(1)
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(1_000)
|
||||
})
|
||||
expect(localScan).toHaveBeenCalledTimes(2)
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
expect(localScan).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('cancels the settled scan on unmount', async () => {
|
||||
const changed = await mountLocalScanner()
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
act(() => root?.unmount())
|
||||
root = null
|
||||
await vi.advanceTimersByTimeAsync(2_000)
|
||||
expect(localScan).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('skips the settled scan while hidden and accepts the next visible URL change', async () => {
|
||||
let visibility: DocumentVisibilityState = 'visible'
|
||||
const restore = overrideDocumentVisibilityState(() => visibility)
|
||||
try {
|
||||
const changed = await mountLocalScanner()
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
visibility = 'hidden'
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(2_000)
|
||||
})
|
||||
expect(localScan).toHaveBeenCalledTimes(1)
|
||||
visibility = 'visible'
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
expect(localScan).toHaveBeenCalledTimes(2)
|
||||
} finally {
|
||||
restore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('releases the URL burst when its leading scan finishes while hidden', async () => {
|
||||
let visibility: DocumentVisibilityState = 'visible'
|
||||
const restore = overrideDocumentVisibilityState(() => visibility)
|
||||
try {
|
||||
useAppStore.setState({ settings: getDefaultSettings('/tmp/orca-workspaces') })
|
||||
await act(async () => {
|
||||
root?.render(<WorkspacePortScanner />)
|
||||
await flushPromises()
|
||||
})
|
||||
const changed = vi.mocked(window.api.workspacePorts.onAdvertisedUrlChanged).mock
|
||||
.calls[0][0] as () => void
|
||||
let finish!: (scan: WorkspacePortScanResult) => void
|
||||
localScan.mockClear()
|
||||
localScan.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<WorkspacePortScanResult>((resolve) => {
|
||||
finish = resolve
|
||||
})
|
||||
)
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
visibility = 'hidden'
|
||||
await act(async () => {
|
||||
finish(emptyScan)
|
||||
await flushPromises()
|
||||
})
|
||||
visibility = 'visible'
|
||||
await act(async () => {
|
||||
changed()
|
||||
await flushPromises()
|
||||
})
|
||||
expect(localScan).toHaveBeenCalledTimes(2)
|
||||
} finally {
|
||||
restore()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -280,6 +280,7 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }):
|
||||
return
|
||||
}
|
||||
|
||||
let burstRefresh: Promise<void> | null = null
|
||||
let eventSequence = 0
|
||||
let disposed = false
|
||||
let retryTimer: ReturnType<typeof setTimeout> | null = null
|
||||
@@ -296,15 +297,24 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }):
|
||||
const sequence = eventSequence
|
||||
clearRetryTimer()
|
||||
if (!isWindowVisible()) {
|
||||
burstRefresh = null
|
||||
return
|
||||
}
|
||||
void refresh({ force: true, targets: [runtimeTarget] }).finally(() => {
|
||||
if (disposed || sequence !== eventSequence || !isWindowVisible()) {
|
||||
// Keep the leading scan through the quiet window so sequential events share it too.
|
||||
burstRefresh ??= refresh({ force: true, targets: [runtimeTarget] })
|
||||
void burstRefresh.finally(() => {
|
||||
if (disposed || sequence !== eventSequence) {
|
||||
return
|
||||
}
|
||||
if (!isWindowVisible()) {
|
||||
burstRefresh = null
|
||||
return
|
||||
}
|
||||
// Why: some dev servers print their URL just before the listener is
|
||||
// visible to lsof/netstat. One quiet settle scan catches that startup race.
|
||||
retryTimer = setTimeout(() => {
|
||||
retryTimer = null
|
||||
burstRefresh = null
|
||||
if (disposed || sequence !== eventSequence || !isWindowVisible()) {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user