From: Christian Brauner Date: Mon, 27 Apr 2026 10:34:27 +0000 (+0200) Subject: vmspawn-varlink: treat QMP disconnect as success for Terminate X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0d6d2c9ebda27a7da2da249458eb44610968bc97;p=thirdparty%2Fsystemd.git vmspawn-varlink: treat QMP disconnect as success for Terminate QMP "quit" tells QEMU to exit, which races the reply with the socket EOF: sometimes the disconnect lands in qmp_client_fail_pending() with -ECONNRESET before the reply has been parsed. The shared completion callback then translates that into io.systemd.MachineInstance.NotConnected, turning the desired outcome into a varlink error. This is exactly what TEST-87-AUX-UTILS-VM exposes during its repeated start/pause/resume/terminate stress loop: a successful Pause/Describe followed milliseconds later by a Terminate that fails with NotConnected when the disconnect path wins the race. Give Terminate its own completion callback that treats disconnect-class errors as success, since QEMU shutting down is the whole point of "quit". The other simple commands (Pause, Resume, PowerOff, Reboot) keep the existing semantics: they expect QMP to remain alive, so NotConnected is the correct reply for them. Link: https://github.com/systemd/systemd/actions/runs/24986080288/job/73159585425?pr=41835 Signed-off-by: Christian Brauner (Amutable) --- diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 51a1091e40a..9aa6afeae03 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -60,6 +60,29 @@ static int on_qmp_simple_complete( return 0; } +/* "quit" tells QEMU to exit, which races the QMP reply with the socket EOF — sometimes the + * disconnect lands in qmp_client_fail_pending() before the reply has been parsed. For Terminate + * that's the desired outcome, so treat disconnect-class errors as success. */ +static int on_qmp_terminate_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + sd_varlink *link = ASSERT_PTR(userdata); + + assert(client); + + if (error < 0 && !ERRNO_IS_DISCONNECT(error)) + (void) qmp_error_to_varlink(link, error_desc, error); + else + (void) sd_varlink_reply(link, NULL); + + sd_varlink_unref(link); + return 0; +} + static int qmp_execute_varlink_async( VmspawnVarlinkContext *ctx, sd_varlink *link, @@ -87,7 +110,7 @@ static int qmp_execute_simple_async(sd_varlink *link, VmspawnVarlinkContext *ctx } static int vl_method_terminate(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return qmp_execute_simple_async(link, ASSERT_PTR(userdata), "quit"); + return qmp_execute_varlink_async(ASSERT_PTR(userdata), link, "quit", /* arguments= */ NULL, on_qmp_terminate_complete); } static int vl_method_pause(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {