Marc-André reported an issue on QEMU crash when retrying a cancelled migration during early setup phase, see "Link:" for more information, and also easy way to reproduce. This patch is a replacement of the prior fix proposed by not only switching to migration_cleanup(), but also fixing it from CPR side, so that we track hup_source properly to know if src QEMU is waiting or the HUP signal. To put it simple: this chunk of special casing in migration_cancel() should not affect normal migration, but only cpr-transfer migration to cover the small window when the src QEMU is waiting for a HUP signal on cpr channel (so that src QEMU can continue the migration on the main channel). To achieve that, we'll also need to remember to detach the hup_source whenenver invoked: after that point, we should always be able to cleanup the migration. It's not a generic operation to explicitly detach a gsource from its context while in its dispatch() function. But it should be safe, because gsource disptch() will only happen with a boosted refcount for the dispatcher so that the gsource will not be freed until the callback completes. It's also safe to return G_SOURCE_REMOVE after the gsource is detached, as glib will simply ignore the G_SOURCE_REMOVE. One can refer to latest 2.86.5 glib code in g_main_dispatch() for that: https://github.com/GNOME/glib/blob/2.86.5/glib/gmain.c#L3592 When at this, add a bunch of assertions to make sure nothing surprises us. After this patch applied, the 2nd migration will not crash QEMU, instead it'll be in CANCELLING until the socket connection times out (it will take ~2min on my Fedora default kernel). During this process no 2nd migration will be allowed, and after it timed out migration can be restarted. It's because so far we don't have control over socket_connect_outgoing(), or anything yet managed by a task executed in qio_task_run_in_thread(). Speeding up the cancellation to be left for future. I also tested cpr-transfer by only providing cpr channel not the main channel (with -incoming defer), kickoff migration on source, then cancel it on source directly without providing the main channel. It keeps working. I wanted to add an unit test for that but it'll need to refactor current cpr-transfer tests first; let's leave it for later. Link: https://lore.kernel.org/r/20260417184742.293061-1-marcandre.lureau@redhat.com Reported-by: Marc-André Lureau <marcandre.lureau@redhat.com> Tested-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Fabiano Rosas <farosas@suse.de> Link: https://lore.kernel.org/r/20260421175820.302795-1-peterx@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com>
70 lines
2.1 KiB
C
70 lines
2.1 KiB
C
/*
|
|
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
#ifndef MIGRATION_CPR_H
|
|
#define MIGRATION_CPR_H
|
|
|
|
#include "qapi/qapi-types-migration.h"
|
|
#include "io/channel.h"
|
|
#include "qemu/queue.h"
|
|
|
|
#define MIG_MODE_NONE -1
|
|
|
|
#define QEMU_CPR_FILE_MAGIC 0x51435052
|
|
#define QEMU_CPR_FILE_VERSION 0x00000001
|
|
#define CPR_STATE "CprState"
|
|
|
|
typedef QLIST_HEAD(CprFdList, CprFd) CprFdList;
|
|
typedef QLIST_HEAD(CprVFIODeviceList, CprVFIODevice) CprVFIODeviceList;
|
|
|
|
typedef struct CprState {
|
|
CprFdList fds;
|
|
CprVFIODeviceList vfio_devices;
|
|
} CprState;
|
|
|
|
extern CprState cpr_state;
|
|
|
|
void cpr_save_fd(const char *name, int id, int fd);
|
|
void cpr_delete_fd(const char *name, int id);
|
|
int cpr_find_fd(const char *name, int id);
|
|
void cpr_resave_fd(const char *name, int id, int fd);
|
|
int cpr_open_fd(const char *path, int flags, const char *name, int id,
|
|
Error **errp);
|
|
|
|
typedef bool (*cpr_walk_fd_cb)(int fd);
|
|
bool cpr_walk_fd(cpr_walk_fd_cb cb);
|
|
|
|
MigMode cpr_get_incoming_mode(void);
|
|
void cpr_set_incoming_mode(MigMode mode);
|
|
bool cpr_is_incoming(void);
|
|
|
|
bool cpr_state_save(MigrationChannel *channel, Error **errp);
|
|
bool cpr_state_load(MigrationChannel *channel, Error **errp);
|
|
void cpr_state_close(void);
|
|
struct QIOChannel *cpr_state_ioc(void);
|
|
|
|
bool cpr_incoming_needed(void *opaque);
|
|
int cpr_get_fd_param(const char *name, const char *fdname, int index,
|
|
Error **errp);
|
|
|
|
QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp);
|
|
QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp);
|
|
|
|
void cpr_transfer_add_hup_watch(MigrationState *s, QIOChannelFunc func,
|
|
void *opaque);
|
|
void cpr_transfer_source_destroy(MigrationState *s);
|
|
bool cpr_transfer_source_active(MigrationState *s);
|
|
|
|
void cpr_exec_init(void);
|
|
QEMUFile *cpr_exec_output(Error **errp);
|
|
QEMUFile *cpr_exec_input(Error **errp);
|
|
bool cpr_exec_persist_state(QEMUFile *f, Error **errp);
|
|
bool cpr_exec_has_state(void);
|
|
void cpr_exec_unpersist_state(void);
|
|
void cpr_exec_unpreserve_fds(void);
|
|
#endif
|