浏览器重启逻辑
This commit is contained in:
@@ -32,6 +32,10 @@ class BrowserPool:
|
||||
self._startup_error: str | None = None
|
||||
self._retained_contexts: list[Any] = []
|
||||
self._retained_pages: list[Any] = []
|
||||
# 主动回收相关计数:累计创建的上下文数、本次浏览器启动时刻、当前在用上下文数
|
||||
self._contexts_since_start = 0
|
||||
self._browser_started_at: float | None = None
|
||||
self._active_contexts = 0
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
@@ -95,6 +99,8 @@ class BrowserPool:
|
||||
)
|
||||
self._started = True
|
||||
self._startup_error = None
|
||||
self._contexts_since_start = 0
|
||||
self._browser_started_at = asyncio.get_running_loop().time()
|
||||
logger.info("浏览器启动完成")
|
||||
except Exception as exc:
|
||||
self._playwright = None
|
||||
@@ -163,6 +169,51 @@ class BrowserPool:
|
||||
|
||||
await self._restart_browser("browser is disconnected before acquiring page session")
|
||||
|
||||
def _recycle_due(self) -> bool:
|
||||
"""判断浏览器是否达到主动回收阈值(累计上下文数或运行时长任一超限)。"""
|
||||
max_contexts = self._settings.browser_recycle_after_contexts
|
||||
if max_contexts > 0 and self._contexts_since_start >= max_contexts:
|
||||
return True
|
||||
|
||||
max_age = self._settings.browser_recycle_after_seconds
|
||||
if max_age > 0 and self._browser_started_at is not None:
|
||||
elapsed = asyncio.get_running_loop().time() - self._browser_started_at
|
||||
if elapsed >= max_age:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _maybe_recycle(self) -> None:
|
||||
"""达到回收阈值且当前无在用上下文时,主动重建浏览器以释放累积内存。
|
||||
|
||||
采用「空闲时回收」策略:若仍有其他会话在使用上下文,则跳过本次,
|
||||
交由后续空闲时机处理,避免关闭浏览器时打断进行中的请求。
|
||||
"""
|
||||
if not self._recycle_due():
|
||||
return
|
||||
|
||||
async with self._restart_lock:
|
||||
# 持锁后二次确认,避免与并发回收/重建重复执行
|
||||
if not self._recycle_due():
|
||||
return
|
||||
if self._active_contexts > 0:
|
||||
logger.debug(
|
||||
"浏览器达到回收阈值但仍有 %s 个上下文在用,延迟回收",
|
||||
self._active_contexts,
|
||||
)
|
||||
return
|
||||
|
||||
logger.info(
|
||||
"浏览器达到回收阈值,主动重建:contexts=%s elapsed=%.0fs",
|
||||
self._contexts_since_start,
|
||||
0.0 if self._browser_started_at is None
|
||||
else asyncio.get_running_loop().time() - self._browser_started_at,
|
||||
)
|
||||
await self.close()
|
||||
await self.start()
|
||||
if not self._is_browser_connected():
|
||||
raise BrowserUnavailableError(self._startup_error or "Browser recycle failed")
|
||||
|
||||
@staticmethod
|
||||
def _should_retry_context_creation(exc: Exception) -> bool:
|
||||
"""识别 Playwright driver 断链类错误,允许执行一次重建后重试。"""
|
||||
@@ -240,7 +291,10 @@ class BrowserPool:
|
||||
|
||||
context = None
|
||||
page = None
|
||||
context_counted = False
|
||||
try:
|
||||
# 创建上下文前检查是否需要主动回收(仅在空闲时执行,不打断进行中的会话)
|
||||
await self._maybe_recycle()
|
||||
for attempt in range(2):
|
||||
try:
|
||||
context = await self._browser.new_context(
|
||||
@@ -258,6 +312,11 @@ class BrowserPool:
|
||||
continue
|
||||
raise
|
||||
|
||||
# 上下文创建成功,计入回收阈值统计并标记为在用
|
||||
self._contexts_since_start += 1
|
||||
self._active_contexts += 1
|
||||
context_counted = True
|
||||
|
||||
if self._settings.browser_stealth_enabled:
|
||||
try:
|
||||
from playwright_stealth import Stealth
|
||||
@@ -296,4 +355,6 @@ class BrowserPool:
|
||||
await page.close()
|
||||
if context is not None:
|
||||
await context.close()
|
||||
if context_counted:
|
||||
self._active_contexts -= 1
|
||||
self._semaphore.release()
|
||||
|
||||
Reference in New Issue
Block a user