@@ -149,14 +149,15 @@ async def update_timeline(self):
149149 recent_reqs = sum (1 for req in self .completed_requests
150150 if now - req .get ("end_time" , 0 ) < 5 )
151151
152- # Browser counts (acquire lock to prevent race conditions)
153- from crawler_pool import PERMANENT , HOT_POOL , COLD_POOL , LOCK
154- async with LOCK :
155- browser_count = {
156- "permanent" : 1 if PERMANENT else 0 ,
157- "hot" : len (HOT_POOL ),
158- "cold" : len (COLD_POOL )
159- }
152+ # Browser counts — lock-free snapshot to avoid contending on
153+ # the pool LOCK held during slow browser start/close (issue #1754)
154+ from crawler_pool import get_pool_snapshot
155+ snap = get_pool_snapshot ()
156+ browser_count = {
157+ "permanent" : 1 if snap ["permanent" ] else 0 ,
158+ "hot" : len (snap ["hot_pool" ]),
159+ "cold" : len (snap ["cold_pool" ]),
160+ }
160161
161162 self .memory_timeline .append ({"time" : now , "value" : mem_pct })
162163 self .requests_timeline .append ({"time" : now , "value" : recent_reqs })
@@ -232,17 +233,17 @@ async def get_health_summary(self) -> Dict:
232233 # Network I/O (delta since last call)
233234 net = psutil .net_io_counters ()
234235
235- # Pool status (acquire lock to prevent race conditions )
236- from crawler_pool import PERMANENT , HOT_POOL , COLD_POOL , LOCK
237- async with LOCK :
238- # TODO: Track actual browser process memory instead of estimates
239- # These are conservative estimates based on typical Chromium usage
240- permanent_mem = 270 if PERMANENT else 0 # Estimate: ~270MB for permanent browser
241- hot_mem = len (HOT_POOL ) * 180 # Estimate: ~180MB per hot pool browser
242- cold_mem = len (COLD_POOL ) * 180 # Estimate: ~180MB per cold pool browser
243- permanent_active = PERMANENT is not None
244- hot_count = len (HOT_POOL )
245- cold_count = len (COLD_POOL )
236+ # Pool status — lock-free snapshot (issue #1754 )
237+ from crawler_pool import get_pool_snapshot
238+ snap = get_pool_snapshot ()
239+ # TODO: Track actual browser process memory instead of estimates
240+ # These are conservative estimates based on typical Chromium usage
241+ permanent_mem = 270 if snap [ "permanent" ] else 0 # Estimate: ~270MB for permanent browser
242+ hot_mem = len (snap [ "hot_pool" ] ) * 180 # Estimate: ~180MB per hot pool browser
243+ cold_mem = len (snap [ "cold_pool" ] ) * 180 # Estimate: ~180MB per cold pool browser
244+ permanent_active = snap [ "permanent" ] is not None
245+ hot_count = len (snap [ "hot_pool" ] )
246+ cold_count = len (snap [ "cold_pool" ] )
246247
247248 return {
248249 "container" : {
@@ -287,45 +288,52 @@ def get_completed_requests(self, limit: int = 50, filter_status: str = "all") ->
287288
288289 async def get_browser_list (self ) -> List [Dict ]:
289290 """Get detailed browser pool information."""
290- from crawler_pool import PERMANENT , HOT_POOL , COLD_POOL , LAST_USED , USAGE_COUNT , DEFAULT_CONFIG_SIG , LOCK
291+ from crawler_pool import get_pool_snapshot
291292
292293 browsers = []
293294 now = time .time ()
294295
295- # Acquire lock to prevent race conditions during iteration
296- async with LOCK :
297- if PERMANENT :
298- browsers .append ({
299- "type" : "permanent" ,
300- "sig" : DEFAULT_CONFIG_SIG [:8 ] if DEFAULT_CONFIG_SIG else "unknown" ,
301- "age_seconds" : int (now - self .start_time ),
302- "last_used_seconds" : int (now - LAST_USED .get (DEFAULT_CONFIG_SIG , now )),
303- "memory_mb" : 270 ,
304- "hits" : USAGE_COUNT .get (DEFAULT_CONFIG_SIG , 0 ),
305- "killable" : False
306- })
307-
308- for sig , crawler in HOT_POOL .items ():
309- browsers .append ({
310- "type" : "hot" ,
311- "sig" : sig [:8 ],
312- "age_seconds" : int (now - self .start_time ), # Approximation
313- "last_used_seconds" : int (now - LAST_USED .get (sig , now )),
314- "memory_mb" : 180 , # Estimate
315- "hits" : USAGE_COUNT .get (sig , 0 ),
316- "killable" : True
317- })
318-
319- for sig , crawler in COLD_POOL .items ():
320- browsers .append ({
321- "type" : "cold" ,
322- "sig" : sig [:8 ],
323- "age_seconds" : int (now - self .start_time ),
324- "last_used_seconds" : int (now - LAST_USED .get (sig , now )),
325- "memory_mb" : 180 ,
326- "hits" : USAGE_COUNT .get (sig , 0 ),
327- "killable" : True
328- })
296+ # Lock-free snapshot — iterates over copies (issue #1754)
297+ snap = get_pool_snapshot ()
298+ permanent = snap ["permanent" ]
299+ permanent_sig = snap ["permanent_sig" ]
300+ hot_pool = snap ["hot_pool" ]
301+ cold_pool = snap ["cold_pool" ]
302+ last_used = snap ["last_used" ]
303+ usage_count = snap ["usage_count" ]
304+
305+ if permanent :
306+ browsers .append ({
307+ "type" : "permanent" ,
308+ "sig" : permanent_sig [:8 ] if permanent_sig else "unknown" ,
309+ "age_seconds" : int (now - self .start_time ),
310+ "last_used_seconds" : int (now - last_used .get (permanent_sig , now )),
311+ "memory_mb" : 270 ,
312+ "hits" : usage_count .get (permanent_sig , 0 ),
313+ "killable" : False
314+ })
315+
316+ for sig , crawler in hot_pool .items ():
317+ browsers .append ({
318+ "type" : "hot" ,
319+ "sig" : sig [:8 ],
320+ "age_seconds" : int (now - self .start_time ), # Approximation
321+ "last_used_seconds" : int (now - last_used .get (sig , now )),
322+ "memory_mb" : 180 , # Estimate
323+ "hits" : usage_count .get (sig , 0 ),
324+ "killable" : True
325+ })
326+
327+ for sig , crawler in cold_pool .items ():
328+ browsers .append ({
329+ "type" : "cold" ,
330+ "sig" : sig [:8 ],
331+ "age_seconds" : int (now - self .start_time ),
332+ "last_used_seconds" : int (now - last_used .get (sig , now )),
333+ "memory_mb" : 180 ,
334+ "hits" : usage_count .get (sig , 0 ),
335+ "killable" : True
336+ })
329337
330338 return browsers
331339
0 commit comments