mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Fix telnet console silent-proxy hang on non-ConnectionError exit (#2344)
The run() cleanup block was guarded by `except (ConnectionError, OSError):`, so exits via asyncio.CancelledError or any other exception type skipped cleanup. Result: `_reader_process` stays pinned to the dead reader and `_get_reader()` returns None for every subsequent client — the silent-proxy symptom described in #2344. Convert the except block to try/finally so cleanup always runs, regardless of how `_process()` exits. Also: - catch asyncio.CancelledError + generic Exception (with log.exception) so unexpected failures don't swallow the cleanup - reset `_current_read = None` after cancellation - use `dict.pop(..., None)` instead of `del` to avoid KeyError races if the broadcast loop's timeout handler already removed the entry Triggering pattern observed in practice: a diagnostic tool opens a console, sends a few commands, and closes abruptly (e.g. from a test harness or orchestration script that cancels its Task). If the `_process()` task was awaiting on one of the `network_read` / `reader_read` futures at the time of cancellation, the CancelledError propagates up through `run()` and bypasses the ConnectionError-only except clause. The proxy accepts future connections (the listen socket is still alive) but never forwards any data because `_reader_process` never got reset. Validated against gns3/gns3-server:latest (2.2.56.1) running a 10-scenario sequential regression batch that previously hung reliably on the 4th sp_v1 / L3VPN scenario and now completes cleanly across all 10.
This commit is contained in:
parent
ae3ae46151
commit
de0d050689
@ -215,18 +215,34 @@ class AsyncioTelnetServer:
|
||||
await self._write_intro(network_writer, echo=self._echo, binary=self._binary, naws=self._naws)
|
||||
await connection.connected()
|
||||
await self._process(network_reader, network_writer, connection)
|
||||
except (ConnectionError, OSError):
|
||||
except (ConnectionError, OSError, asyncio.CancelledError):
|
||||
pass
|
||||
except Exception:
|
||||
# Catch any unexpected exception so the cleanup below still runs.
|
||||
# Without the try/finally, an uncaught exception here would leave
|
||||
# _reader_process pinned to a dead reader, and subsequent client
|
||||
# connections would see _get_reader() return None and never
|
||||
# receive node output -- the "silent proxy" hang (issue #2344).
|
||||
log.exception("Unexpected error in telnet proxy; cleaning up client connection")
|
||||
finally:
|
||||
async with self._lock:
|
||||
network_writer.close()
|
||||
# await network_writer.wait_closed() # this doesn't work in Python 3.6
|
||||
try:
|
||||
network_writer.close()
|
||||
except Exception:
|
||||
pass
|
||||
if self._reader_process == network_reader:
|
||||
self._reader_process = None
|
||||
# Cancel current read from this reader
|
||||
if self._current_read is not None:
|
||||
self._current_read.cancel()
|
||||
|
||||
await connection.disconnected()
|
||||
del self._connections[network_writer]
|
||||
self._current_read = None
|
||||
try:
|
||||
await connection.disconnected()
|
||||
except Exception:
|
||||
pass
|
||||
# pop() instead of del to avoid KeyError if already removed
|
||||
# elsewhere (e.g. by the broadcast loop's timeout handler).
|
||||
self._connections.pop(network_writer, None)
|
||||
|
||||
async def close(self):
|
||||
for writer, connection in self._connections.items():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user