Skip to content

Commit a274c02

Browse files
committed
timeouts
1 parent 15719dc commit a274c02

File tree

2 files changed

+29
-27
lines changed

2 files changed

+29
-27
lines changed

stagehand/context.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import os
23
import weakref
34

@@ -85,20 +86,21 @@ async def init(cls, context: BrowserContext, stagehand):
8586

8687
# Add event listener for new pages (popups, new tabs from window.open, etc.)
8788
def handle_page_event(pw_page):
88-
instance._handle_new_page(pw_page)
89+
# Playwright expects sync handler, so we schedule the async work
90+
asyncio.create_task(instance._handle_new_page(pw_page))
8991

9092
context.on("page", handle_page_event)
9193

9294
return instance
9395

94-
def _handle_new_page(self, pw_page: Page):
96+
async def _handle_new_page(self, pw_page: Page):
9597
"""
9698
Handle new pages created by the browser (popups, window.open, etc.).
97-
This runs synchronously in the event handler context.
99+
Uses the page switch lock to prevent race conditions with ongoing operations.
98100
"""
99-
100-
async def _async_handle():
101-
try:
101+
try:
102+
# Use timeout to prevent indefinite blocking
103+
async with asyncio.timeout(30):
102104
async with self.stagehand._page_switch_lock:
103105
self.stagehand.logger.debug(
104106
f"Creating StagehandPage for new page with URL: {pw_page.url}",
@@ -109,26 +111,14 @@ async def _async_handle():
109111
self.stagehand.logger.debug(
110112
"New page detected and initialized", category="context"
111113
)
112-
except Exception as e:
113-
self.stagehand.logger.error(
114-
f"Failed to initialize new page: {str(e)}", category="context"
115-
)
116-
import traceback
117-
118-
self.stagehand.logger.error(
119-
f"Traceback: {traceback.format_exc()}", category="context"
120-
)
121-
122-
# Schedule the async work
123-
import asyncio
124-
125-
try:
126-
loop = asyncio.get_running_loop()
127-
loop.create_task(_async_handle())
128-
except RuntimeError:
129-
# No event loop running, which shouldn't happen in normal operation
114+
except asyncio.TimeoutError:
115+
self.stagehand.logger.error(
116+
f"Timeout waiting for page switch lock when handling new page: {pw_page.url}",
117+
category="context",
118+
)
119+
except Exception as e:
130120
self.stagehand.logger.error(
131-
"No event loop available to handle new page", category="context"
121+
f"Failed to initialize new page: {str(e)}", category="context"
132122
)
133123

134124
def __getattr__(self, name):

stagehand/main.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,20 @@ def __init__(self, stagehand_instance):
4646
async def _ensure_page_stability(self):
4747
"""Wait for any pending page switches to complete"""
4848
if hasattr(self._stagehand, "_page_switch_lock"):
49-
async with self._stagehand._page_switch_lock:
50-
pass # Just wait for any ongoing switches
49+
try:
50+
# Use timeout to prevent indefinite blocking
51+
async with asyncio.timeout(30):
52+
async with self._stagehand._page_switch_lock:
53+
pass # Just wait for any ongoing switches
54+
except asyncio.TimeoutError:
55+
# Log the timeout and raise to let caller handle it
56+
if hasattr(self._stagehand, "logger"):
57+
self._stagehand.logger.error(
58+
"Timeout waiting for page stability lock", category="live_proxy"
59+
)
60+
raise RuntimeError from asyncio.TimeoutError(
61+
"Page stability lock timeout - possible deadlock detected"
62+
)
5163

5264
def __getattr__(self, name):
5365
"""Delegate all attribute access to the current active page."""

0 commit comments

Comments
 (0)