增强转图浏览器保活:截图后探活并自动重建,增加后台心跳巡检

This commit is contained in:
liuwei
2026-04-17 10:14:04 +08:00
parent 2a87863d9c
commit 97bc4560b6

View File

@@ -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}")