@crawlee/browser-pool 3.18.2-beta.3 → 3.18.2-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,7 @@ export declare abstract class BrowserController<Library extends CommonLibrary =
42
42
  isActive: boolean;
43
43
  activePages: number;
44
44
  private readonly closedPages;
45
+ private readonly pageTeardowns;
45
46
  totalPages: number;
46
47
  lastPageOpenedAt: number;
47
48
  private _activate;
@@ -82,9 +83,19 @@ export declare abstract class BrowserController<Library extends CommonLibrary =
82
83
  * Accounts for a page no longer being open. Callable from either side and safe to call twice:
83
84
  * the page's own `close` event, or the pool when it gives up on a close that never settles.
84
85
  * Tracked in a `WeakSet` rather than via `isClosed()`, because that reports the same event.
85
- * @ignore
86
+ * @internal
86
87
  */
87
88
  registerPageClosed(page: NewPageResult): void;
89
+ /**
90
+ * Registers cleanup belonging to a single page: an anonymizing proxy server, an incognito
91
+ * context. It runs from `registerPageClosed`, so it happens whether the page closed on its own
92
+ * or the pool gave up on a close that never settled, and it runs at most once.
93
+ *
94
+ * Hanging it off the page's `close` event instead leaks it in exactly the case this controller
95
+ * now handles, because that event does not arrive for a page the browser never destroyed.
96
+ * @ignore
97
+ */
98
+ protected registerPageTeardown(page: NewPageResult, teardown: () => Promise<void>): void;
88
99
  setCookies(page: NewPageResult, cookies: Cookie[]): Promise<void>;
89
100
  getCookies(page: NewPageResult): Promise<Cookie[]>;
90
101
  /**
@@ -88,6 +88,12 @@ class BrowserController extends tiny_typed_emitter_1.TypedEmitter {
88
88
  writable: true,
89
89
  value: new WeakSet()
90
90
  });
91
+ Object.defineProperty(this, "pageTeardowns", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: new WeakMap()
96
+ });
91
97
  Object.defineProperty(this, "totalPages", {
92
98
  enumerable: true,
93
99
  configurable: true,
@@ -205,13 +211,32 @@ class BrowserController extends tiny_typed_emitter_1.TypedEmitter {
205
211
  * Accounts for a page no longer being open. Callable from either side and safe to call twice:
206
212
  * the page's own `close` event, or the pool when it gives up on a close that never settles.
207
213
  * Tracked in a `WeakSet` rather than via `isClosed()`, because that reports the same event.
208
- * @ignore
214
+ * @internal
209
215
  */
210
216
  registerPageClosed(page) {
211
217
  if (this.closedPages.has(page))
212
218
  return;
213
219
  this.closedPages.add(page);
214
220
  this.activePages--;
221
+ const teardown = this.pageTeardowns.get(page);
222
+ if (!teardown)
223
+ return;
224
+ this.pageTeardowns.delete(page);
225
+ teardown().catch((error) => {
226
+ logger_1.log.debug(`Could not clean up after a closed page.\nCause:${error.message}`, { id: this.id });
227
+ });
228
+ }
229
+ /**
230
+ * Registers cleanup belonging to a single page: an anonymizing proxy server, an incognito
231
+ * context. It runs from `registerPageClosed`, so it happens whether the page closed on its own
232
+ * or the pool gave up on a close that never settled, and it runs at most once.
233
+ *
234
+ * Hanging it off the page's `close` event instead leaks it in exactly the case this controller
235
+ * now handles, because that event does not arrive for a page the browser never destroyed.
236
+ * @ignore
237
+ */
238
+ registerPageTeardown(page, teardown) {
239
+ this.pageTeardowns.set(page, teardown);
215
240
  }
216
241
  async setCookies(page, cookies) {
217
242
  return this._setCookies(page, cookies);
package/browser-pool.js CHANGED
@@ -604,7 +604,7 @@ class BrowserPool extends tiny_typed_emitter_1.TypedEmitter {
604
604
  let timeout;
605
605
  const finished = await Promise.race([
606
606
  closing.then(() => true, (err) => {
607
- logger_1.log.warning(`Page close hook failed.\nCause:${err.message}`, {
607
+ logger_1.log.warning(`Closing a page failed, releasing it from the pool anyway.\nCause:${err.message}`, {
608
608
  id: browserController.id,
609
609
  pageId,
610
610
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/browser-pool",
3
- "version": "3.18.2-beta.3",
3
+ "version": "3.18.2-beta.5",
4
4
  "description": "Rotate multiple browsers using popular automation libraries such as Playwright or Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=16.0.0"
@@ -38,8 +38,8 @@
38
38
  "dependencies": {
39
39
  "@apify/log": "^2.4.0",
40
40
  "@apify/timeout": "^0.4.0",
41
- "@crawlee/core": "3.18.2-beta.3",
42
- "@crawlee/types": "3.18.2-beta.3",
41
+ "@crawlee/core": "3.18.2-beta.5",
42
+ "@crawlee/types": "3.18.2-beta.5",
43
43
  "fingerprint-generator": "^2.1.68",
44
44
  "fingerprint-injector": "^2.1.68",
45
45
  "lodash.merge": "^4.6.2",
@@ -70,5 +70,5 @@
70
70
  }
71
71
  }
72
72
  },
73
- "gitHead": "feed328453d1967b36ccd45cc4531d44498915b8"
73
+ "gitHead": "d126d83399f7aa71bd0920d6a50b6bb9e6373790"
74
74
  }
@@ -49,9 +49,9 @@ class PlaywrightController extends browser_controller_1.BrowserController {
49
49
  }
50
50
  try {
51
51
  const page = await this.browser.newPage(contextOptions);
52
- page.once('close', async () => {
52
+ this.registerPageTeardown(page, close);
53
+ page.once('close', () => {
53
54
  this.registerPageClosed(page);
54
- await close();
55
55
  });
56
56
  if (this.launchContext.experimentalContainers) {
57
57
  await page.goto('data:text/plain,tabid');
@@ -55,17 +55,18 @@ class PuppeteerController extends browser_controller_1.BrowserController {
55
55
  tryCancel();
56
56
  }
57
57
  */
58
- page.once('close', async () => {
59
- this.registerPageClosed(page);
60
- try {
61
- await context.close();
62
- }
63
- catch (error) {
58
+ this.registerPageTeardown(page, async () => {
59
+ // The proxy server is not chained behind the context close: that can hang on
60
+ // the same target the page hung on, and the proxy runs in this process, so it
61
+ // has to be reclaimed either way.
62
+ const contextClosed = context.close().catch((error) => {
64
63
  logger_1.log.exception(error, 'Failed to close context.');
65
- }
66
- finally {
67
- await close();
68
- }
64
+ });
65
+ await close();
66
+ await contextClosed;
67
+ });
68
+ page.once('close', () => {
69
+ this.registerPageClosed(page);
69
70
  });
70
71
  return page;
71
72
  }