system/runstate: add VM state change cb with return value

This patch adds the new VM state change cb type `VMChangeStateHandlerWithRet`,
which has return value for `VMChangeStateEntry`.

Thus, we can register a new VM state change cb with return value for device.
Note that `VMChangeStateHandler` and `VMChangeStateHandlerWithRet` are mutually
exclusive and cannot be provided at the same time.

This patch is the pre patch for 'vhost-user: return failure if backend crashes
when live migration', which makes the live migration aware of the loss of
connection with the vhost-user backend and aborts the live migration.

Virtio device will use VMChangeStateHandlerWithRet.

Signed-off-by: Haoqian He <haoqian.he@smartx.com>
Message-Id: <20250416024729.3289157-2-haoqian.he@smartx.com>
Tested-by: Lei Yang <leiyang@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Haoqian He 2025-04-15 22:47:26 -04:00 committed by Michael S. Tsirkin
parent 8717987fb5
commit e0f300b36d
8 changed files with 62 additions and 20 deletions

View file

@ -299,14 +299,18 @@ static int do_vm_stop(RunState state, bool send_stop)
if (oldstate == RUN_STATE_RUNNING) {
pause_all_vcpus();
}
vm_state_notify(0, state);
ret = vm_state_notify(0, state);
if (send_stop) {
qapi_event_send_stop();
}
}
bdrv_drain_all();
ret = bdrv_flush_all();
/*
* Even if vm_state_notify() return failure,
* it would be better to flush as before.
*/
ret |= bdrv_flush_all();
trace_vm_stop_flush_all(ret);
return ret;

View file

@ -297,6 +297,7 @@ void qemu_system_vmstop_request(RunState state)
struct VMChangeStateEntry {
VMChangeStateHandler *cb;
VMChangeStateHandler *prepare_cb;
VMChangeStateHandlerWithRet *cb_ret;
void *opaque;
QTAILQ_ENTRY(VMChangeStateEntry) entries;
int priority;
@ -320,14 +321,15 @@ static QTAILQ_HEAD(, VMChangeStateEntry) vm_change_state_head =
VMChangeStateEntry *qemu_add_vm_change_state_handler_prio(
VMChangeStateHandler *cb, void *opaque, int priority)
{
return qemu_add_vm_change_state_handler_prio_full(cb, NULL, opaque,
priority);
return qemu_add_vm_change_state_handler_prio_full(cb, NULL, NULL,
opaque, priority);
}
/**
* qemu_add_vm_change_state_handler_prio_full:
* @cb: the main callback to invoke
* @prepare_cb: a callback to invoke before the main callback
* @cb_ret: the main callback to invoke with return value
* @opaque: user data passed to the callbacks
* @priority: low priorities execute first when the vm runs and the reverse is
* true when the vm stops
@ -344,6 +346,7 @@ VMChangeStateEntry *qemu_add_vm_change_state_handler_prio(
VMChangeStateEntry *
qemu_add_vm_change_state_handler_prio_full(VMChangeStateHandler *cb,
VMChangeStateHandler *prepare_cb,
VMChangeStateHandlerWithRet *cb_ret,
void *opaque, int priority)
{
VMChangeStateEntry *e;
@ -352,6 +355,7 @@ qemu_add_vm_change_state_handler_prio_full(VMChangeStateHandler *cb,
e = g_malloc0(sizeof(*e));
e->cb = cb;
e->prepare_cb = prepare_cb;
e->cb_ret = cb_ret;
e->opaque = opaque;
e->priority = priority;
@ -379,9 +383,10 @@ void qemu_del_vm_change_state_handler(VMChangeStateEntry *e)
g_free(e);
}
void vm_state_notify(bool running, RunState state)
int vm_state_notify(bool running, RunState state)
{
VMChangeStateEntry *e, *next;
int ret = 0;
trace_vm_state_notify(running, state, RunState_str(state));
@ -393,7 +398,17 @@ void vm_state_notify(bool running, RunState state)
}
QTAILQ_FOREACH_SAFE(e, &vm_change_state_head, entries, next) {
e->cb(e->opaque, running, state);
if (e->cb) {
e->cb(e->opaque, running, state);
} else if (e->cb_ret) {
/*
* Here ignore the return value of cb_ret because
* we only care about the stopping the device during
* the VM live migration to indicate whether the
* connection between qemu and backend is normal.
*/
e->cb_ret(e->opaque, running, state);
}
}
} else {
QTAILQ_FOREACH_REVERSE_SAFE(e, &vm_change_state_head, entries, next) {
@ -403,9 +418,19 @@ void vm_state_notify(bool running, RunState state)
}
QTAILQ_FOREACH_REVERSE_SAFE(e, &vm_change_state_head, entries, next) {
e->cb(e->opaque, running, state);
if (e->cb) {
e->cb(e->opaque, running, state);
} else if (e->cb_ret) {
/*
* We should execute all registered callbacks even if
* one of them returns failure, otherwise, some cleanup
* work of the device will be skipped.
*/
ret |= e->cb_ret(e->opaque, running, state);
}
}
}
return ret;
}
static ShutdownCause reset_requested;