]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn-varlink: treat QMP disconnect as success for Terminate main
authorChristian Brauner <brauner@kernel.org>
Mon, 27 Apr 2026 10:34:27 +0000 (12:34 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 27 Apr 2026 13:07:26 +0000 (15:07 +0200)
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) <brauner@kernel.org>
src/vmspawn/vmspawn-varlink.c

index 51a1091e40a0ce0f88ab1fe3b290efcf198ed881..9aa6afeae0385151b64b51c50f14a099c1463849 100644 (file)
@@ -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) {