Skip to content

Commit ba0e9d5

Browse files
committed
fix: handle WebSocket ConnectionClosed to prevent hang on shell exit
1 parent c6d42ed commit ba0e9d5

1 file changed

Lines changed: 30 additions & 22 deletions

File tree

centml/cli/shell.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,24 @@ async def _forward_io(ws, screen, stream):
219219

220220
async def _read_ws():
221221
nonlocal exit_code
222-
async for raw_msg in ws:
223-
msg = json.loads(raw_msg)
224-
if msg.get("data"):
225-
# Convert bare \n to \r\n before feeding pyte, equivalent
226-
# to xterm.js ``convertEol: true``.
227-
stream.feed(msg["data"].replace("\n", "\r\n"))
228-
_render_dirty(screen, sys.stdout.buffer)
229-
elif msg.get("error"):
230-
stream.feed(f"Error: {msg['error']}\r\n")
231-
_render_dirty(screen, sys.stdout.buffer)
232-
if "Code" in msg:
233-
exit_code = msg["Code"]
234-
return
222+
try:
223+
async for raw_msg in ws:
224+
msg = json.loads(raw_msg)
225+
if msg.get("data"):
226+
# pyte expects \r\n; remote PTY may send bare \n
227+
# (same as xterm.js ``convertEol: true``).
228+
stream.feed(msg["data"].replace("\n", "\r\n"))
229+
_render_dirty(screen, sys.stdout.buffer)
230+
elif msg.get("error"):
231+
stream.feed(f"Error: {msg['error']}\r\n")
232+
_render_dirty(screen, sys.stdout.buffer)
233+
if "Code" in msg:
234+
exit_code = msg["Code"]
235+
return
236+
except websockets.ConnectionClosed:
237+
# Backend proxy may not send a clean close frame when
238+
# ArgoCD disconnects after the remote shell exits.
239+
return
235240

236241
async def _read_stdin():
237242
read_queue = asyncio.Queue()
@@ -250,16 +255,19 @@ def _on_stdin_ready():
250255
data = await asyncio.wait_for(read_queue.get(), timeout=0.5)
251256
except asyncio.TimeoutError:
252257
continue
253-
await ws.send(
254-
json.dumps(
255-
{
256-
"operation": "stdin",
257-
"data": data.decode("utf-8", errors="replace"),
258-
"rows": screen.lines,
259-
"cols": screen.columns,
260-
}
258+
try:
259+
await ws.send(
260+
json.dumps(
261+
{
262+
"operation": "stdin",
263+
"data": data.decode("utf-8", errors="replace"),
264+
"rows": screen.lines,
265+
"cols": screen.columns,
266+
}
267+
)
261268
)
262-
)
269+
except websockets.ConnectionClosed:
270+
return
263271
finally:
264272
loop.remove_reader(stdin_fd)
265273

0 commit comments

Comments
 (0)