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:
|
async def _shutdown(self) -> None:
|
||||||
# Set exit event first so main loop exits immediately
|
# Set exit event first so main loop exits immediately
|
||||||
self.exit_event.set()
|
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):
|
with contextlib.suppress(Exception):
|
||||||
await ws.close()
|
await self.session_manager.close_all()
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
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:
|
async def _run_local_server(self) -> None:
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
|
|||||||
@@ -262,6 +262,9 @@ class TerminalSession(Session):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def close(self) -> None:
|
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:
|
if self.pid is not None:
|
||||||
try:
|
try:
|
||||||
os.kill(self.pid, signal.SIGHUP)
|
os.kill(self.pid, signal.SIGHUP)
|
||||||
@@ -270,10 +273,10 @@ class TerminalSession(Session):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning("Error closing terminal session %s: %s", self.session_id, 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:
|
if self._task is not None:
|
||||||
with contextlib.suppress(asyncio.CancelledError):
|
with contextlib.suppress(asyncio.CancelledError, TimeoutError):
|
||||||
await self._task
|
await asyncio.wait_for(asyncio.shield(self._task), timeout=timeout)
|
||||||
|
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
"""Check if the terminal session is still running."""
|
"""Check if the terminal session is still running."""
|
||||||
|
|||||||
Reference in New Issue
Block a user