diff --git a/src/main/browser/offscreen-browser-backend-reclaim.test.ts b/src/main/browser/offscreen-browser-backend-reclaim.test.ts index 0df53d65230..c4b36f5b761 100644 --- a/src/main/browser/offscreen-browser-backend-reclaim.test.ts +++ b/src/main/browser/offscreen-browser-backend-reclaim.test.ts @@ -489,6 +489,24 @@ describe('OffscreenBrowserBackend reclamation', () => { expect(createTimeout).toBeUndefined() }) + it('stops sweeping once nothing is resident, and resumes on wake', async () => { + // Why: an idle headless host should not keep waking every interval to find + // nothing to reclaim. Waking has to bring the sweep back or the woken page + // would never be reclaimed again. + const h = createHarness() + await h.backend.createTab({ url: 'https://a', browserPageId: 'a' }) + const reclaimer = (h.backend as unknown as { reclaimer: { isScheduled: boolean } }).reclaimer + expect(reclaimer.isScheduled).toBe(true) + + h.clock.value += 120_000 + await h.backend.reclaimIdlePages() + expect(h.backend.listParkedPages()).toHaveLength(1) + expect(reclaimer.isScheduled).toBe(false) + + await h.backend.wakeTab('a') + expect(reclaimer.isScheduled).toBe(true) + }) + it('coalesces concurrent wakes into one renderer', async () => { const h = createHarness() await h.backend.createTab({ url: 'https://example.test/a' }) diff --git a/src/main/browser/offscreen-browser-backend.ts b/src/main/browser/offscreen-browser-backend.ts index 198adde0980..3387c874987 100644 --- a/src/main/browser/offscreen-browser-backend.ts +++ b/src/main/browser/offscreen-browser-backend.ts @@ -185,6 +185,9 @@ export class OffscreenBrowserBackend implements BrowserBackend { return false } page.lastActivityAt = this.now() + // Why: the sweep stops once nothing is resident, so a wake has to bring + // it back or this page would never be reclaimed again. + this.reclaimer.ensureScheduled() return true })() this.waking.set(browserPageId, wake) diff --git a/src/main/browser/offscreen-browser-page-reclaimer.ts b/src/main/browser/offscreen-browser-page-reclaimer.ts index 2cdd89bf06f..93ef228783e 100644 --- a/src/main/browser/offscreen-browser-page-reclaimer.ts +++ b/src/main/browser/offscreen-browser-page-reclaimer.ts @@ -60,9 +60,20 @@ export class OffscreenBrowserPageReclaimer { await this.deps.park(browserPageId) parked.push(browserPageId) } + // Why: with nothing resident there is nothing left to reclaim, so the sweep + // has no work until a page is created or woken — both of which reschedule + // it. Otherwise an idle host keeps waking up forever to find nothing. + if (this.deps.pages.resident().length === 0) { + this.stop() + } return parked } + /** Whether a sweep is currently scheduled. Exposed so the owner can assert it. */ + get isScheduled(): boolean { + return this.sweepTimer !== null + } + ensureScheduled(): void { if (this.sweepTimer) { return