[ssh-process] Fix callback lifetime and remove error storage#5004
[ssh-process] Fix callback lifetime and remove error storage#5004tobe2098 wants to merge 6 commits into
Conversation
d0ac497 to
5e547b5
Compare
1c37bde to
98173ee
Compare
98173ee to
746dc8e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5004 +/- ##
==========================================
- Coverage 87.57% 87.37% -0.19%
==========================================
Files 275 274 -1
Lines 14674 14710 +36
==========================================
+ Hits 12849 12852 +3
- Misses 1825 1858 +33 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR addresses libssh channel exit-status retrieval reliability by registering channel callbacks for the full PlainSSHProcess lifetime (instead of only during read_exit_code()), and refactors unit tests to use a callback-driven mock rather than storing/rethrowing exceptions.
Changes:
- Register libssh channel callbacks when creating the channel, and track exit status / EOF / close via callbacks in
PlainSSHProcess. - Remove the “store exception_ptr and rethrow later” behavior in favor of storing channel state and throwing on each failed attempt.
- Update unit tests to use a new callback/event polling mock (
CallbackEngineMock) to drive exit status, EOF, and close events.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_sshfsmount.cpp | Switches sshfs-related tests from exit-status mock to callback-engine mock. |
| tests/unit/test_ssh_process.cpp | Updates exit status retrieval test to push callback state rather than invoking callback directly. |
| tests/unit/test_sftpserver.cpp | Refactors SFTP server tests to drive process lifecycle via callback-engine mock states. |
| tests/unit/test_plain_ssh_session.cpp | Introduces callback-engine mock instantiation to support new callback registration behavior. |
| tests/unit/mock_ssh_process_exit_status.h | Repurposes/duplicates mock definitions into callback-engine style types (now overlaps with new header). |
| tests/unit/mock_ssh_callback_engine.h | Adds new callback-engine mock used by updated unit tests. |
| src/ssh/plain_ssh_process.cpp | Implements lifetime callbacks and new exit-code/EOF/close tracking logic. |
| include/multipass/ssh/plain_ssh_process.h | Updates PlainSSHProcess interface/state to support callbacks and optional exit result. |
Comments suppressed due to low confidence (1)
src/ssh/plain_ssh_process.cpp:57
- If
ssh_channel_open_session()orssh_channel_request_exec()throws after callbacks are registered,PlainSSHProcesswill never reach its destructor, so callbacks can remain registered withuserdata = thiswhile the partially-constructed object is being unwound. Unregister callbacks on these error paths to avoid potential use-after-free during libssh cleanup.
mp::SSH::throw_on_error(channel,
session,
"[ssh proc] failed to open session channel",
ssh_channel_open_session);
mp::SSH::throw_on_error(channel,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
f700c7a to
077bb35
Compare
077bb35 to
fb2f7cd
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/ssh/plain_ssh_process.cpp:257
read_stream()uses only the internalchannel_closedflag to decide whether the channel is closed. Since this flag is updated only via callbacks, it can lag behind the actual libssh channel state; when the channel is already closed, this can lead to callingssh_channel_read_timeout()and throwing on the new libssh “closed channel” error instead of returning gracefully.
// If the channel is closed there's no output to read
if (!channel || channel_closed) // TODO@sftp
{
mpl::trace_location(category, "{}", !channel ? "null channel" : "channel closed");
return std::string();
}
src/ssh/plain_ssh_process.cpp:279
- In the read error path, the “channel closed” special-case check also relies only on
channel_closed. If the close callback hasn’t fired yet but libssh already considers the channel closed, this will throw instead of returning the buffered output.
if (num_bytes < 0)
{
// Latest libssh now returns an error if the channel has been closed instead of
// returning 0 bytes
if (channel_closed)
{
mpl::trace_location(category, "channel closed");
return output.str();
}
tests/unit/mock_ssh_callback_engine.h:58
CallbackEngineMockunconditionally callschannel_eof_function/channel_close_functionwhencb_s.eof/cb_s.closedare set. In libssh these callback function pointers are optional and can legitimately be null; this mock should guard against null to avoid test crashes when a caller only registers a subset of callbacks.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
tests/unit/mock_ssh_callback_engine.h:35
- The TODO comment has a grammatical typo (“can we can rid”).
The assert does not hold anymore, a sync function could have triggered the callback.
fb2f7cd to
cb7257e
Compare
Description
What does this PR do? This PR first fixes the callback lifetime in PlainSSHProcess, and removes the previous convention of storing an exception pointer and re-throwing. Instead, we store the actual status of the channel and re-throw an error on every attempt.
Why is this change needed? The previous implementation only registered callbacks during read_exit_code, which could cause a loss of the exit status of the
ssh_channel.Related Issue(s)
Closes #4990
Testing
Checklist