22
33from __future__ import annotations
44
5+ from asyncio import Lock
56from datetime import datetime , timedelta , timezone
67from typing import TYPE_CHECKING , Any , cast
78
@@ -77,6 +78,19 @@ def __init__(
7778
7879 self ._total_opened_pages = 0
7980
81+ self ._context_creation_lock : Lock | None = None
82+
83+ async def _get_context_creation_lock (self ) -> Lock :
84+ """Get context checking and creation lock.
85+
86+ It should be done with lock to prevent multiple concurrent attempts to create context, which could lead to
87+ memory leak as one of the two concurrently created contexts will become orphaned and not properly closed.
88+ """
89+ if self ._context_creation_lock :
90+ return self ._context_creation_lock
91+ self ._context_creation_lock = Lock ()
92+ return self ._context_creation_lock
93+
8094 @property
8195 @override
8296 def pages (self ) -> list [Page ]:
@@ -137,12 +151,6 @@ async def new_page(
137151 Raises:
138152 ValueError: If the browser has reached the maximum number of open pages.
139153 """
140- if not self ._browser_context :
141- self ._browser_context = await self ._create_browser_context (
142- browser_new_context_options = browser_new_context_options ,
143- proxy_info = proxy_info ,
144- )
145-
146154 if not self .has_free_capacity :
147155 raise ValueError ('Cannot open more pages in this browser.' )
148156
@@ -154,11 +162,12 @@ async def new_page(
154162 )
155163 page = await new_context .new_page ()
156164 else :
157- if not self ._browser_context :
158- self ._browser_context = await self ._create_browser_context (
159- browser_new_context_options = browser_new_context_options ,
160- proxy_info = proxy_info ,
161- )
165+ async with await self ._get_context_creation_lock ():
166+ if not self ._browser_context :
167+ self ._browser_context = await self ._create_browser_context (
168+ browser_new_context_options = browser_new_context_options ,
169+ proxy_info = proxy_info ,
170+ )
162171 page = await self ._browser_context .new_page ()
163172
164173 # Handle page close event
@@ -169,7 +178,6 @@ async def new_page(
169178 self ._last_page_opened_at = datetime .now (timezone .utc )
170179
171180 self ._total_opened_pages += 1
172-
173181 return page
174182
175183 @override
@@ -206,7 +214,6 @@ async def _create_browser_context(
206214 `self._fingerprint_generator` is available.
207215 """
208216 browser_new_context_options = dict (browser_new_context_options ) if browser_new_context_options else {}
209-
210217 if proxy_info :
211218 if browser_new_context_options .get ('proxy' ):
212219 logger .warning ("browser_new_context_options['proxy'] overriden by explicit `proxy_info` argument." )
@@ -244,5 +251,4 @@ async def _create_browser_context(
244251 browser_new_context_options ['extra_http_headers' ] = browser_new_context_options .get (
245252 'extra_http_headers' , extra_http_headers
246253 )
247-
248254 return await self ._browser .new_context (** browser_new_context_options )
0 commit comments