mirror of
https://github.com/open-webui/open-webui.git
synced 2026-06-14 03:30:25 +00:00
fix: don't hang terminal proxy when one forwarding pump exits first (#25479)
The proxy gathered _client_to_upstream and _upstream_to_client with return_exceptions=True. When upstream sends a graceful CLOSE, _upstream_to_client returns but gather keeps waiting on _client_to_upstream, which is blocked in ws.receive() until the browser disconnects. The handler stays pending and the finally: session.close() cleanup is deferred, leaking a ClientSession and an open browser socket. Use asyncio.wait(return_when=FIRST_COMPLETED) and cancel the pending sibling, so the proxy unwinds as soon as either direction finishes. The pumps' bare except Exception already lets CancelledError (a BaseException) propagate, so cancellation is clean and they need no change. Fixes #25464 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -331,11 +331,20 @@ async def ws_terminal(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
await asyncio.gather(
|
||||
_client_to_upstream(),
|
||||
_upstream_to_client(),
|
||||
return_exceptions=True,
|
||||
)
|
||||
# End the proxy as soon as either direction finishes (e.g. a
|
||||
# graceful upstream CLOSE) and cancel the sibling, which would
|
||||
# otherwise hang on a blocked ws.receive() until the browser leaves.
|
||||
tasks = [
|
||||
asyncio.create_task(_client_to_upstream()),
|
||||
asyncio.create_task(_upstream_to_client()),
|
||||
]
|
||||
_done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
try:
|
||||
await task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
log.exception('Terminal WebSocket proxy error: %s', e)
|
||||
finally:
|
||||
|
||||
Reference in New Issue
Block a user