Skip to content

fix(daemon): fire-and-forget mark-title eval so load events don't stall#171

Open
mvanhorn wants to merge 1 commit intobrowser-use:mainfrom
mvanhorn:fix/136-tap-handler-blocking-await
Open

fix(daemon): fire-and-forget mark-title eval so load events don't stall#171
mvanhorn wants to merge 1 commit intobrowser-use:mainfrom
mvanhorn:fix/136-tap-handler-blocking-await

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

@mvanhorn mvanhorn commented Apr 23, 2026

Per #136, the tap handler awaits Runtime.evaluate on both Page.loadEventFired and Page.domContentEventFired. Because cdp-use dispatches events one at a time, the await on domContentEventFired holds up loadEventFired in the queue, and Chrome's V8 thread is busy so the eval doesn't return in time. The user's next js(...) then queues behind the second eval on V8. Two ~2s waits compound per navigation.

Reporter measured the median iteration time at 4.595s with the current double-await, dropping to 1.060s with fire-and-forget. Playwright baseline on the same browser is ~0.9s.

Change

Module-level _silent(coro) wrapper preserves the current except Exception: pass behavior (the set_session branch at line 172 has the same pattern around the same send_raw call, and the generic handle() path explicitly handles "Session with given id not found", so the existing suppression is load-bearing, not cosmetic). The loadEventFired / domContentEventFired branch uses asyncio.create_task(_silent(...)) to fire-and-forget. The title mark still lands a moment later, just off the critical path.

Scope

  • daemon.py only, +8 / -2
  • mark_js definition unchanged
  • set_session branch at line 172 intentionally NOT touched (runs per session-switch, not per navigation)
  • No test suite in repo, verified with python3 -m py_compile daemon.py

Fixes #136


Summary by cubic

Make the mark-title eval fire-and-forget in daemon.py so Page.loadEventFired / Page.domContentEventFired don’t stall the event queue. This removes ~2s per navigation delay and drops median iteration time from ~4.6s to ~1.06s. Fixes #136.

  • Bug Fixes
    • Replace awaited Runtime.evaluate with asyncio.create_task(_silent(...)) in the tap handler for load/DOMContent events.
    • Preserve prior exception suppression via _silent; mark_js and the set_session path remain unchanged.

Written for commit 742fc8a. Summary will update on new commits.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="daemon.py">

<violation number="1" location="daemon.py:164">
P2: Fire-and-forget `Runtime.evaluate` on load events removed timeout bounding, allowing pending tasks to accumulate indefinitely when CDP calls stall.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread daemon.py
elif method in ("Page.loadEventFired", "Page.domContentEventFired"):
try: await asyncio.wait_for(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session), timeout=2)
except Exception: pass
asyncio.create_task(_silent(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session)))
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Fire-and-forget Runtime.evaluate on load events removed timeout bounding, allowing pending tasks to accumulate indefinitely when CDP calls stall.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At daemon.py, line 164:

<comment>Fire-and-forget `Runtime.evaluate` on load events removed timeout bounding, allowing pending tasks to accumulate indefinitely when CDP calls stall.</comment>

<file context>
@@ -154,8 +161,7 @@ async def tap(method, params, session_id=None):
             elif method in ("Page.loadEventFired", "Page.domContentEventFired"):
-                try: await asyncio.wait_for(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session), timeout=2)
-                except Exception: pass
+                asyncio.create_task(_silent(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session)))
             return await orig(method, params, session_id)
         self.cdp._event_registry.handle_event = tap
</file context>
Suggested change
asyncio.create_task(_silent(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session)))
asyncio.create_task(_silent(asyncio.wait_for(self.cdp.send_raw("Runtime.evaluate", {"expression": mark_js}, session_id=self.session), timeout=2)))
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tap handler in daemon.py blocks ~2–4s per navigation on await Runtime.evaluate

1 participant