Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The paused-versus-suspended shutdown case is unresolved, and regression coverage is still needed.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes power-off handling for HCS-backed VMs after suspension by cleaning saved-state files and adjusting lifecycle handling.
Changes:
- Adds reusable saved-state cleanup.
- Reuses cleanup during startup and forceful shutdown.
- Handles suspended states during shutdown polling and warns on cleanup failure.
File summaries
| File | Summary | Final review notes |
|---|---|---|
src/platform/backends/hyperv_api/hcs_virtual_machine.h |
Declares the saved-state cleanup helper. | No findings. |
src/platform/backends/hyperv_api/hcs_virtual_machine.cpp |
Implements lifecycle, cleanup, shutdown, and polling changes. | Critical paused-versus-suspended shutdown issue; moderate regression-test gap; two typo fixes (reling → relying, exits → exists). |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const auto r = HCS().terminate_compute_system(hcs_system); | ||
| mpl::debug(get_name(), "shutdown -> terminate_compute_system result: {}", r.code); | ||
| } | ||
| remove_suspend_state_file_if_exists(); |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5259 +/- ##
=======================================
Coverage 72.56% 72.56%
=======================================
Files 339 339
Lines 18310 18310
=======================================
Hits 13285 13285
Misses 5025 5025 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🔵 Needs a closer look
Shutdown handling can skip termination and report success when saved-state removal fails, leaving the VM suspended.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
src/platform/backends/hyperv_api/hcs_virtual_machine.cpp:487
- This treats a failed saved-state-file removal as a successful shutdown. In that case
has_saved_state_file()keeps mapping the stopped HCS system toState::suspended, so the command returns successfully while later operations still see a stale suspend state. Please propagate the removal failure (or keep retrying with a bounded error) instead of returningdonefor this failure case.
This issue also appears on line 487 of the same file.
src/platform/backends/hyperv_api/hcs_virtual_machine.cpp:462
State::suspendedis also produced when HCS reportspaused, not only when a stopped system has a saved-state file. If the earlier suspend-timeterminate_compute_systemfailed, this branch skips termination, removes only the file, andcurrent_state()remains suspended, so the new completion case reports success without powering off the VM. Please keep the terminate operation for this case (or branch on the actual HCS state) before removing the saved-state file.
if (state != State::suspended)
{
const auto r = HCS().terminate_compute_system(hcs_system);
mpl::debug(get_name(), "shutdown -> terminate_compute_system result: {}", r.code);
}
src/platform/backends/hyperv_api/hcs_virtual_machine.cpp:487
- When the saved-state file is removed successfully,
current_state()changes the in-memory state tostoppedduring this wait, but this path does not callhandle_state_update(); because termination was skipped, there is also noSystemExitedcallback to persist it. The status monitor can therefore continue to persist the VM as suspended after a successful shutdown. Persist the refreshed state once the wait completes.
return multipass::utils::TimeoutAction::done;
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
Also throw exception in case deleting the state file fails
5fc68d5 to
4d382a2
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved moderate and critical findings affect shutdown retry behavior and suspended-state preservation.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ahmed Essam <aessam.dahy@gmail.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical mock-expectation failures and moderate suspended-state handling issues remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
src/platform/backends/hyperv_api/hcs_virtual_machine.cpp:489
State::suspendedis not synonymous with a saved-state file here:set_state()maps HCSpausedto this state unconditionally. A force-off of a paused HCS VM can therefore enter this case with no.SavedState.vmrs(or while termination is still settling), causing it to throw “Could not remove state file” instead of retrying until the HCS state becomes stopped. Only throw whenhas_saved_state_file()is still true; otherwise returnTimeoutAction::retry.
case VirtualMachine::State::suspended:
// FIXME: If we're here, then we've tried to delete the state file but
// failed. VM state will remain suspended which might cause issues the
// next time the VM is run.
//
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Add successful terminate_compute_system expectations before shutdown in both affected tests.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine.cpp:562
mock_hcsis aStrictMock, and this shutdown path always invokesterminate_compute_systembefore attempting the saved-state removal. This test has no expectation for that call, so it fails on the unexpected strict-mock invocation before it can verify the removal-failure behavior. Add a successful termination expectation before callingshutdown.
EXPECT_THROW(uut->shutdown(multipass::VirtualMachine::ShutdownPolicy::Poweroff),
std::runtime_error);
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
| // The proper solution is to fetch the api state and check whether | ||
| // the VM is in running/unknown/paused state, but that would cause a number | ||
| // of tests to fail, so let's just ignore the error log message for now. | ||
| const auto r = HCS().terminate_compute_system(hcs_system); |
There was a problem hiding this comment.
We shouldn't proceed with the wait lambda if terminate fails, since it's what triggers the state change via the compute system callback. We should also probably reset the hcs_system handle in compute_system_event_callback when a system exits.
| case VirtualMachine::State::stopped: | ||
| case VirtualMachine::State::off: | ||
| return multipass::utils::TimeoutAction::done; | ||
| case VirtualMachine::State::suspended: |
There was a problem hiding this comment.
This does not need special handling -- suspended VMs are, by definition, terminated. We need to ensure that if a suspended VM gets a forceful shutdown call, we remove the suspend state file and set the VM state to OFF.
| mpl::debug(get_name(), "shutdown -> terminate_compute_system result: {}", r.code); | ||
|
|
||
| if (shutdown_policy == ShutdownPolicy::Poweroff) | ||
| remove_saved_state_file_if_exists(); |
There was a problem hiding this comment.
We can change this if block to
if suspended:
if not remove_saved_state_file_if_exists:
... log a warning msg
set state to off
Description
If you suspend a VM, then try to forcefully power it off, the multipass daemon gets stuck.
The issue is that the VM stays in the suspended state, and an infinite loop happens here:
multipass/src/platform/backends/hyperv_api/hcs_virtual_machine.cpp
Lines 476 to 485 in 33f26fe
because we never go to either of the
stoppedstate oroffstate.The fix is to try to remove the suspension state file if we're trying to forcefully shutdown the VM (matching the behavior of the qemu backend for example).
Closes #5263
Testing
multipass stop --force <instance>.Checklist