增强转图浏览器保活:截图后探活并自动重建,增加后台心跳巡检
This commit is contained in:
@@ -521,6 +521,8 @@ class _PersistentBrowser:
|
||||
self._last_launch_source = "unknown"
|
||||
# 记录当前常驻浏览器所属事件循环,避免跨 loop 复用导致的句柄异常。
|
||||
self._owner_loop_id: Optional[int] = None
|
||||
# 保活心跳任务:定期探测浏览器连通性,异常时自动重建。
|
||||
self._heartbeat_task: Optional[asyncio.Task] = None
|
||||
|
||||
async def _launch_browser(self):
|
||||
if self._playwright is None:
|
||||
@@ -576,6 +578,7 @@ class _PersistentBrowser:
|
||||
f"[md2img] 常驻浏览器就绪: source={self._last_launch_source}, "
|
||||
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
||||
)
|
||||
self._ensure_heartbeat_task()
|
||||
return self._browser
|
||||
|
||||
async def restart_browser(self):
|
||||
@@ -593,8 +596,41 @@ class _PersistentBrowser:
|
||||
f"[md2img] 常驻浏览器已重建: source={self._last_launch_source}, "
|
||||
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
||||
)
|
||||
self._ensure_heartbeat_task()
|
||||
return self._browser
|
||||
|
||||
async def _is_browser_alive(self, browser, timeout_seconds: float = 3.0) -> bool:
|
||||
"""探测浏览器是否仍可用。"""
|
||||
if not browser or not browser.is_connected():
|
||||
return False
|
||||
try:
|
||||
await asyncio.wait_for(browser.version(), timeout=timeout_seconds)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def _heartbeat_loop(self):
|
||||
"""周期性探测浏览器可用性,断连后自动重建。"""
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(10)
|
||||
# 没有浏览器实例时只保持心跳存活,不主动创建,避免空闲时不必要消耗。
|
||||
if not self._browser:
|
||||
continue
|
||||
if not await self._is_browser_alive(self._browser, timeout_seconds=2.0):
|
||||
logger.warning("[md2img] 心跳探测发现浏览器已断连,准备自动重建")
|
||||
await self.restart_browser()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.warning(f"[md2img] 心跳探测异常: {e}")
|
||||
|
||||
def _ensure_heartbeat_task(self):
|
||||
"""确保保活任务已启动(幂等)。"""
|
||||
if self._heartbeat_task and not self._heartbeat_task.done():
|
||||
return
|
||||
self._heartbeat_task = asyncio.create_task(self._heartbeat_loop(), name="md2img:heartbeat")
|
||||
|
||||
async def screenshot(self, html_content: str, output_image: str):
|
||||
browser = await self.ensure_browser()
|
||||
|
||||
@@ -619,6 +655,11 @@ class _PersistentBrowser:
|
||||
|
||||
try:
|
||||
await _capture_with_browser(browser)
|
||||
# 截图完成后立刻做一次可用性探测。
|
||||
# 在部分系统环境中,浏览器可能在任务完成后迅速断连,这里主动重建保证“常驻”语义。
|
||||
if not await self._is_browser_alive(browser, timeout_seconds=2.0):
|
||||
logger.warning("[md2img] 截图后浏览器已断连,立即执行自动重建")
|
||||
await self.restart_browser()
|
||||
except Exception as e:
|
||||
# 首次失败后重建一次浏览器再重试,提升抗偶发故障能力。
|
||||
logger.warning(f"[md2img] 常驻浏览器截图失败,准备重建后重试: {e}")
|
||||
|
||||
Reference in New Issue
Block a user