From 85c7a44ee3bc2d1ffebac91fb28b0492648b0d9b Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Tue, 18 Aug 2026 19:54:31 -0400 Subject: [PATCH] perf(browser): stop the reclaim sweep once nothing is resident MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sweep only stopped when the last page was closed, so a host whose pages had all been parked kept waking every interval to find nothing to reclaim — on a headless server that is meant to sit idle cheaply. It now stops as soon as nothing is resident, and a wake reschedules it, because a woken page has to become reclaimable again. The timer only ever served the idle evictor. The cap is inherently event-driven — residency changes only on create and wake — but a page going idle produces no event by definition, so something has to look. Polling is the cheaper form of looking here rather than the lazier one: activity is recorded on every command, so per-page deadlines would be rescheduled far more often than the interval fires. --- .../offscreen-browser-backend-reclaim.test.ts | 18 ++++++++++++++++++ src/main/browser/offscreen-browser-backend.ts | 3 +++ .../offscreen-browser-page-reclaimer.ts | 11 +++++++++++ 3 files changed, 32 insertions(+) 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