fix: WebSSH terminal disconnect after connect + stale pool retry
- Fix shell creation failure on stale pooled SSH connections:
force-close and retry with fresh connection
- Fix empty error message in logs: use {type(e).__name__}: {e!r}
- Fix double WebSocket.close() causing RuntimeError in finally block:
track ws_closed flag to avoid closing twice
- Fix Alpine undefined error in terminal.html: $d() now returns
default object when Alpine hasn't initialized yet
- All websocket.close() calls now wrapped in try/except
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+57
-21
@@ -156,22 +156,56 @@ async def terminal_ws(
|
||||
|
||||
logger.info(f"WebSSH: admin={admin.username} connecting to server={server.name} ({server.domain})")
|
||||
|
||||
# ── Establish SSH connection ──
|
||||
# ── Establish SSH connection (with stale-connection retry) ──
|
||||
conn = None
|
||||
try:
|
||||
conn = await ssh_pool.acquire(server)
|
||||
except Exception as e:
|
||||
logger.error(f"WebSSH connection failed: {e}")
|
||||
await websocket.send_json({"type": MSG_ERROR, "message": f"SSH连接失败: {str(e)}"})
|
||||
await websocket.close(code=4003, reason="SSH connection failed")
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_ERROR, "message": f"SSH连接失败: {str(e)}"})
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await websocket.close(code=4003, reason="SSH connection failed")
|
||||
except Exception:
|
||||
pass
|
||||
await _close_ssh_session(session_id)
|
||||
return
|
||||
|
||||
# ── Create SSH shell ──
|
||||
# ── Create SSH shell (retry once if stale pooled connection) ──
|
||||
ws_closed = False # Track whether we've closed the WebSocket
|
||||
try:
|
||||
async with conn.create_process(
|
||||
term_type="xterm-256color",
|
||||
term_size=(24, 80),
|
||||
) as shell:
|
||||
try:
|
||||
shell_proc = conn.create_process(
|
||||
term_type="xterm-256color",
|
||||
term_size=(24, 80),
|
||||
)
|
||||
except Exception as e:
|
||||
# Stale pooled connection — force-close and retry with fresh connection
|
||||
logger.warning(f"WebSSH shell creation failed on pooled connection (server={server.id}): {type(e).__name__}: {e!r}")
|
||||
await ssh_pool.close_connection(server.id)
|
||||
try:
|
||||
conn = await ssh_pool.acquire(server)
|
||||
shell_proc = conn.create_process(
|
||||
term_type="xterm-256color",
|
||||
term_size=(24, 80),
|
||||
)
|
||||
except Exception as e2:
|
||||
logger.error(f"WebSSH shell creation failed on fresh connection (server={server.id}): {type(e2).__name__}: {e2!r}")
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_ERROR, "message": f"Shell创建失败: {type(e2).__name__}"})
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await websocket.close(code=4003, reason="Shell creation failed")
|
||||
except Exception:
|
||||
pass
|
||||
ws_closed = True
|
||||
await _close_ssh_session(session_id)
|
||||
return
|
||||
|
||||
async with shell_proc as shell:
|
||||
# Send TERMINAL_INIT to client
|
||||
await websocket.send_json({
|
||||
"type": MSG_TERMINAL_INIT,
|
||||
@@ -267,25 +301,27 @@ async def terminal_ws(
|
||||
logger.warning(f"WebSSH relay error: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WebSSH shell creation failed: {e}")
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_ERROR, "message": f"Shell创建失败: {str(e)}"})
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(f"WebSSH shell creation failed: {type(e).__name__}: {e!r}")
|
||||
if not ws_closed:
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_ERROR, "message": f"Shell创建失败: {type(e).__name__}"})
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# ── Cleanup ──
|
||||
await ssh_pool.release(server.id)
|
||||
await _close_ssh_session(session_id)
|
||||
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_CLOSE, "session_id": session_id})
|
||||
except Exception:
|
||||
pass
|
||||
if not ws_closed:
|
||||
try:
|
||||
await websocket.send_json({"type": MSG_CLOSE, "session_id": session_id})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info(f"WebSSH session closed: admin={admin.username}, server={server.name}, session={session_id}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user