Fix Ctrl+C handling - add timeouts to prevent blocking
- Cancel terminal read task in close() before sending SIGHUP - Add 2s timeout to terminal_session.wait() - Add 3s timeout to server _shutdown() to prevent hanging - Ensures clean exit even if child processes don't respond
This commit is contained in:
@@ -253,12 +253,19 @@ class LocalServer:
|
||||
async def _shutdown(self) -> None:
|
||||
# Set exit event first so main loop exits immediately
|
||||
self.exit_event.set()
|
||||
# Then clean up resources (best effort, don't block exit)
|
||||
for ws in list(self._websocket_connections.values()):
|
||||
|
||||
# Clean up resources with timeout (best effort, don't block exit)
|
||||
async def cleanup() -> None:
|
||||
for ws in list(self._websocket_connections.values()):
|
||||
with contextlib.suppress(Exception):
|
||||
await ws.close()
|
||||
with contextlib.suppress(Exception):
|
||||
await ws.close()
|
||||
with contextlib.suppress(Exception):
|
||||
await self.session_manager.close_all()
|
||||
await self.session_manager.close_all()
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(cleanup(), timeout=3.0)
|
||||
except TimeoutError:
|
||||
log.warning("Shutdown timed out, forcing exit")
|
||||
|
||||
async def _run_local_server(self) -> None:
|
||||
app = web.Application()
|
||||
|
||||
@@ -262,6 +262,9 @@ class TerminalSession(Session):
|
||||
return True
|
||||
|
||||
async def close(self) -> None:
|
||||
# Cancel the read task first to unblock any waiting queue.get()
|
||||
if self._task is not None and not self._task.done():
|
||||
self._task.cancel()
|
||||
if self.pid is not None:
|
||||
try:
|
||||
os.kill(self.pid, signal.SIGHUP)
|
||||
@@ -270,10 +273,10 @@ class TerminalSession(Session):
|
||||
except Exception as e:
|
||||
log.warning("Error closing terminal session %s: %s", self.session_id, e)
|
||||
|
||||
async def wait(self) -> None:
|
||||
async def wait(self, timeout: float = 2.0) -> None:
|
||||
if self._task is not None:
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await self._task
|
||||
with contextlib.suppress(asyncio.CancelledError, TimeoutError):
|
||||
await asyncio.wait_for(asyncio.shield(self._task), timeout=timeout)
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""Check if the terminal session is still running."""
|
||||
|
||||
Reference in New Issue
Block a user