Skip to content

Commit 7c27470

Browse files
author
gilangjavier
committed
feat(collector): add maxItemsSavedPerNavigation to prevent unbounded memory growth\n\n- Introduce protected maxItemsSavedPerNavigation in PageCollector\n- NetworkCollector sets to 5000, ConsoleCollector to 1000\n- Add pruning logic to current navigation array\n- Add test for pruning behavior\n\nFixes #1214. This addresses memory growth in autoConnect mode where collectors could accumulate unlimited items in a single navigation if the page never navigates but makes many requests/console events.
1 parent 9a47b65 commit 7c27470

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

src/PageCollector.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ export class PageCollector<T> {
6464
#listeners = new WeakMap<Page, ListenerMap>();
6565
protected maxNavigationSaved = 3;
6666

67+
/**
68+
* Max number of collected items to keep for the *current* navigation.
69+
*
70+
* This is a safety valve to prevent unbounded memory growth in long-running
71+
* sessions (e.g. autoConnect mode) on highly active pages.
72+
*/
73+
protected maxItemsSavedPerNavigation = Number.POSITIVE_INFINITY;
74+
6775
/**
6876
* This maps a Page to a list of navigations with a sub-list
6977
* of all collected resources.
@@ -134,7 +142,9 @@ export class PageCollector<T> {
134142
withId[stableIdSymbol] = idGenerator();
135143

136144
const navigations = this.storage.get(page) ?? [[]];
137-
navigations[0].push(withId);
145+
const current = navigations[0];
146+
current.push(withId);
147+
this.#pruneCurrentNavigation(current);
138148
});
139149

140150
listeners['framenavigated'] = (frame: Frame) => {
@@ -152,6 +162,20 @@ export class PageCollector<T> {
152162
this.#listeners.set(page, listeners);
153163
}
154164

165+
#pruneCurrentNavigation(navigation: Array<WithSymbolId<T>>) {
166+
const max = this.maxItemsSavedPerNavigation;
167+
if (!Number.isFinite(max)) {
168+
return;
169+
}
170+
if (max <= 0) {
171+
navigation.length = 0;
172+
return;
173+
}
174+
if (navigation.length > max) {
175+
navigation.splice(0, navigation.length - max);
176+
}
177+
}
178+
155179
protected splitAfterNavigation(page: Page) {
156180
const navigations = this.storage.get(page);
157181
if (!navigations) {
@@ -234,6 +258,8 @@ export class ConsoleCollector extends PageCollector<
234258
> {
235259
#subscribedPages = new WeakMap<Page, PageEventSubscriber>();
236260

261+
protected override maxItemsSavedPerNavigation = 1000;
262+
237263
override addPage(page: Page): void {
238264
super.addPage(page);
239265
if (!this.#subscribedPages.has(page)) {
@@ -372,6 +398,8 @@ class PageEventSubscriber {
372398
}
373399

374400
export class NetworkCollector extends PageCollector<HTTPRequest> {
401+
protected override maxItemsSavedPerNavigation = 5000;
402+
375403
constructor(
376404
browser: Browser,
377405
listeners: (

tests/PageCollector.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,37 @@ describe('PageCollector', () => {
185185
assert.equal(collector.getIdForResource(request1), 1);
186186
assert.equal(collector.getIdForResource(request2), 2);
187187
});
188+
189+
it('should prune current navigation to maxItemsSavedPerNavigation', async () => {
190+
const browser = getMockBrowser();
191+
const page = (await browser.pages())[0];
192+
193+
class LimitedCollector extends PageCollector<HTTPRequest> {
194+
protected override maxItemsSavedPerNavigation = 2;
195+
}
196+
197+
const request1 = getMockRequest({url: 'http://example.com/1'});
198+
const request2 = getMockRequest({url: 'http://example.com/2'});
199+
const request3 = getMockRequest({url: 'http://example.com/3'});
200+
201+
const collector = new LimitedCollector(browser, collect => {
202+
return {
203+
request: req => {
204+
collect(req);
205+
},
206+
} as ListenerMap;
207+
});
208+
await collector.init([page]);
209+
210+
page.emit('request', request1);
211+
page.emit('request', request2);
212+
page.emit('request', request3);
213+
214+
const data = collector.getData(page);
215+
assert.equal(data.length, 2);
216+
assert.equal(data[0], request2);
217+
assert.equal(data[1], request3);
218+
});
188219
});
189220

190221
describe('NetworkCollector', () => {

0 commit comments

Comments
 (0)