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 change makes AI Copilot features optional to reduce installation
size and support restricted environments.
Changes:
- Split AI dependencies into ai-requirements.txt
- Add ai-copilot optional dependency in pyproject.toml
- Add import protection in gns3server/agent/__init__.py
- Return 501 for AI endpoints when dependencies not installed
- Add gns3server-uninstall-ai-copilot command for cleanup
- Update README with installation and uninstallation instructions
Installation:
- Basic: pip install gns3-server
- With AI: pip install gns3-server[ai-copilot]
- Development: pip install gns3-server[ai-copilot,dev]
Uninstallation:
- gns3server-uninstall-ai-copilot
- Catch OSError alongside ConnectionError in connection processing to handle more network errors
- Move client_info retrieval inside try block to prevent AttributeError on failed connections
- Replace bare except with specific exceptions (OSError, ConnectionError, asyncio.TimeoutError)
- Improve error logging to include specific exception details and client information
- Catch OSError alongside ConnectionError in connection processing to handle more network errors
- Move client_info retrieval inside try block to prevent AttributeError on failed connections
- Replace bare except with specific exceptions (OSError, ConnectionError, asyncio.TimeoutError)
- Improve error logging to include specific exception details and client information
Fixes two critical issues in telnet server when clients rapidly
connect/disconnect during broadcast operations:
1. **OSError [Errno 107]**: Transport endpoint not connected
- Root cause: getpeername() called outside try block
- Fix: Move getpeername() inside try block and catch OSError
- Expand exception handling to include OSError and TimeoutError
2. **KeyError**: Double deletion from connections dictionary
- Root cause: Connection deleted in broadcast loop, then deleted
again in top-level exception handler
- Fix: Use dict.pop(key, None) instead of del dict[key]
**Changes**:
- Line 216: Add OSError to top-level exception handler
- Line 227-228: Use pop() to avoid KeyError on double deletion
- Line 305-316: Move getpeername() inside try block, expand
exception types, use pop() for safe deletion
**Impact**:
- Prevents unhandled exceptions from propagating to asyncio event loop
- Ensures proper resource cleanup even with race conditions
- No resource leaks or zombie connections
- Long-lived clients unaffected by rapid client disconnects
**Test Script**:
- Added stress test script to reproduce the issue
- Supports IOU-L3, VPCS, and generic device types
- Rapid clients send broadcast-triggering commands (show/run/OSPF)
**Related**: Fixes bug documented in docs/bugs/telnet-server-connection-race-condition.md
- Add exception for .claude/development.md to allow tracking development documentation
- Comment out PROJECT_CONTEXT.md exclusion to enable version control of project context file
- Create PROJECT_CONTEXT.md with comprehensive project overview for AI assistant support
- Document project structure, AI copilot data flow, SSE event types, and code standards
- Provide flake8 static analysis guidelines and common error fixes
- Introduce new API route `/access` for managing LLM model configurations
- Add LLMModelConfig model to database models
- Include comprehensive schemas for LLM model config CRUD operations
- Register new router with tags for LLM Model Configurations
Set tcp keepalive timers to 60 seconds. Seems to default to 2 hours on ubuntu 22. Most firewalls will age out an idle tcp session at 1 hour.
Will not address telnet console failing after a tcp session has failed (TimeoutError).