* [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races
@ 2024-02-06 21:51 Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 1/6] migration/multifd: Join the TLS thread Fabiano Rosas
` (7 more replies)
0 siblings, 8 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé
Based-on: 20240202102857.110210-1-peterx@redhat.com
[PATCH v2 00/23] migration/multifd: Refactor ->send_prepare() and cleanups
https://lore.kernel.org/r/20240202102857.110210-1-peterx@redhat.com
Hi,
For v3 I fixed the refcounting issue spotted by Avihai. The situation
there is a bit clunky due to historical reasons. The gist is that we
have an assumption that channel creation never fails after p->c has
been set, so when 'p->c == NULL' we have to unref and when 'p->c !=
NULL' the cleanup code will do the unref.
CI run: https://gitlab.com/farosas/qemu/-/pipelines/1166889341
v2:
https://lore.kernel.org/r/20240205194929.28963-1-farosas@suse.de
In this v2 I made sure NO channel is created after the semaphores are
posted. Feel free to call me out if that's not the case.
Not much changes, except that now both TLS and non-TLS go through the
same code, so there's a centralized place to do error handling and
releasing the semaphore.
CI run: https://gitlab.com/farosas/qemu/-/pipelines/1165206107
based on Peter's code: https://gitlab.com/farosas/qemu/-/pipelines/1165303276
v1:
https://lore.kernel.org/r/20240202191128.1901-1-farosas@suse.de
This contains 2 patches from my previous series addressing the
p->running misuse and the TLS thread leak and 3 new patches to fix the
cleanup-while-creating-threads race.
For the p->running I'm keeping the idea from the other series to
remove p->running and use a more narrow p->thread_created flag. This
flag is used only inform whether the thread has been created so we can
join it.
For the cleanup race I have moved some code around and added a
semaphore to make multifd_save_setup() only return once all channel
creation tasks have started.
The idea is that after multifd_save_setup() returns, no new creations
are in flight and the p->thread_created flags will never change again,
so they're enough to cause the cleanup code to wait for the threads to
join.
CI run: https://gitlab.com/farosas/qemu/-/pipelines/1162798843
@Peter: I can rebase this on top of your series once we decide about
it.
Fabiano Rosas (6):
migration/multifd: Join the TLS thread
migration/multifd: Remove p->running
migration/multifd: Move multifd_send_setup error handling in to the
function
migration/multifd: Move multifd_send_setup into migration thread
migration/multifd: Unify multifd and TLS connection paths
migration/multifd: Add a synchronization point for channel creation
migration/migration.c | 14 ++--
migration/multifd.c | 168 +++++++++++++++++++++++++-----------------
migration/multifd.h | 11 ++-
3 files changed, 109 insertions(+), 84 deletions(-)
--
2.35.3
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 1/6] migration/multifd: Join the TLS thread
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 2/6] migration/multifd: Remove p->running Fabiano Rosas
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé, qemu-stable
We're currently leaking the resources of the TLS thread by not joining
it and also overwriting the p->thread pointer altogether.
Fixes: a1af605bd5 ("migration/multifd: fix hangup with TLS-Multifd due to blocking handshake")
Cc: qemu-stable <qemu-stable@nongnu.org>
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/multifd.c | 8 +++++++-
migration/multifd.h | 2 ++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/migration/multifd.c b/migration/multifd.c
index ef13e2e781..8195c1daf3 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -630,6 +630,10 @@ static void multifd_send_terminate_threads(void)
for (i = 0; i < migrate_multifd_channels(); i++) {
MultiFDSendParams *p = &multifd_send_state->params[i];
+ if (p->tls_thread_created) {
+ qemu_thread_join(&p->tls_thread);
+ }
+
if (p->running) {
qemu_thread_join(&p->thread);
}
@@ -921,7 +925,9 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
trace_multifd_tls_outgoing_handshake_start(ioc, tioc, hostname);
qio_channel_set_name(QIO_CHANNEL(tioc), "multifd-tls-outgoing");
p->c = QIO_CHANNEL(tioc);
- qemu_thread_create(&p->thread, "multifd-tls-handshake-worker",
+
+ p->tls_thread_created = true;
+ qemu_thread_create(&p->tls_thread, "multifd-tls-handshake-worker",
multifd_tls_handshake_thread, p,
QEMU_THREAD_JOINABLE);
return true;
diff --git a/migration/multifd.h b/migration/multifd.h
index 78a2317263..720c9d50db 100644
--- a/migration/multifd.h
+++ b/migration/multifd.h
@@ -73,6 +73,8 @@ typedef struct {
char *name;
/* channel thread id */
QemuThread thread;
+ QemuThread tls_thread;
+ bool tls_thread_created;
/* communication channel */
QIOChannel *c;
/* is the yank function registered */
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 2/6] migration/multifd: Remove p->running
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 1/6] migration/multifd: Join the TLS thread Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 3/6] migration/multifd: Move multifd_send_setup error handling in to the function Fabiano Rosas
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé, qemu-stable,
chenyuhui5
We currently only need p->running to avoid calling qemu_thread_join()
on a non existent thread if the thread has never been created.
However, there are at least two bugs in this logic:
1) On the sending side, p->running is set too early and
qemu_thread_create() can be skipped due to an error during TLS
handshake, leaving the flag set and leading to a crash when
multifd_send_cleanup() calls qemu_thread_join().
2) During exit, the multifd thread clears the flag while holding the
channel lock. The counterpart at multifd_send_cleanup() reads the flag
outside of the lock and might free the mutex while the multifd thread
still has it locked.
Fix the first issue by setting the flag right before creating the
thread. Rename it from p->running to p->thread_created to clarify its
usage.
Fix the second issue by not clearing the flag at the multifd thread
exit. We don't have any use for that.
Note that these bugs are straight-forward logic issues and not race
conditions. There is still a gap for races to affect this code due to
multifd_send_cleanup() being allowed to run concurrently with the
thread creation loop. This issue is solved in the next patches.
Cc: qemu-stable <qemu-stable@nongnu.org>
Fixes: 29647140157a ("migration/tls: add support for multifd tls-handshake")
Reported-by: Avihai Horon <avihaih@nvidia.com>
Reported-by: <chenyuhui5@huawei.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/multifd.c | 27 ++++++++++++---------------
migration/multifd.h | 7 ++-----
2 files changed, 14 insertions(+), 20 deletions(-)
diff --git a/migration/multifd.c b/migration/multifd.c
index 8195c1daf3..515d88e04b 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -634,7 +634,7 @@ static void multifd_send_terminate_threads(void)
qemu_thread_join(&p->tls_thread);
}
- if (p->running) {
+ if (p->thread_created) {
qemu_thread_join(&p->thread);
}
}
@@ -862,7 +862,6 @@ out:
error_free(local_err);
}
- p->running = false;
rcu_unregister_thread();
migration_threads_remove(thread);
trace_multifd_send_thread_end(p->id, p->packets_sent, p->total_normal_pages);
@@ -953,6 +952,8 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
migration_ioc_register_yank(ioc);
p->registered_yank = true;
p->c = ioc;
+
+ p->thread_created = true;
qemu_thread_create(&p->thread, p->name, multifd_send_thread, p,
QEMU_THREAD_JOINABLE);
return true;
@@ -967,7 +968,6 @@ static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque)
trace_multifd_new_send_channel_async(p->id);
if (!qio_task_propagate_error(task, &local_err)) {
qio_channel_set_delay(ioc, false);
- p->running = true;
if (multifd_channel_connect(p, ioc, &local_err)) {
return;
}
@@ -1128,15 +1128,15 @@ void multifd_recv_cleanup(void)
for (i = 0; i < migrate_multifd_channels(); i++) {
MultiFDRecvParams *p = &multifd_recv_state->params[i];
- if (p->running) {
- /*
- * multifd_recv_thread may hung at MULTIFD_FLAG_SYNC handle code,
- * however try to wakeup it without harm in cleanup phase.
- */
- qemu_sem_post(&p->sem_sync);
- }
+ /*
+ * multifd_recv_thread may hung at MULTIFD_FLAG_SYNC handle code,
+ * however try to wakeup it without harm in cleanup phase.
+ */
+ qemu_sem_post(&p->sem_sync);
- qemu_thread_join(&p->thread);
+ if (p->thread_created) {
+ qemu_thread_join(&p->thread);
+ }
}
for (i = 0; i < migrate_multifd_channels(); i++) {
multifd_recv_cleanup_channel(&multifd_recv_state->params[i]);
@@ -1222,9 +1222,6 @@ static void *multifd_recv_thread(void *opaque)
multifd_recv_terminate_threads(local_err);
error_free(local_err);
}
- qemu_mutex_lock(&p->mutex);
- p->running = false;
- qemu_mutex_unlock(&p->mutex);
rcu_unregister_thread();
trace_multifd_recv_thread_end(p->id, p->packets_recved, p->total_normal_pages);
@@ -1330,7 +1327,7 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp)
p->c = ioc;
object_ref(OBJECT(ioc));
- p->running = true;
+ p->thread_created = true;
qemu_thread_create(&p->thread, p->name, multifd_recv_thread, p,
QEMU_THREAD_JOINABLE);
qatomic_inc(&multifd_recv_state->count);
diff --git a/migration/multifd.h b/migration/multifd.h
index 720c9d50db..7881980ee6 100644
--- a/migration/multifd.h
+++ b/migration/multifd.h
@@ -73,6 +73,7 @@ typedef struct {
char *name;
/* channel thread id */
QemuThread thread;
+ bool thread_created;
QemuThread tls_thread;
bool tls_thread_created;
/* communication channel */
@@ -93,8 +94,6 @@ typedef struct {
/* syncs main thread and channels */
QemuSemaphore sem_sync;
- /* is this channel thread running */
- bool running;
/* multifd flags for each packet */
uint32_t flags;
/*
@@ -143,6 +142,7 @@ typedef struct {
char *name;
/* channel thread id */
QemuThread thread;
+ bool thread_created;
/* communication channel */
QIOChannel *c;
/* packet allocated len */
@@ -157,8 +157,6 @@ typedef struct {
/* this mutex protects the following parameters */
QemuMutex mutex;
- /* is this channel thread running */
- bool running;
/* should this thread finish */
bool quit;
/* multifd flags for each packet */
@@ -217,4 +215,3 @@ static inline void multifd_send_prepare_header(MultiFDSendParams *p)
#endif
-
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 3/6] migration/multifd: Move multifd_send_setup error handling in to the function
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 1/6] migration/multifd: Join the TLS thread Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 2/6] migration/multifd: Remove p->running Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 4/6] migration/multifd: Move multifd_send_setup into migration thread Fabiano Rosas
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé
Hide the error handling inside multifd_send_setup to make it cleaner
for the next patch to move the function around.
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/migration.c | 6 +-----
migration/multifd.c | 24 +++++++++++++++++-------
migration/multifd.h | 2 +-
3 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/migration/migration.c b/migration/migration.c
index ba99772e76..2942f8cf42 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -3623,11 +3623,7 @@ void migrate_fd_connect(MigrationState *s, Error *error_in)
return;
}
- if (multifd_send_setup(&local_err) != 0) {
- migrate_set_error(s, local_err);
- error_report_err(local_err);
- migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
- MIGRATION_STATUS_FAILED);
+ if (!multifd_send_setup()) {
migrate_fd_cleanup(s);
return;
}
diff --git a/migration/multifd.c b/migration/multifd.c
index 515d88e04b..cc10be2c3f 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -985,14 +985,16 @@ static void multifd_new_send_channel_create(gpointer opaque)
socket_send_channel_create(multifd_new_send_channel_async, opaque);
}
-int multifd_send_setup(Error **errp)
+bool multifd_send_setup(void)
{
- int thread_count;
+ MigrationState *s = migrate_get_current();
+ Error *local_err = NULL;
+ int thread_count, ret = 0;
uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size();
uint8_t i;
if (!migrate_multifd()) {
- return 0;
+ return true;
}
thread_count = migrate_multifd_channels();
@@ -1026,14 +1028,22 @@ int multifd_send_setup(Error **errp)
for (i = 0; i < thread_count; i++) {
MultiFDSendParams *p = &multifd_send_state->params[i];
- int ret;
- ret = multifd_send_state->ops->send_setup(p, errp);
+ ret = multifd_send_state->ops->send_setup(p, &local_err);
if (ret) {
- return ret;
+ break;
}
}
- return 0;
+
+ if (ret) {
+ migrate_set_error(s, local_err);
+ error_report_err(local_err);
+ migrate_set_state(&s->state, MIGRATION_STATUS_SETUP,
+ MIGRATION_STATUS_FAILED);
+ return false;
+ }
+
+ return true;
}
struct {
diff --git a/migration/multifd.h b/migration/multifd.h
index 7881980ee6..8a1cad0996 100644
--- a/migration/multifd.h
+++ b/migration/multifd.h
@@ -13,7 +13,7 @@
#ifndef QEMU_MIGRATION_MULTIFD_H
#define QEMU_MIGRATION_MULTIFD_H
-int multifd_send_setup(Error **errp);
+bool multifd_send_setup(void);
void multifd_send_shutdown(void);
int multifd_recv_setup(Error **errp);
void multifd_recv_cleanup(void);
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 4/6] migration/multifd: Move multifd_send_setup into migration thread
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
` (2 preceding siblings ...)
2024-02-06 21:51 ` [PATCH v3 3/6] migration/multifd: Move multifd_send_setup error handling in to the function Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 5/6] migration/multifd: Unify multifd and TLS connection paths Fabiano Rosas
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé
We currently have an unfavorable situation around multifd channels
creation and the migration thread execution.
We create the multifd channels with qio_channel_socket_connect_async
-> qio_task_run_in_thread, but only connect them at the
multifd_new_send_channel_async callback, called from
qio_task_complete, which is registered as a glib event.
So at multifd_send_setup() we create the channels, but they will only
be actually usable after the whole multifd_send_setup() calling stack
returns back to the main loop. Which means that the migration thread
is already up and running without any possibility for the multifd
channels to be ready on time.
We currently rely on the channels-ready semaphore blocking
multifd_send_sync_main() until channels start to come up and release
it. However there have been bugs recently found when a channel's
creation fails and multifd_send_cleanup() is allowed to run while
other channels are still being created.
Let's start to organize this situation by moving the
multifd_send_setup() call into the migration thread. That way we
unblock the main-loop to dispatch the completion callbacks and
actually have a chance of getting the multifd channels ready for when
the migration thread needs them.
The next patches will deal with the synchronization aspects.
Note that this takes multifd_send_setup() out of the BQL.
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/migration.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/migration/migration.c b/migration/migration.c
index 2942f8cf42..0675e12c64 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -3315,6 +3315,10 @@ static void *migration_thread(void *opaque)
object_ref(OBJECT(s));
update_iteration_initial_status(s);
+ if (!multifd_send_setup()) {
+ goto out;
+ }
+
bql_lock();
qemu_savevm_state_header(s->to_dst_file);
bql_unlock();
@@ -3386,6 +3390,7 @@ static void *migration_thread(void *opaque)
urgent = migration_rate_limit();
}
+out:
trace_migration_thread_after_loop();
migration_iteration_finish(s);
object_unref(OBJECT(s));
@@ -3623,11 +3628,6 @@ void migrate_fd_connect(MigrationState *s, Error *error_in)
return;
}
- if (!multifd_send_setup()) {
- migrate_fd_cleanup(s);
- return;
- }
-
if (migrate_background_snapshot()) {
qemu_thread_create(&s->thread, "bg_snapshot",
bg_migration_thread, s, QEMU_THREAD_JOINABLE);
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 5/6] migration/multifd: Unify multifd and TLS connection paths
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
` (3 preceding siblings ...)
2024-02-06 21:51 ` [PATCH v3 4/6] migration/multifd: Move multifd_send_setup into migration thread Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 6/6] migration/multifd: Add a synchronization point for channel creation Fabiano Rosas
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé
During multifd channel creation (multifd_send_new_channel_async) when
TLS is enabled, the multifd_channel_connect function is called twice,
once to create the TLS handshake thread and another time after the
asynchrounous TLS handshake has finished.
This creates a slightly confusing call stack where
multifd_channel_connect() is called more times than the number of
channels. It also splits error handling between the two callers of
multifd_channel_connect() causing some code duplication. Lastly, it
gets in the way of having a single point to determine whether all
channel creation tasks have been initiated.
Refactor the code to move the reentrancy one level up at the
multifd_new_send_channel_async() level, de-duplicating the error
handling and allowing for the next patch to introduce a
synchronization point common to all the multifd channel creation,
regardless of TLS.
Note that the previous code would never fail once p->c had been set.
This patch changes this assumption, which affects refcounting, so add
comments around object_unref to explain the situation.
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/multifd.c | 83 ++++++++++++++++++++++-----------------------
1 file changed, 40 insertions(+), 43 deletions(-)
diff --git a/migration/multifd.c b/migration/multifd.c
index cc10be2c3f..339f2428f3 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -869,30 +869,7 @@ out:
return NULL;
}
-static bool multifd_channel_connect(MultiFDSendParams *p,
- QIOChannel *ioc,
- Error **errp);
-
-static void multifd_tls_outgoing_handshake(QIOTask *task,
- gpointer opaque)
-{
- MultiFDSendParams *p = opaque;
- QIOChannel *ioc = QIO_CHANNEL(qio_task_get_source(task));
- Error *err = NULL;
-
- if (!qio_task_propagate_error(task, &err)) {
- trace_multifd_tls_outgoing_handshake_complete(ioc);
- if (multifd_channel_connect(p, ioc, &err)) {
- return;
- }
- }
-
- trace_multifd_tls_outgoing_handshake_error(ioc, error_get_pretty(err));
-
- multifd_send_set_error(err);
- multifd_send_kick_main(p);
- error_free(err);
-}
+static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque);
static void *multifd_tls_handshake_thread(void *opaque)
{
@@ -900,7 +877,7 @@ static void *multifd_tls_handshake_thread(void *opaque)
QIOChannelTLS *tioc = QIO_CHANNEL_TLS(p->c);
qio_channel_tls_handshake(tioc,
- multifd_tls_outgoing_handshake,
+ multifd_new_send_channel_async,
p,
NULL,
NULL);
@@ -920,6 +897,10 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
return false;
}
+ /*
+ * Ownership of the socket channel now transfers to the newly
+ * created TLS channel, which has already taken a reference.
+ */
object_unref(OBJECT(ioc));
trace_multifd_tls_outgoing_handshake_start(ioc, tioc, hostname);
qio_channel_set_name(QIO_CHANNEL(tioc), "multifd-tls-outgoing");
@@ -936,18 +917,7 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
QIOChannel *ioc,
Error **errp)
{
- trace_multifd_set_outgoing_channel(
- ioc, object_get_typename(OBJECT(ioc)),
- migrate_get_current()->hostname);
-
- if (migrate_channel_requires_tls_upgrade(ioc)) {
- /*
- * tls_channel_connect will call back to this
- * function after the TLS handshake,
- * so we mustn't call multifd_send_thread until then
- */
- return multifd_tls_channel_connect(p, ioc, errp);
- }
+ qio_channel_set_delay(ioc, false);
migration_ioc_register_yank(ioc);
p->registered_yank = true;
@@ -959,24 +929,51 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
return true;
}
+/*
+ * When TLS is enabled this function is called once to establish the
+ * TLS connection and a second time after the TLS handshake to create
+ * the multifd channel. Without TLS it goes straight into the channel
+ * creation.
+ */
static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque)
{
MultiFDSendParams *p = opaque;
QIOChannel *ioc = QIO_CHANNEL(qio_task_get_source(task));
Error *local_err = NULL;
+ bool ret;
trace_multifd_new_send_channel_async(p->id);
- if (!qio_task_propagate_error(task, &local_err)) {
- qio_channel_set_delay(ioc, false);
- if (multifd_channel_connect(p, ioc, &local_err)) {
- return;
- }
+
+ if (qio_task_propagate_error(task, &local_err)) {
+ ret = false;
+ goto out;
+ }
+
+ trace_multifd_set_outgoing_channel(ioc, object_get_typename(OBJECT(ioc)),
+ migrate_get_current()->hostname);
+
+ if (migrate_channel_requires_tls_upgrade(ioc)) {
+ ret = multifd_tls_channel_connect(p, ioc, &local_err);
+ } else {
+ ret = multifd_channel_connect(p, ioc, &local_err);
+ }
+
+ if (ret) {
+ return;
}
+out:
trace_multifd_new_send_channel_async_error(p->id, local_err);
multifd_send_set_error(local_err);
multifd_send_kick_main(p);
- object_unref(OBJECT(ioc));
+ if (!p->c) {
+ /*
+ * If no channel has been created, drop the initial
+ * reference. Otherwise cleanup happens at
+ * multifd_send_channel_destroy()
+ */
+ object_unref(OBJECT(ioc));
+ }
error_free(local_err);
}
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 6/6] migration/multifd: Add a synchronization point for channel creation
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
` (4 preceding siblings ...)
2024-02-06 21:51 ` [PATCH v3 5/6] migration/multifd: Unify multifd and TLS connection paths Fabiano Rosas
@ 2024-02-06 21:51 ` Fabiano Rosas
2024-02-07 2:29 ` [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Peter Xu
2024-02-08 3:01 ` Peter Xu
7 siblings, 0 replies; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-06 21:51 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Avihai Horon, Daniel P . Berrangé
It is possible that one of the multifd channels fails to be created at
multifd_new_send_channel_async() while the rest of the channel
creation tasks are still in flight.
This could lead to multifd_save_cleanup() executing the
qemu_thread_join() loop too early and not waiting for the threads
which haven't been created yet, leading to the freeing of resources
that the newly created threads will try to access and crash.
Add a synchronization point after which there will be no attempts at
thread creation and therefore calling multifd_save_cleanup() past that
point will ensure it properly waits for the threads.
A note about performance: Prior to this patch, if a channel took too
long to be established, other channels could finish connecting first
and already start taking load. Now we're bounded by the
slowest-connecting channel.
Reported-by: Avihai Horon <avihaih@nvidia.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/multifd.c | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/migration/multifd.c b/migration/multifd.c
index 339f2428f3..ee77047031 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -62,6 +62,11 @@ struct {
* Make it easy for now.
*/
uintptr_t packet_num;
+ /*
+ * Synchronization point past which no more channels will be
+ * created.
+ */
+ QemuSemaphore channels_created;
/* send channels ready */
QemuSemaphore channels_ready;
/*
@@ -622,10 +627,6 @@ static void multifd_send_terminate_threads(void)
/*
* Finally recycle all the threads.
- *
- * TODO: p->running is still buggy, e.g. we can reach here without the
- * corresponding multifd_new_send_channel_async() get invoked yet,
- * then a new thread can even be created after this function returns.
*/
for (i = 0; i < migrate_multifd_channels(); i++) {
MultiFDSendParams *p = &multifd_send_state->params[i];
@@ -670,6 +671,7 @@ static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp)
static void multifd_send_cleanup_state(void)
{
+ qemu_sem_destroy(&multifd_send_state->channels_created);
qemu_sem_destroy(&multifd_send_state->channels_ready);
g_free(multifd_send_state->params);
multifd_send_state->params = NULL;
@@ -954,18 +956,26 @@ static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque)
if (migrate_channel_requires_tls_upgrade(ioc)) {
ret = multifd_tls_channel_connect(p, ioc, &local_err);
+ if (ret) {
+ return;
+ }
} else {
ret = multifd_channel_connect(p, ioc, &local_err);
}
+out:
+ /*
+ * Here we're not interested whether creation succeeded, only that
+ * it happened at all.
+ */
+ qemu_sem_post(&multifd_send_state->channels_created);
+
if (ret) {
return;
}
-out:
trace_multifd_new_send_channel_async_error(p->id, local_err);
multifd_send_set_error(local_err);
- multifd_send_kick_main(p);
if (!p->c) {
/*
* If no channel has been created, drop the initial
@@ -998,6 +1008,7 @@ bool multifd_send_setup(void)
multifd_send_state = g_malloc0(sizeof(*multifd_send_state));
multifd_send_state->params = g_new0(MultiFDSendParams, thread_count);
multifd_send_state->pages = multifd_pages_init(page_count);
+ qemu_sem_init(&multifd_send_state->channels_created, 0);
qemu_sem_init(&multifd_send_state->channels_ready, 0);
qatomic_set(&multifd_send_state->exiting, 0);
multifd_send_state->ops = multifd_ops[migrate_multifd_compression()];
@@ -1023,6 +1034,15 @@ bool multifd_send_setup(void)
multifd_new_send_channel_create(p);
}
+ /*
+ * Wait until channel creation has started for all channels. The
+ * creation can still fail, but no more channels will be created
+ * past this point.
+ */
+ for (i = 0; i < thread_count; i++) {
+ qemu_sem_wait(&multifd_send_state->channels_created);
+ }
+
for (i = 0; i < thread_count; i++) {
MultiFDSendParams *p = &multifd_send_state->params[i];
--
2.35.3
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
` (5 preceding siblings ...)
2024-02-06 21:51 ` [PATCH v3 6/6] migration/multifd: Add a synchronization point for channel creation Fabiano Rosas
@ 2024-02-07 2:29 ` Peter Xu
2024-02-07 13:31 ` Fabiano Rosas
2024-02-08 3:01 ` Peter Xu
7 siblings, 1 reply; 11+ messages in thread
From: Peter Xu @ 2024-02-07 2:29 UTC (permalink / raw)
To: Fabiano Rosas; +Cc: qemu-devel, Avihai Horon, Daniel P . Berrangé
On Tue, Feb 06, 2024 at 06:51:12PM -0300, Fabiano Rosas wrote:
> Based-on: 20240202102857.110210-1-peterx@redhat.com
> [PATCH v2 00/23] migration/multifd: Refactor ->send_prepare() and cleanups
> https://lore.kernel.org/r/20240202102857.110210-1-peterx@redhat.com
>
> Hi,
>
> For v3 I fixed the refcounting issue spotted by Avihai. The situation
> there is a bit clunky due to historical reasons. The gist is that we
> have an assumption that channel creation never fails after p->c has
> been set, so when 'p->c == NULL' we have to unref and when 'p->c !=
> NULL' the cleanup code will do the unref.
Yes, this looks good to me. That's a good catch.
I'll leave at least one more day for Avihai and/or Dan to have another
look. My r-b persist as of now on patch 5.
Actually I think the conditional unref is slightly tricky, but it's not its
own fault, IMHO, OTOH it's more about a1af605bd5ad where p->c is slightly
abused. My understanding is we can avoid that conditional unref with below
patch 1 as a cleanup (on top of this series). Then patch 2 comes all
alongside.
We don't need to rush on these, though, we should fix the thread race first
because multiple of us hit it, and all cleanups can be done later.
=====
From 0830819d86e08c5175d6669505aa712a0a09717f Mon Sep 17 00:00:00 2001
From: Peter Xu <peterx@redhat.com>
Date: Wed, 7 Feb 2024 10:08:35 +0800
Subject: [PATCH 1/2] migration/multifd: Cleanup TLS iochannel referencing
Commit a1af605bd5 ("migration/multifd: fix hangup with TLS-Multifd due to
blocking handshake") introduced a thread for TLS channels, which will
resolve the issue on blocking the main thread. However in the same commit
p->c is slightly abused just to be able to pass over the pointer "p" into
the thread.
That's the major reason we'll need to conditionally free the io channel in
the fault paths.
To clean it up, using a separate structure to pass over both "p" and "tioc"
in the tls handshake thread. Then we can make it a rule that p->c will
never be set until the channel is completely setup. With that, we can drop
the tricky conditional unref of the io channel in the error path.
Signed-off-by: Peter Xu <peterx@redhat.com>
---
migration/multifd.c | 37 +++++++++++++++++++++++--------------
1 file changed, 23 insertions(+), 14 deletions(-)
diff --git a/migration/multifd.c b/migration/multifd.c
index adfe8c9a0a..4a85a6b7b3 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -873,16 +873,22 @@ out:
static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque);
+typedef struct {
+ MultiFDSendParams *p;
+ QIOChannelTLS *tioc;
+} MultiFDTLSThreadArgs;
+
static void *multifd_tls_handshake_thread(void *opaque)
{
- MultiFDSendParams *p = opaque;
- QIOChannelTLS *tioc = QIO_CHANNEL_TLS(p->c);
+ MultiFDTLSThreadArgs *args = opaque;
- qio_channel_tls_handshake(tioc,
+ qio_channel_tls_handshake(args->tioc,
multifd_new_send_channel_async,
- p,
+ args->p,
NULL,
NULL);
+ g_free(args);
+
return NULL;
}
@@ -892,6 +898,7 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
{
MigrationState *s = migrate_get_current();
const char *hostname = s->hostname;
+ MultiFDTLSThreadArgs *args;
QIOChannelTLS *tioc;
tioc = migration_tls_client_create(ioc, hostname, errp);
@@ -906,11 +913,14 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
object_unref(OBJECT(ioc));
trace_multifd_tls_outgoing_handshake_start(ioc, tioc, hostname);
qio_channel_set_name(QIO_CHANNEL(tioc), "multifd-tls-outgoing");
- p->c = QIO_CHANNEL(tioc);
+
+ args = g_new0(MultiFDTLSThreadArgs, 1);
+ args->tioc = tioc;
+ args->p = p;
p->tls_thread_created = true;
qemu_thread_create(&p->tls_thread, "multifd-tls-handshake-worker",
- multifd_tls_handshake_thread, p,
+ multifd_tls_handshake_thread, args,
QEMU_THREAD_JOINABLE);
return true;
}
@@ -923,6 +933,7 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
migration_ioc_register_yank(ioc);
p->registered_yank = true;
+ /* Setup p->c only if the channel is completely setup */
p->c = ioc;
p->thread_created = true;
@@ -976,14 +987,12 @@ out:
trace_multifd_new_send_channel_async_error(p->id, local_err);
multifd_send_set_error(local_err);
- if (!p->c) {
- /*
- * If no channel has been created, drop the initial
- * reference. Otherwise cleanup happens at
- * multifd_send_channel_destroy()
- */
- object_unref(OBJECT(ioc));
- }
+ /*
+ * For error cases (TLS or non-TLS), IO channel is always freed here
+ * rather than when cleanup multifd: since p->c is not set, multifd
+ * cleanup code doesn't even know its existance.
+ */
+ object_unref(OBJECT(ioc));
error_free(local_err);
}
--
2.43.0
=====
From 9e574c3216f6459e3a808096d905e2554d962cad Mon Sep 17 00:00:00 2001
From: Peter Xu <peterx@redhat.com>
Date: Wed, 7 Feb 2024 10:24:39 +0800
Subject: [PATCH 2/2] migration/multifd: Drop registered_yank
With a clear definition of p->c protocol, where we only set it up if the
channel is fully established (TLS or non-TLS), registered_yank boolean will
have equal meaning of "p->c != NULL".
Drop registered_yank by checking p->c instead.
Signed-off-by: Peter Xu <peterx@redhat.com>
---
migration/multifd.h | 2 --
migration/multifd.c | 7 +++----
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/migration/multifd.h b/migration/multifd.h
index 8a1cad0996..b3fe27ae93 100644
--- a/migration/multifd.h
+++ b/migration/multifd.h
@@ -78,8 +78,6 @@ typedef struct {
bool tls_thread_created;
/* communication channel */
QIOChannel *c;
- /* is the yank function registered */
- bool registered_yank;
/* packet allocated len */
uint32_t packet_len;
/* guest page size */
diff --git a/migration/multifd.c b/migration/multifd.c
index 4a85a6b7b3..278453cf84 100644
--- a/migration/multifd.c
+++ b/migration/multifd.c
@@ -648,11 +648,11 @@ static int multifd_send_channel_destroy(QIOChannel *send)
static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp)
{
- if (p->registered_yank) {
+ if (p->c) {
migration_ioc_unregister_yank(p->c);
+ multifd_send_channel_destroy(p->c);
+ p->c = NULL;
}
- multifd_send_channel_destroy(p->c);
- p->c = NULL;
qemu_sem_destroy(&p->sem);
qemu_sem_destroy(&p->sem_sync);
g_free(p->name);
@@ -932,7 +932,6 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
qio_channel_set_delay(ioc, false);
migration_ioc_register_yank(ioc);
- p->registered_yank = true;
/* Setup p->c only if the channel is completely setup */
p->c = ioc;
--
2.43.0
====
Thanks,
--
Peter Xu
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races
2024-02-07 2:29 ` [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Peter Xu
@ 2024-02-07 13:31 ` Fabiano Rosas
2024-02-08 3:32 ` Peter Xu
0 siblings, 1 reply; 11+ messages in thread
From: Fabiano Rosas @ 2024-02-07 13:31 UTC (permalink / raw)
To: Peter Xu; +Cc: qemu-devel, Avihai Horon, Daniel P . Berrangé
Peter Xu <peterx@redhat.com> writes:
> On Tue, Feb 06, 2024 at 06:51:12PM -0300, Fabiano Rosas wrote:
>> Based-on: 20240202102857.110210-1-peterx@redhat.com
>> [PATCH v2 00/23] migration/multifd: Refactor ->send_prepare() and cleanups
>> https://lore.kernel.org/r/20240202102857.110210-1-peterx@redhat.com
>>
>> Hi,
>>
>> For v3 I fixed the refcounting issue spotted by Avihai. The situation
>> there is a bit clunky due to historical reasons. The gist is that we
>> have an assumption that channel creation never fails after p->c has
>> been set, so when 'p->c == NULL' we have to unref and when 'p->c !=
>> NULL' the cleanup code will do the unref.
>
> Yes, this looks good to me. That's a good catch.
>
> I'll leave at least one more day for Avihai and/or Dan to have another
> look. My r-b persist as of now on patch 5.
>
> Actually I think the conditional unref is slightly tricky, but it's not its
> own fault, IMHO, OTOH it's more about a1af605bd5ad where p->c is slightly
> abused. My understanding is we can avoid that conditional unref with below
> patch 1 as a cleanup (on top of this series). Then patch 2 comes all
> alongside.
Yes, I even wrote some code to always set p->c and leave the unref to
the cleanup routine. Doing reference counting in the middle of the code
like that leaves us exposed to new code relying on p->c being valid
during cleanup. There's already yank and the cleanup itself which expect
p->c to be valid.
However, I couldn't get past the uglyness of overwriting p->c, so I kept
the changes minimal for this version.
I'm also wondering whether we can remove the TLS handshake thread
altogether now that we moved the multifd_send_setup call into the
migration thread. My (poor) understanding is that a1af605bd5ad hit the
issue that the QIOTask completion would just execute after
multifd_save_setup returned. Otherwise I don't see how adding a thread
to an already async task would have helped. But I need to think about
that a bit more.
>
> We don't need to rush on these, though, we should fix the thread race
>first because multiple of us hit it, and all cleanups can be done
>later.
I said we should not merge these two changes right now, but I take that
back. I'll leave it up to you, there doesn't seem to be that much impact
in adding them.
>
> =====
> From 0830819d86e08c5175d6669505aa712a0a09717f Mon Sep 17 00:00:00 2001
> From: Peter Xu <peterx@redhat.com>
> Date: Wed, 7 Feb 2024 10:08:35 +0800
> Subject: [PATCH 1/2] migration/multifd: Cleanup TLS iochannel referencing
>
> Commit a1af605bd5 ("migration/multifd: fix hangup with TLS-Multifd due to
> blocking handshake") introduced a thread for TLS channels, which will
> resolve the issue on blocking the main thread. However in the same commit
> p->c is slightly abused just to be able to pass over the pointer "p" into
> the thread.
>
> That's the major reason we'll need to conditionally free the io channel in
> the fault paths.
>
> To clean it up, using a separate structure to pass over both "p" and "tioc"
> in the tls handshake thread. Then we can make it a rule that p->c will
> never be set until the channel is completely setup. With that, we can drop
> the tricky conditional unref of the io channel in the error path.
>
> Signed-off-by: Peter Xu <peterx@redhat.com>
> ---
> migration/multifd.c | 37 +++++++++++++++++++++++--------------
> 1 file changed, 23 insertions(+), 14 deletions(-)
>
> diff --git a/migration/multifd.c b/migration/multifd.c
> index adfe8c9a0a..4a85a6b7b3 100644
> --- a/migration/multifd.c
> +++ b/migration/multifd.c
> @@ -873,16 +873,22 @@ out:
>
> static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque);
>
> +typedef struct {
> + MultiFDSendParams *p;
> + QIOChannelTLS *tioc;
> +} MultiFDTLSThreadArgs;
> +
> static void *multifd_tls_handshake_thread(void *opaque)
> {
> - MultiFDSendParams *p = opaque;
> - QIOChannelTLS *tioc = QIO_CHANNEL_TLS(p->c);
> + MultiFDTLSThreadArgs *args = opaque;
>
> - qio_channel_tls_handshake(tioc,
> + qio_channel_tls_handshake(args->tioc,
> multifd_new_send_channel_async,
> - p,
> + args->p,
> NULL,
> NULL);
> + g_free(args);
> +
> return NULL;
> }
>
> @@ -892,6 +898,7 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
> {
> MigrationState *s = migrate_get_current();
> const char *hostname = s->hostname;
> + MultiFDTLSThreadArgs *args;
> QIOChannelTLS *tioc;
>
> tioc = migration_tls_client_create(ioc, hostname, errp);
> @@ -906,11 +913,14 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
> object_unref(OBJECT(ioc));
> trace_multifd_tls_outgoing_handshake_start(ioc, tioc, hostname);
> qio_channel_set_name(QIO_CHANNEL(tioc), "multifd-tls-outgoing");
> - p->c = QIO_CHANNEL(tioc);
This assignment also meant multifd_send_channel_destroy() would call
object_unref on the tioc object. Removing it means
qio_channel_tls_finalize() will never be called.
It also means the socket channel (ioc) refcount will be decremented one
too many times, due to the object_unref above^.
So I think we should find a point where tioc is not needed anymore and
unref it and remove the object_unref(ioc) above.
Right?
> +
> + args = g_new0(MultiFDTLSThreadArgs, 1);
> + args->tioc = tioc;
> + args->p = p;
>
> p->tls_thread_created = true;
> qemu_thread_create(&p->tls_thread, "multifd-tls-handshake-worker",
> - multifd_tls_handshake_thread, p,
> + multifd_tls_handshake_thread, args,
> QEMU_THREAD_JOINABLE);
> return true;
> }
> @@ -923,6 +933,7 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
>
> migration_ioc_register_yank(ioc);
> p->registered_yank = true;
> + /* Setup p->c only if the channel is completely setup */
> p->c = ioc;
>
> p->thread_created = true;
> @@ -976,14 +987,12 @@ out:
>
> trace_multifd_new_send_channel_async_error(p->id, local_err);
> multifd_send_set_error(local_err);
> - if (!p->c) {
> - /*
> - * If no channel has been created, drop the initial
> - * reference. Otherwise cleanup happens at
> - * multifd_send_channel_destroy()
> - */
> - object_unref(OBJECT(ioc));
> - }
> + /*
> + * For error cases (TLS or non-TLS), IO channel is always freed here
> + * rather than when cleanup multifd: since p->c is not set, multifd
> + * cleanup code doesn't even know its existance.
> + */
> + object_unref(OBJECT(ioc));
> error_free(local_err);
> }
>
> --
> 2.43.0
> =====
> From 9e574c3216f6459e3a808096d905e2554d962cad Mon Sep 17 00:00:00 2001
> From: Peter Xu <peterx@redhat.com>
> Date: Wed, 7 Feb 2024 10:24:39 +0800
> Subject: [PATCH 2/2] migration/multifd: Drop registered_yank
>
> With a clear definition of p->c protocol, where we only set it up if the
> channel is fully established (TLS or non-TLS), registered_yank boolean will
> have equal meaning of "p->c != NULL".
>
> Drop registered_yank by checking p->c instead.
>
> Signed-off-by: Peter Xu <peterx@redhat.com>
This one looks good. I know it depends on the previous patch, but if you
plan to add it:
Reviewed-by: Fabiano Rosas <farosas@suse.de>
> ---
> migration/multifd.h | 2 --
> migration/multifd.c | 7 +++----
> 2 files changed, 3 insertions(+), 6 deletions(-)
>
> diff --git a/migration/multifd.h b/migration/multifd.h
> index 8a1cad0996..b3fe27ae93 100644
> --- a/migration/multifd.h
> +++ b/migration/multifd.h
> @@ -78,8 +78,6 @@ typedef struct {
> bool tls_thread_created;
> /* communication channel */
> QIOChannel *c;
> - /* is the yank function registered */
> - bool registered_yank;
> /* packet allocated len */
> uint32_t packet_len;
> /* guest page size */
> diff --git a/migration/multifd.c b/migration/multifd.c
> index 4a85a6b7b3..278453cf84 100644
> --- a/migration/multifd.c
> +++ b/migration/multifd.c
> @@ -648,11 +648,11 @@ static int multifd_send_channel_destroy(QIOChannel *send)
>
> static bool multifd_send_cleanup_channel(MultiFDSendParams *p, Error **errp)
> {
> - if (p->registered_yank) {
> + if (p->c) {
> migration_ioc_unregister_yank(p->c);
> + multifd_send_channel_destroy(p->c);
> + p->c = NULL;
> }
> - multifd_send_channel_destroy(p->c);
> - p->c = NULL;
> qemu_sem_destroy(&p->sem);
> qemu_sem_destroy(&p->sem_sync);
> g_free(p->name);
> @@ -932,7 +932,6 @@ static bool multifd_channel_connect(MultiFDSendParams *p,
> qio_channel_set_delay(ioc, false);
>
> migration_ioc_register_yank(ioc);
> - p->registered_yank = true;
> /* Setup p->c only if the channel is completely setup */
> p->c = ioc;
>
> --
> 2.43.0
> ====
>
> Thanks,
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
` (6 preceding siblings ...)
2024-02-07 2:29 ` [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Peter Xu
@ 2024-02-08 3:01 ` Peter Xu
7 siblings, 0 replies; 11+ messages in thread
From: Peter Xu @ 2024-02-08 3:01 UTC (permalink / raw)
To: Fabiano Rosas; +Cc: qemu-devel, Avihai Horon, Daniel P . Berrangé
On Tue, Feb 06, 2024 at 06:51:12PM -0300, Fabiano Rosas wrote:
> Based-on: 20240202102857.110210-1-peterx@redhat.com
> [PATCH v2 00/23] migration/multifd: Refactor ->send_prepare() and cleanups
> https://lore.kernel.org/r/20240202102857.110210-1-peterx@redhat.com
>
> Hi,
>
> For v3 I fixed the refcounting issue spotted by Avihai. The situation
> there is a bit clunky due to historical reasons. The gist is that we
> have an assumption that channel creation never fails after p->c has
> been set, so when 'p->c == NULL' we have to unref and when 'p->c !=
> NULL' the cleanup code will do the unref.
>
> CI run: https://gitlab.com/farosas/qemu/-/pipelines/1166889341
Apologize if I queue this too fast, but i'll disappear tomorrow, so I want
to have this thread race fixed soon. I hope that's already complete from
angle of all race can happen, but if otherwise we work on top.
queued, thanks.
--
Peter Xu
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races
2024-02-07 13:31 ` Fabiano Rosas
@ 2024-02-08 3:32 ` Peter Xu
0 siblings, 0 replies; 11+ messages in thread
From: Peter Xu @ 2024-02-08 3:32 UTC (permalink / raw)
To: Fabiano Rosas; +Cc: qemu-devel, Avihai Horon, Daniel P . Berrangé
On Wed, Feb 07, 2024 at 10:31:51AM -0300, Fabiano Rosas wrote:
> Peter Xu <peterx@redhat.com> writes:
>
> > On Tue, Feb 06, 2024 at 06:51:12PM -0300, Fabiano Rosas wrote:
> >> Based-on: 20240202102857.110210-1-peterx@redhat.com
> >> [PATCH v2 00/23] migration/multifd: Refactor ->send_prepare() and cleanups
> >> https://lore.kernel.org/r/20240202102857.110210-1-peterx@redhat.com
> >>
> >> Hi,
> >>
> >> For v3 I fixed the refcounting issue spotted by Avihai. The situation
> >> there is a bit clunky due to historical reasons. The gist is that we
> >> have an assumption that channel creation never fails after p->c has
> >> been set, so when 'p->c == NULL' we have to unref and when 'p->c !=
> >> NULL' the cleanup code will do the unref.
> >
> > Yes, this looks good to me. That's a good catch.
> >
> > I'll leave at least one more day for Avihai and/or Dan to have another
> > look. My r-b persist as of now on patch 5.
> >
> > Actually I think the conditional unref is slightly tricky, but it's not its
> > own fault, IMHO, OTOH it's more about a1af605bd5ad where p->c is slightly
> > abused. My understanding is we can avoid that conditional unref with below
> > patch 1 as a cleanup (on top of this series). Then patch 2 comes all
> > alongside.
>
> Yes, I even wrote some code to always set p->c and leave the unref to
> the cleanup routine. Doing reference counting in the middle of the code
> like that leaves us exposed to new code relying on p->c being valid
> during cleanup. There's already yank and the cleanup itself which expect
> p->c to be valid.
>
> However, I couldn't get past the uglyness of overwriting p->c, so I kept
> the changes minimal for this version.
Yep. The good part of "only set p->c if channel fully established" is that
then the migration thread fully owns the ioc as long as set, and no
overwritting possible.
>
> I'm also wondering whether we can remove the TLS handshake thread
> altogether now that we moved the multifd_send_setup call into the
> migration thread. My (poor) understanding is that a1af605bd5ad hit the
> issue that the QIOTask completion would just execute after
> multifd_save_setup returned. Otherwise I don't see how adding a thread
> to an already async task would have helped. But I need to think about
> that a bit more.
It could be even simpler than that, iiuc. Note that in the stack showed in
that commit message, it hasn't even reached the async handling:
Src: (multifd_send_0)
multifd_channel_connect
multifd_tls_channel_connect
multifd_tls_channel_connect
qio_channel_tls_handshake
qio_channel_tls_handshake_task <---- async handling provided here..
qcrypto_tls_session_handshake
gnutls_handshake <-------------- but we're still at sync phase..
...
recvmsg (Blocking I/O waiting for response)
IMHO it'll be much easier if we can remove those threads. Please keep the
adventure there, just a heads-up that the async paths seem to have a close
dependency so far on gmainloop contexts, while the migration thread may not
provide that anymore (and I hope we don't introduce anything either; I
think it's better we stick with a full threaded model in migration rather
than event based).
>
> >
> > We don't need to rush on these, though, we should fix the thread race
> >first because multiple of us hit it, and all cleanups can be done
> >later.
>
> I said we should not merge these two changes right now, but I take that
> back. I'll leave it up to you, there doesn't seem to be that much impact
> in adding them.
Thanks. I just sent the pull without them, as I don't yet have plan to
queue them so soon; I'll be accused to abuse the maintainership. :-)
If you think worthwhile, I can still repost them as formal patches later
and put there on the list. If your explore on a bigger hammer works then I
think we can ignore these two patches. But if you or anyone thinks we
could have these as good cleanups, we can also merge them first for 9.0 and
leave whatever else for later.
>
> >
> > =====
> > From 0830819d86e08c5175d6669505aa712a0a09717f Mon Sep 17 00:00:00 2001
> > From: Peter Xu <peterx@redhat.com>
> > Date: Wed, 7 Feb 2024 10:08:35 +0800
> > Subject: [PATCH 1/2] migration/multifd: Cleanup TLS iochannel referencing
> >
> > Commit a1af605bd5 ("migration/multifd: fix hangup with TLS-Multifd due to
> > blocking handshake") introduced a thread for TLS channels, which will
> > resolve the issue on blocking the main thread. However in the same commit
> > p->c is slightly abused just to be able to pass over the pointer "p" into
> > the thread.
> >
> > That's the major reason we'll need to conditionally free the io channel in
> > the fault paths.
> >
> > To clean it up, using a separate structure to pass over both "p" and "tioc"
> > in the tls handshake thread. Then we can make it a rule that p->c will
> > never be set until the channel is completely setup. With that, we can drop
> > the tricky conditional unref of the io channel in the error path.
> >
> > Signed-off-by: Peter Xu <peterx@redhat.com>
> > ---
> > migration/multifd.c | 37 +++++++++++++++++++++++--------------
> > 1 file changed, 23 insertions(+), 14 deletions(-)
> >
> > diff --git a/migration/multifd.c b/migration/multifd.c
> > index adfe8c9a0a..4a85a6b7b3 100644
> > --- a/migration/multifd.c
> > +++ b/migration/multifd.c
> > @@ -873,16 +873,22 @@ out:
> >
> > static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque);
> >
> > +typedef struct {
> > + MultiFDSendParams *p;
> > + QIOChannelTLS *tioc;
> > +} MultiFDTLSThreadArgs;
> > +
> > static void *multifd_tls_handshake_thread(void *opaque)
> > {
> > - MultiFDSendParams *p = opaque;
> > - QIOChannelTLS *tioc = QIO_CHANNEL_TLS(p->c);
> > + MultiFDTLSThreadArgs *args = opaque;
> >
> > - qio_channel_tls_handshake(tioc,
> > + qio_channel_tls_handshake(args->tioc,
> > multifd_new_send_channel_async,
> > - p,
> > + args->p,
> > NULL,
> > NULL);
> > + g_free(args);
> > +
> > return NULL;
> > }
> >
> > @@ -892,6 +898,7 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
> > {
> > MigrationState *s = migrate_get_current();
> > const char *hostname = s->hostname;
> > + MultiFDTLSThreadArgs *args;
> > QIOChannelTLS *tioc;
> >
> > tioc = migration_tls_client_create(ioc, hostname, errp);
> > @@ -906,11 +913,14 @@ static bool multifd_tls_channel_connect(MultiFDSendParams *p,
> > object_unref(OBJECT(ioc));
> > trace_multifd_tls_outgoing_handshake_start(ioc, tioc, hostname);
> > qio_channel_set_name(QIO_CHANNEL(tioc), "multifd-tls-outgoing");
> > - p->c = QIO_CHANNEL(tioc);
>
> This assignment also meant multifd_send_channel_destroy() would call
> object_unref on the tioc object. Removing it means
> qio_channel_tls_finalize() will never be called.
I think it'll still be properly released / called?
Just to make sure we're on the same page: qio_channel_tls_finalize() will
be called on the last ref released. Then let's discuss error paths on how
this patch affects the last unref.
Before this patch, it will be called until multifd_send_channel_destroy()
as you said when cleanup, because that did the last unref (while your patch
has the "if" where you'll skip the cleanup even if error):
multifd_send_channel_destroy
socket_send_channel_destroy
object_unref
After this patch, that object_unref() will be called in
multifd_channel_connect() directly, and the cleanup code, seeing
p->c==NULL, does nothing later.
>
> It also means the socket channel (ioc) refcount will be decremented one
> too many times, due to the object_unref above^.
>
> So I think we should find a point where tioc is not needed anymore and
> unref it and remove the object_unref(ioc) above.
>
> Right?
[...]
> > From 9e574c3216f6459e3a808096d905e2554d962cad Mon Sep 17 00:00:00 2001
> > From: Peter Xu <peterx@redhat.com>
> > Date: Wed, 7 Feb 2024 10:24:39 +0800
> > Subject: [PATCH 2/2] migration/multifd: Drop registered_yank
> >
> > With a clear definition of p->c protocol, where we only set it up if the
> > channel is fully established (TLS or non-TLS), registered_yank boolean will
> > have equal meaning of "p->c != NULL".
> >
> > Drop registered_yank by checking p->c instead.
> >
> > Signed-off-by: Peter Xu <peterx@redhat.com>
>
> This one looks good. I know it depends on the previous patch, but if you
> plan to add it:
>
> Reviewed-by: Fabiano Rosas <farosas@suse.de>
I'll pick it up when posting the formal patches, thanks.
Let's see whether above will address your concern. If not, we can move the
discussion over to that thread.
--
Peter Xu
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2024-02-08 3:33 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-06 21:51 [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 1/6] migration/multifd: Join the TLS thread Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 2/6] migration/multifd: Remove p->running Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 3/6] migration/multifd: Move multifd_send_setup error handling in to the function Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 4/6] migration/multifd: Move multifd_send_setup into migration thread Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 5/6] migration/multifd: Unify multifd and TLS connection paths Fabiano Rosas
2024-02-06 21:51 ` [PATCH v3 6/6] migration/multifd: Add a synchronization point for channel creation Fabiano Rosas
2024-02-07 2:29 ` [PATCH v3 0/6] migration/multifd: Fix channel creation vs. cleanup races Peter Xu
2024-02-07 13:31 ` Fabiano Rosas
2024-02-08 3:32 ` Peter Xu
2024-02-08 3:01 ` Peter Xu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).