2
2
3
3
from __future__ import annotations
4
4
5
+ from asyncio import Lock
5
6
from datetime import datetime , timedelta , timezone
6
7
from typing import TYPE_CHECKING , Any , cast
7
8
@@ -77,6 +78,19 @@ def __init__(
77
78
78
79
self ._total_opened_pages = 0
79
80
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
+
80
94
@property
81
95
@override
82
96
def pages (self ) -> list [Page ]:
@@ -137,12 +151,6 @@ async def new_page(
137
151
Raises:
138
152
ValueError: If the browser has reached the maximum number of open pages.
139
153
"""
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
-
146
154
if not self .has_free_capacity :
147
155
raise ValueError ('Cannot open more pages in this browser.' )
148
156
@@ -154,11 +162,12 @@ async def new_page(
154
162
)
155
163
page = await new_context .new_page ()
156
164
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
+ )
162
171
page = await self ._browser_context .new_page ()
163
172
164
173
# Handle page close event
@@ -169,7 +178,6 @@ async def new_page(
169
178
self ._last_page_opened_at = datetime .now (timezone .utc )
170
179
171
180
self ._total_opened_pages += 1
172
-
173
181
return page
174
182
175
183
@override
@@ -206,7 +214,6 @@ async def _create_browser_context(
206
214
`self._fingerprint_generator` is available.
207
215
"""
208
216
browser_new_context_options = dict (browser_new_context_options ) if browser_new_context_options else {}
209
-
210
217
if proxy_info :
211
218
if browser_new_context_options .get ('proxy' ):
212
219
logger .warning ("browser_new_context_options['proxy'] overriden by explicit `proxy_info` argument." )
@@ -244,5 +251,4 @@ async def _create_browser_context(
244
251
browser_new_context_options ['extra_http_headers' ] = browser_new_context_options .get (
245
252
'extra_http_headers' , extra_http_headers
246
253
)
247
-
248
254
return await self ._browser .new_context (** browser_new_context_options )
0 commit comments