增强转图浏览器保活:截图后探活并自动重建,增加后台心跳巡检
This commit is contained in:
@@ -521,6 +521,8 @@ class _PersistentBrowser:
|
|||||||
self._last_launch_source = "unknown"
|
self._last_launch_source = "unknown"
|
||||||
# 记录当前常驻浏览器所属事件循环,避免跨 loop 复用导致的句柄异常。
|
# 记录当前常驻浏览器所属事件循环,避免跨 loop 复用导致的句柄异常。
|
||||||
self._owner_loop_id: Optional[int] = None
|
self._owner_loop_id: Optional[int] = None
|
||||||
|
# 保活心跳任务:定期探测浏览器连通性,异常时自动重建。
|
||||||
|
self._heartbeat_task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
async def _launch_browser(self):
|
async def _launch_browser(self):
|
||||||
if self._playwright is None:
|
if self._playwright is None:
|
||||||
@@ -576,6 +578,7 @@ class _PersistentBrowser:
|
|||||||
f"[md2img] 常驻浏览器就绪: source={self._last_launch_source}, "
|
f"[md2img] 常驻浏览器就绪: source={self._last_launch_source}, "
|
||||||
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
||||||
)
|
)
|
||||||
|
self._ensure_heartbeat_task()
|
||||||
return self._browser
|
return self._browser
|
||||||
|
|
||||||
async def restart_browser(self):
|
async def restart_browser(self):
|
||||||
@@ -593,8 +596,41 @@ class _PersistentBrowser:
|
|||||||
f"[md2img] 常驻浏览器已重建: source={self._last_launch_source}, "
|
f"[md2img] 常驻浏览器已重建: source={self._last_launch_source}, "
|
||||||
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
f"loop={self._owner_loop_id}, pid={browser_pid}"
|
||||||
)
|
)
|
||||||
|
self._ensure_heartbeat_task()
|
||||||
return self._browser
|
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):
|
async def screenshot(self, html_content: str, output_image: str):
|
||||||
browser = await self.ensure_browser()
|
browser = await self.ensure_browser()
|
||||||
|
|
||||||
@@ -619,6 +655,11 @@ class _PersistentBrowser:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await _capture_with_browser(browser)
|
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:
|
except Exception as e:
|
||||||
# 首次失败后重建一次浏览器再重试,提升抗偶发故障能力。
|
# 首次失败后重建一次浏览器再重试,提升抗偶发故障能力。
|
||||||
logger.warning(f"[md2img] 常驻浏览器截图失败,准备重建后重试: {e}")
|
logger.warning(f"[md2img] 常驻浏览器截图失败,准备重建后重试: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user