fix: add per-device execute lock to prevent I/O interleaving - #902
fix: add per-device execute lock to prevent I/O interleaving#902ChristopherJHart wants to merge 1 commit into
Conversation
884efcd to
f66197c
Compare
|
@ChristopherJHart : good catch, and I reckon worth a defensive fix. Executing the tests sequentially on each device has been a design decision to not overload any device, so the risk is rather low. |
|
Heads-up from reviewing #899, since the two overlap. Merge conflict incoming either way. #899 rewrites Lock scoping — worth a thought while you're in here. The interleaved-PTY problem this PR fixes is real and I'd like it merged. But there's a second, adjacent hazard that the current placement doesn't cover, and it gets more likely once #899's reconnect-and-retry lands:
Two options if you want to close it here rather than in a separate issue:
No objection to keeping this PR narrowly scoped to the I/O interleaving and tracking the teardown race separately — mainly flagging that a per-device lock landing now is the natural moment to decide which lock owns the teardown, so we don't end up with three. |
…n races Consolidate connection_locks and the proposed _execute_locks into a single _device_locks dict. The lock in _execute_command now spans the full get-connection → execute → failure-handling → retry cycle, which closes two hazards: (1) interleaved PTY I/O from concurrent execute() calls on Unicon's non-thread-safe spawn, and (2) a stale caller tearing down a successor's connection during the reconnect-and-retry window. _get_connection and _run_and_cache are now lock-free — callers hold the lock. External entry points (_ensure_connection, _disconnect_device) acquire _device_locks themselves. Addresses review feedback on PR netascode#902 and overlap with netascode#899. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> AI-Generated: yes AI-Tool: claude-code AI-Model: opus-4.6 AI-Percent: 56 AI-Reason: per-device lock consolidation addressing PR netascode#902 review feedback
f66197c to
5b47140
Compare
|
Thanks for the detailed review — both the merge-order flag and the lock-scoping analysis were spot-on. Rebased onto main (post-#899 merge). The old commit is replaced by a single new one ( Lock consolidation (your Option A).
I also added a double-check of the command cache under the lock to avoid redundant execution when two callers race on the same command. All 62 broker tests (unit + integration) pass. |
Summary
Adds a per-hostname
asyncio.Lockaround command execution to prevent interleaved PTY I/O on shared Unicon spawns.Problem
The broker dispatches commands via
run_in_executor(None, connection.execute, cmd)with no per-device serialization. Unicon's spawn is not thread-safe — concurrentexecute()calls on the same device interleave I/O on the PTY, corrupting command output.Today this is safe only by accident (tests are sequential per device). Any future intra-device parallelism would immediately hit this bug.
Solution
_execute_locks: dict[str, asyncio.Lock]to the brokerImpact
Files Changed
nac_test/pyats_core/broker/connection_broker.pyasyncio.Lockin_execute_commandTest plan
🤖 Generated with Claude Code