Skip to content

Commit faf8de8

Browse files
committed
fix(backend): avoid event loop blocking and treat CDP timeout as success
Use run_in_executor for blocking readline so the event loop stays responsive during the CDP readiness check. Treat the timeout case (process alive, CDP slow) as success since the browser window is already open for the user. Only report failure when the process actually crashes.
1 parent f3b81d0 commit faf8de8

1 file changed

Lines changed: 22 additions & 23 deletions

File tree

backend/app/controller/tool_controller.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def _wait_for_cdp_ready(
5050
timeout: float = 10,
5151
interval: float = 0.3,
5252
) -> bool:
53-
r"""Poll until CDP port is open or process exits. Returns True if ready."""
53+
"""Poll until CDP port is open or process exits. Returns True if ready."""
5454
elapsed = 0.0
5555
while elapsed < timeout:
5656
if process.poll() is not None:
@@ -783,11 +783,18 @@ def is_port_in_use(port):
783783

784784
_login_browser_process = process
785785

786-
# Create async task to log Electron output
786+
# Log Electron output in a background task.
787+
# readline() is blocking, so run it in an executor
788+
# to avoid stalling the event loop.
787789
async def log_electron_output():
788-
for line in iter(process.stdout.readline, ""):
789-
if line:
790-
logger.info(f"[ELECTRON OUTPUT] {line.strip()}")
790+
loop = asyncio.get_event_loop()
791+
while True:
792+
line = await loop.run_in_executor(
793+
None, process.stdout.readline
794+
)
795+
if not line:
796+
break
797+
logger.info(f"[ELECTRON OUTPUT] {line.strip()}")
791798

792799
asyncio.create_task(log_electron_output())
793800

@@ -810,23 +817,15 @@ async def log_electron_output():
810817
" before CDP became ready."
811818
f" Exit code: {exit_code}",
812819
}
813-
else:
814-
# Timeout — process is still alive but CDP
815-
# port never opened. Keep tracking the process
816-
# so /browser/status still reports it.
817-
logger.warning(
818-
"[PROFILE USER LOGIN] CDP port"
819-
f" {cdp_port} not ready after"
820-
" timeout, but process"
821-
f" {process.pid} is still running"
822-
)
823-
return {
824-
"success": False,
825-
"error": "Browser started but CDP"
826-
f" port {cdp_port} did not become"
827-
" available in time.",
828-
"pid": process.pid,
829-
}
820+
# Timeout — process is alive but CDP port not
821+
# ready yet. The browser window is open so the
822+
# user can proceed with login.
823+
logger.warning(
824+
"[PROFILE USER LOGIN] CDP port"
825+
f" {cdp_port} not ready after"
826+
" timeout, but process"
827+
f" {process.pid} is still running"
828+
)
830829

831830
logger.info(
832831
"[PROFILE USER LOGIN] Electron"
@@ -860,7 +859,7 @@ async def log_electron_output():
860859

861860
@router.get("/browser/status", name="browser status")
862861
async def browser_status():
863-
r"""Check if the login browser is still running."""
862+
"""Check if the login browser is still running."""
864863
global _login_browser_process
865864
if _login_browser_process is None:
866865
return {"is_open": False}

0 commit comments

Comments
 (0)