Skip to content

Commit b191579

Browse files
committed
fixed signal handler
1 parent cd0686a commit b191579

File tree

1 file changed

+64
-8
lines changed

1 file changed

+64
-8
lines changed

stagehand/client.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def __init__(
187187
raise ValueError(
188188
"browserbase_project_id is required for BROWSERBASE env with existing session_id (or set BROWSERBASE_PROJECT_ID in env)."
189189
)
190+
191+
# Register signal handlers for graceful shutdown
192+
self._register_signal_handlers()
190193

191194
self._client: Optional[httpx.AsyncClient] = (
192195
None # Used for server communication in BROWSERBASE
@@ -213,6 +216,61 @@ def __init__(
213216
**self.model_client_options,
214217
)
215218

219+
def _register_signal_handlers(self):
220+
"""Register signal handlers for SIGINT and SIGTERM to ensure proper cleanup."""
221+
def cleanup_handler(sig, frame):
222+
# Prevent multiple cleanup calls
223+
if self.__class__._cleanup_called:
224+
return
225+
226+
self.__class__._cleanup_called = True
227+
print(f"\n[{signal.Signals(sig).name}] received. Ending Browserbase session...")
228+
229+
try:
230+
# Try to get the current event loop
231+
try:
232+
loop = asyncio.get_running_loop()
233+
except RuntimeError:
234+
# No event loop running - create one to run cleanup
235+
print("No event loop running, creating one for cleanup...")
236+
try:
237+
asyncio.run(self._async_cleanup())
238+
except Exception as e:
239+
print(f"Error during cleanup: {str(e)}")
240+
finally:
241+
sys.exit(0)
242+
return
243+
244+
# Schedule cleanup in the existing event loop
245+
# Use call_soon_threadsafe since signal handlers run in a different thread context
246+
def schedule_cleanup():
247+
task = asyncio.create_task(self._async_cleanup())
248+
# Shield the task to prevent it from being cancelled
249+
shielded = asyncio.shield(task)
250+
# We don't need to await here since we're in call_soon_threadsafe
251+
252+
loop.call_soon_threadsafe(schedule_cleanup)
253+
254+
except Exception as e:
255+
print(f"Error during signal cleanup: {str(e)}")
256+
sys.exit(1)
257+
258+
# Register signal handlers
259+
signal.signal(signal.SIGINT, cleanup_handler)
260+
signal.signal(signal.SIGTERM, cleanup_handler)
261+
262+
async def _async_cleanup(self):
263+
"""Async cleanup method called from signal handler."""
264+
try:
265+
await self.close()
266+
print(f"Session {self.session_id} ended successfully")
267+
except Exception as e:
268+
print(f"Error ending Browserbase session: {str(e)}")
269+
finally:
270+
# Force exit after cleanup completes (or fails)
271+
# Use os._exit to avoid any further Python cleanup that might hang
272+
os._exit(0)
273+
216274
def start_inference_timer(self):
217275
"""Start timer for tracking inference time."""
218276
self._inference_start_time = time.time()
@@ -627,15 +685,12 @@ async def close(self):
627685
self.logger.debug(
628686
f"Attempting to end server session {self.session_id}..."
629687
)
630-
# Use internal client if httpx_client wasn't provided externally
631-
client_to_use = (
632-
self._client if not self.httpx_client else self.httpx_client
688+
# Don't use async with here as it might close the client prematurely
689+
# The _execute method will handle the request properly
690+
result = await self._execute("end", {"sessionId": self.session_id})
691+
self.logger.debug(
692+
f"Server session {self.session_id} ended successfully with result: {result}"
633693
)
634-
async with client_to_use: # Ensure client context is managed
635-
await self._execute("end", {"sessionId": self.session_id})
636-
self.logger.debug(
637-
f"Server session {self.session_id} ended successfully"
638-
)
639694
except Exception as e:
640695
# Log error but continue cleanup
641696
self.logger.error(
@@ -684,6 +739,7 @@ async def close(self):
684739
self.logger.error(f"Error stopping Playwright: {str(e)}")
685740

686741
self._closed = True
742+
self.logger.debug("All resources closed successfully")
687743

688744
async def _create_session(self):
689745
"""

0 commit comments

Comments
 (0)