Repository navigation
feat(core/transport): keep one SSH connection per transport - #214
Merged
Merged
Conversation
Coverage Report for CI Build 39939074938Coverage decreased (-4.8%) to 71.088%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
safl
force-pushed
the
ssh-keep-connection
branch
2 times, most recently
from
October 23, 2026 10:04
9faccb2 to
44079a9
Compare
The SSH transport connected before and disconnected after every run(), get() and put(), so each command paid a key exchange. Measured from a test harness against a host at 43 ms ping, a command cost about 0.55 s that way and about 0.175 s over a kept connection, which made cheap remote checks expensive enough to be left out. Per-command connections existed to cope with a remote that reboots and to not leave connections open at exit; a kept connection handles both by connecting again on next use when the old one is no longer active, and by being closed by Cijoe.close(), at pytest unconfigure, or by a finalizer as Python exits. A connect that fails part way, as when authentication is refused, is closed rather than kept. A 30 s keepalive keeps an idle connection's NAT and firewall state. wait_for_transport closes the connection before each probe, since a kept session can outlive a remote that no longer accepts new ones, and only a fresh connect tells whether it is up or down. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Simon A. F. Lund <os@safl.dk>
safl
force-pushed
the
ssh-keep-connection
branch
from
October 23, 2026 10:26
44079a9 to
40d9721
Compare
naddinadja
approved these changes
Oct 23, 2026
| HEAD can be subject to a git rebase. | ||
|
|
||
| ## 0.9.61 (Upcoming) | ||
|
|
Contributor
There was a problem hiding this comment.
We need to remember to remove the "(Upcoming)" once we do the new tag
Collaborator
Author
|
xNVMe exercises this together with #215 and #216 in xnvme/xnvme#825, which pins cijoe to the preview branch |
This was referenced Oct 23, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The SSH transport now keeps one connection per transport instead of connecting and disconnecting around every
run(),get()andput(). Against a lab host at 43 ms ping a command cost about 0.55 s with a connection each and about 0.175 s over a kept one; oXIO's test harness had to override_SSH__connect/_SSH__disconnectto get this, which this change makes unnecessary.What to look at carefully:
SSH.__connect()reuses the connection whileget_transport().is_active(), otherwise closes and connects again with a 30 s keepalive. A connection that died silently but still reports active blocks the next command until the kernel gives up on it, which with the 30 s keepalive writing is tcp_retries2, about 15 minutes on Linux defaults (a fresh connect gave up after the SYN retries, about 2 minutes); that command then fails once and the one after reconnects. There is deliberately no retry, since a retry could run a command twice. A connect that fails after paramiko started the transport, as on refused authentication, is closed rather than kept.wait_for_transportnow closes the transport before each probe, so "up" and "down" are still decided by a fresh connect rather than by a kept session that can outlive a remote no longer accepting new ones.[cijoe.transport.<name>]is passed straight to paramiko'sconnect(), so a cijoe key there would have to be stripped first. Say if you want one anyway.Cijoe.close(), which takes an optional transport name to close only that one (aswait_for_transportdoes), andTransport.close(); the pytest plugin callsCijoe.close()inpytest_unconfigure, and aweakref.finalizecloses the client at exit.Verification gap: the new tests in
tests/core/test_transport_ssh.pydrive a realCijoeover a fake paramiko client (one connection for many commands and transfers, a reconnect after a drop,close(), a failed connect not being kept, a command's SSH error dropping the connection, a failed transfer dropping it, closing a single transport, and the fresh-connect probe inwait_for_transport). Each fails without the code it covers; onmain,test_wait_for_transport_probes_a_fresh_connectionpasses its assertion and fails only in the fixture, sinceCijoe.close()does not exist there. They ran locally on Python 3.14 with paramiko 5.0.0, and CI runs them on 3.10 to 3.13 on Linux and macOS with whatever paramiko and scp resolve, sincepyproject.tomlpins neither. Nothing ran against a real SSH host from this branch: keepalive, reconnect after a real reboot, SCP over a reused connection andwait_for_transporttiming are unexercised. The same keep-connection approach, as an override in oXIO's harness, has run full test sessions against four lab hosts. Two existing tests intests/core/test_collector.pyfail locally with and without this change, from stray files in/tmpand the templates directory.Assisted-by: Claude Code:claude-opus-5-5