Fix qemu-iotests failures
cover Fix qemu-iotests failures
This reverts the recent fix for the case of qemu-iotests 200 (block job completion races when using a separate I/O thread) that only fixed a symptom and caused many regressions, and replaces it with a new solution that hopefully attacks the right root cause.
The series also contains a bonus fix for a bug that was uncovered by the different timing with the patches that are reverted here.
Kevin Wolf (4):
Revert "coroutine: abort if we try to schedule or enter a pending
coroutine"
Revert "blockjob: do not allow coroutine double entry or
entry-after-completion"
coroutine: Cancel aio_co_schedule() on direct entry
block: Expect graph changes in bdrv_parent_drained_begin/end
include/block/blockjob_int.h | 3 +-- include/qemu/coroutine_int.h | 14 ++++---------- block/io.c | 8 ++++---- blockjob.c | 7 ++----- util/async.c | 26 +++++++++++++------------- util/qemu-coroutine-sleep.c | 12 ------------ util/qemu-coroutine.c | 31 +++++++++++++++++-------------- 7 files changed, 41 insertions(+), 60 deletions(-)
Signature · 1 line
2.13.6
PATCH 1/4 Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
This reverts commit 6133b39f3c36623425a6ede9e89d93175fde15cd.
The commit checked conditions that would expose a bug, but there is no real reason to forbid them apart from the bug, which we'll fix in a minute.
In particular, reentering a coroutine during co_aio_sleep_ns() is fine; the function is explicitly written to allow this.
aio_co_schedule() can indeed conflict with direct coroutine invocations, but this is exactky what we want to fix, so remove that check again, too.
--- include/qemu/coroutine_int.h | 13 +++---------- util/async.c | 13 ------------- util/qemu-coroutine-sleep.c | 12 ------------ util/qemu-coroutine.c | 14 -------------- 4 files changed, 3 insertions(+), 49 deletions(-)
include/qemu/coroutine_int.h +3 -10
@@ -46,21 +46,14 @@ struct Coroutine {size_t locks_held; -/* Only used when the coroutine has yielded. */ -AioContext *ctx; - -/* Used to catch and abort on illegal co-routine entry. -* Will contain the name of the function that had first -* scheduled the coroutine. */ -const char *scheduled; - -QSIMPLEQ_ENTRY (Coroutine) co_queue_next; -/* Coroutines that should be woken up when we yield or terminate. * Only used when the coroutine is running. */ QSIMPLEQ_HEAD(, Coroutine) co_queue_wakeup; +/* Only used when the coroutine has yielded. */ +AioContext *ctx; +QSIMPLEQ_ENTRY (Coroutine) co_queue_next;QSLIST_ENTRY (Coroutine) co_scheduled_next; };
util/async.c +0 -13
@@ -388,9 +388,6 @@ static void co_schedule_bh_cb(void *opaque)QSLIST_REMOVE_HEAD (&straight, co_scheduled_next);trace_aio_co_schedule_bh_cb (ctx, co);aio_context_acquire (ctx); - -/* Protected by write barrier in qemu_aio_coroutine_enter */ -atomic_set (&co->scheduled,NULL );qemu_coroutine_enter (co);aio_context_release (ctx); }
@@ -441,16 +438,6 @@ fail:void aio_co_schedule (AioContext *ctx,Coroutine *co) {trace_aio_co_schedule (ctx, co); -const char *scheduled =atomic_cmpxchg (&co->scheduled,NULL , - __func__); - -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } -QSLIST_INSERT_HEAD_ATOMIC (&ctx->scheduled_coroutines, co, co_scheduled_next);qemu_bh_schedule (ctx->co_schedule_bh);
util/qemu-coroutine-sleep.c +0 -12
@@ -13,7 +13,6 @@#include "qemu/osdep.h" #include "qemu/coroutine.h" -#include "qemu/coroutine_int.h" #include "qemu/timer.h" #include "block/aio.h"
@@ -26,8 +25,6 @@ static void co_sleep_cb(void *opaque) {CoSleepCB *sleep_cb = opaque; -/* Write of schedule protected by barrier write in aio_co_schedule */ -atomic_set (&sleep_cb->co->scheduled,NULL );aio_co_wake (sleep_cb->co); }
@@ -37,15 +34,6 @@ void coroutine_fn co_aio_sleep_ns(AioContext *ctx, QEMUClockType type,CoSleepCB sleep_cb = { .co =qemu_coroutine_self (), }; - -const char *scheduled =atomic_cmpxchg (&sleep_cb.co->scheduled,NULL , - __func__); -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } sleep_cb.ts =aio_timer_new (ctx, type,SCALE_NS , co_sleep_cb, &sleep_cb);timer_mod (sleep_cb.ts,qemu_clock_get_ns (type) + ns);qemu_coroutine_yield ();
util/qemu-coroutine.c +0 -14
@@ -107,22 +107,8 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)Coroutine *self =qemu_coroutine_self ();CoroutineAction ret; -/* Cannot rely on the read barrier for co in aio_co_wake(), as there are -* callers outside of aio_co_wake() */ -const char *scheduled =atomic_mb_read (&co->scheduled); -trace_qemu_aio_coroutine_enter (ctx, self, co, co->entry_arg); -/* if the Coroutine has already been scheduled, entering it again will -* cause us to enter it twice, potentially even after the coroutine has -* been deleted */ -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } -if (co->caller) {fprintf (stderr,"Co-routine re-entered recursively\n" );abort ();
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
Quoted 11 lines · Kevin Wolf
This reverts commit
6133b39f3c36623425a6ede9e89d93175fde15cd.The commit checked conditions that would expose a bug, but there is no real reason to forbid them apart from the bug, which we'll fix in a minute.
In particular, reentering a coroutine during
co_aio_sleep_ns()is fine; the function is explicitly written to allow this.
aio_co_schedule()can indeed conflict with direct coroutine invocations, but this is exactky what we want to fix, so remove that check again,
s/exactky/exactly/
Quoted 137 lines
too.
Signed-off-by Kevin Wolf <[email protected]>--- include/qemu/coroutine_int.h | 13 +++---------- util/async.c | 13 ------------- util/qemu-coroutine-sleep.c | 12 ------------ util/qemu-coroutine.c | 14 -------------- 4 files changed, 3 insertions(+), 49 deletions(-)include/qemu/coroutine_int.h +3
-10@@ -46,21 +46,14 @@ struct Coroutine {size_t locks_held; -/* Only used when the coroutine has yielded. */ -AioContext *ctx; - -/* Used to catch and abort on illegal co-routine entry. -* Will contain the name of the function that had first -* scheduled the coroutine. */ -const char *scheduled; - -QSIMPLEQ_ENTRY (Coroutine) co_queue_next; -/* Coroutines that should be woken up when we yield or terminate. * Only used when the coroutine is running. */ QSIMPLEQ_HEAD(, Coroutine) co_queue_wakeup; +/* Only used when the coroutine has yielded. */ +AioContext *ctx; +QSIMPLEQ_ENTRY (Coroutine) co_queue_next;QSLIST_ENTRY (Coroutine) co_scheduled_next; };util/async.c +0
-13@@ -388,9 +388,6 @@ static void co_schedule_bh_cb(void *opaque)QSLIST_REMOVE_HEAD (&straight, co_scheduled_next);trace_aio_co_schedule_bh_cb (ctx, co);aio_context_acquire (ctx); - -/* Protected by write barrier in qemu_aio_coroutine_enter */ -atomic_set (&co->scheduled,NULL );qemu_coroutine_enter (co);aio_context_release (ctx); }@@ -441,16 +438,6 @@ fail:void aio_co_schedule (AioContext *ctx,Coroutine *co) {trace_aio_co_schedule (ctx, co); -const char *scheduled =atomic_cmpxchg (&co->scheduled,NULL , - __func__); - -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } -QSLIST_INSERT_HEAD_ATOMIC (&ctx->scheduled_coroutines, co, co_scheduled_next);qemu_bh_schedule (ctx->co_schedule_bh);util/qemu-coroutine-sleep.c +0
-12@@ -13,7 +13,6 @@#include "qemu/osdep.h" #include "qemu/coroutine.h" -#include "qemu/coroutine_int.h" #include "qemu/timer.h" #include "block/aio.h" @@ -26,8 +25,6 @@ static void co_sleep_cb(void *opaque) {CoSleepCB *sleep_cb = opaque; -/* Write of schedule protected by barrier write in aio_co_schedule */ -atomic_set (&sleep_cb->co->scheduled,NULL );aio_co_wake (sleep_cb->co); }@@ -37,15 +34,6 @@ void coroutine_fn co_aio_sleep_ns(AioContext *ctx, QEMUClockType type,CoSleepCB sleep_cb = { .co =qemu_coroutine_self (), }; - -const char *scheduled =atomic_cmpxchg (&sleep_cb.co->scheduled,NULL , - __func__); -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } sleep_cb.ts =aio_timer_new (ctx, type,SCALE_NS , co_sleep_cb, &sleep_cb);timer_mod (sleep_cb.ts,qemu_clock_get_ns (type) + ns);qemu_coroutine_yield ();util/qemu-coroutine.c +0
-14@@ -107,22 +107,8 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)Coroutine *self =qemu_coroutine_self ();CoroutineAction ret; -/* Cannot rely on the read barrier for co in aio_co_wake(), as there are -* callers outside of aio_co_wake() */ -const char *scheduled =atomic_mb_read (&co->scheduled); -trace_qemu_aio_coroutine_enter (ctx, self, co, co->entry_arg); -/* if the Coroutine has already been scheduled, entering it again will -* cause us to enter it twice, potentially even after the coroutine has -* been deleted */ -if (scheduled) { -fprintf (stderr, -"%s: Co-routine was already scheduled in '%s'\n" , - __func__, scheduled); -abort (); - } -if (co->caller) {fprintf (stderr,"Co-routine re-entered recursively\n" );abort ();Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
In reply to Kevin WolfQuoted 8 lines · Kevin Wolf
This reverts commit
6133b39f3c36623425a6ede9e89d93175fde15cd.The commit checked conditions that would expose a bug, but there is no real reason to forbid them apart from the bug, which we'll fix in a minute.
In particular, reentering a coroutine during
co_aio_sleep_ns()is fine; the function is explicitly written to allow this.
This is true.
Quoted 3 lines
aio_co_schedule()can indeed conflict with direct coroutine invocations, but this is exactky what we want to fix, so remove that check again, too.
I'm not sure this is a good idea, as I answered in patch 3.
It can also conflict badly with another aio_co_schedule(). Your patch here removes the assertion in this case, and patch 3 makes it easier to get into the situation where two aio_co_schedule()s conflict with each other.
For example, say you have a coroutine that calls aio_co_schedule on itself, like
while (true) {
aio_co_schedule(qemu_get_current_aio_context(),
qemu_coroutine_self());
}If somebody else calls qemu_coroutine_enter on this coroutine, *that* is the bug. These patches would just cause some random corruption or (perhaps worse) hang.
Paolo
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
Am 28.11.2017 um 17:18 hat Paolo Bonzini geschrieben:
Quoted 16 lines · Kevin Wolf
Quoted 8 lines
This reverts commit
6133b39f3c36623425a6ede9e89d93175fde15cd.The commit checked conditions that would expose a bug, but there is no real reason to forbid them apart from the bug, which we'll fix in a minute.
In particular, reentering a coroutine during
co_aio_sleep_ns()is fine; the function is explicitly written to allow this.This is true.
Quoted 3 lines
aio_co_schedule()can indeed conflict with direct coroutine invocations, but this is exactky what we want to fix, so remove that check again, too.
Quoted 6 lines
I'm not sure this is a good idea, as I answered in patch 3.
It can also conflict badly with another
aio_co_schedule(). Your patch here removes the assertion in this case, and patch 3 makes it easier to get into the situation where two aio_co_schedule()s conflict with each other.
I don't see how they conflict. If the second aio_co_schedule() comes before the coroutine is actually entered, they are effectively simply merged into a single one. Which is exactly what was intended.
Quoted 11 lines
For example, say you have a coroutine that calls
aio_co_scheduleon itself, likewhile (true) { aio_co_schedule(qemu_get_current_aio_context(), qemu_coroutine_self()); }If somebody else calls
qemu_coroutine_enteron this coroutine, *that* is the bug. These patches would just cause some random corruption or (perhaps worse) hang.
Obviously not every coroutine is made to be reentered from multiple places, so for some cases it just might not make a whole lot of sense. Coroutines that are made for it generally are one of the types I explained in the commit message of patch 3.
But anyway, how would this cause corruption or a hang (apart from the fact that this example doesn't have any state that could even be corrupted)? The external qemu_coroutine_enter() would just replace the scheduled coroutine call, so the coroutine wouldn't even notice that it was called from qemu_coroutine_enter() rather than its own scheduled call.
Kevin
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
Quoted 8 lines · Kevin Wolf
Quoted 5 lines
It can also conflict badly with another
aio_co_schedule(). Your patch here removes the assertion in this case, and patch 3 makes it easier to get into the situation where two aio_co_schedule()s conflict with each other.I don't see how they conflict. If the second
aio_co_schedule()comes before the coroutine is actually entered, they are effectively simply merged into a single one. Which is exactly what was intended.
Suppose you have
ctx->scheduled_coroutines
|
'---> co
|
'---> NULLThen aio_co_schedule(ctx, co) does QSLIST_INSERT_HEAD_ATOMIC, which is
/* On entry, ctx->scheduled_coroutines == co. */ co->next = ctx->scheduled_coroutines change ctx->scheduled_coroutines from co->next to co
This results in a loop:
ctx->scheduled_coroutines
|
'---> co <-.
| |
'----'This is an obvious hang due to list corruption. In other more complicated cases it can skip a wakeup, which is a more subtle kind of hang. You can also get memory corruption if the coroutine terminates (and is freed) before aio_co_schedule executes.
Basically, once you do aio_co_schedule or aio_co_wake the coroutine is not any more yours. It's owned by the context that will run it and you should not do anything with it.
The same is true for co_aio_sleep_ns. Just because in practice it works (and it seemed like a clever idea at the time) it doesn't mean it *is* a good idea...
Paolo
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
Am 28.11.2017 um 18:01 hat Paolo Bonzini geschrieben:
Quoted 32 lines · Kevin Wolf
Quoted 8 lines
Quoted 5 lines
It can also conflict badly with another
aio_co_schedule(). Your patch here removes the assertion in this case, and patch 3 makes it easier to get into the situation where two aio_co_schedule()s conflict with each other.I don't see how they conflict. If the second
aio_co_schedule()comes before the coroutine is actually entered, they are effectively simply merged into a single one. Which is exactly what was intended.Suppose you have
ctx->scheduled_coroutines | '---> co | '---> NULLThen
aio_co_schedule(ctx, co)doesQSLIST_INSERT_HEAD_ATOMIC, which is/* On entry, ctx->scheduled_coroutines == co. */ co->next = ctx->scheduled_coroutines change ctx->scheduled_coroutines from co->next to coThis results in a loop:
ctx->scheduled_coroutines | '---> co <-. | | '----'
Quoted 4 lines
This is an obvious hang due to list corruption. In other more complicated cases it can skip a wakeup, which is a more subtle kind of hang. You can also get memory corruption if the coroutine terminates (and is freed) before
aio_co_scheduleexecutes.
Okay, I can see that. I thought you were talking about the logic introduced by this series, but you're actually talking about preexisting behaviour which limits the usefulness of the patches.
Quoted 7 lines
Basically, once you do
aio_co_scheduleoraio_co_wakethe coroutine is not any more yours. It's owned by the context that will run it and you should not do anything with it.The same is true for
co_aio_sleep_ns. Just because in practice it works (and it seemed like a clever idea at the time) it doesn't mean it *is* a good idea...
Well, but that poses the question how you can implement any coroutine that can be reentered from more than one place. Not being able to do that would be a severe restriction.
For example, take quorum, which handles requests by spawning a coroutine for every child and then yielding until acb->count == s->num_children. This means that the main request coroutine can be reentered from the child request coroutines in any order and timing.
What saves us currently is that they are all in the same AioContext, so we won't actually end up in aio_co_schedule(), but I'm sure that sooner or later we'll find cases where we're waiting for several workers that are spread across different I/O threads.
I don't think "don't do that" is a good answer to this.
And yes, reentering co_aio_sleep_ns() early is a real requirement, too. The test case that used speed=1 and would have waited a few hours before actually cancelling the job is an extreme example, but even just delaying cancellation for a second is bad if this is a second of migration downtime.
Kevin
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
Quoted 64 lines · Kevin Wolf
Am 28.11.2017 um 18:01 hat Paolo Bonzini geschrieben:
Quoted 32 lines · Kevin Wolf
Quoted 8 lines
Quoted 5 lines
It can also conflict badly with another
aio_co_schedule(). Your patch here removes the assertion in this case, and patch 3 makes it easier to get into the situation where two aio_co_schedule()s conflict with each other.I don't see how they conflict. If the second
aio_co_schedule()comes before the coroutine is actually entered, they are effectively simply merged into a single one. Which is exactly what was intended.Suppose you have
ctx->scheduled_coroutines | '---> co | '---> NULLThen
aio_co_schedule(ctx, co)doesQSLIST_INSERT_HEAD_ATOMIC, which is/* On entry, ctx->scheduled_coroutines == co. */ co->next = ctx->scheduled_coroutines change ctx->scheduled_coroutines from co->next to coThis results in a loop:
ctx->scheduled_coroutines | '---> co <-. | | '----'Quoted 4 lines
This is an obvious hang due to list corruption. In other more complicated cases it can skip a wakeup, which is a more subtle kind of hang. You can also get memory corruption if the coroutine terminates (and is freed) before
aio_co_scheduleexecutes.Okay, I can see that. I thought you were talking about the logic introduced by this series, but you're actually talking about preexisting behaviour which limits the usefulness of the patches.
Quoted 7 lines
Basically, once you do
aio_co_scheduleoraio_co_wakethe coroutine is not any more yours. It's owned by the context that will run it and you should not do anything with it.The same is true for
co_aio_sleep_ns. Just because in practice it works (and it seemed like a clever idea at the time) it doesn't mean it *is* a good idea...Well, but that poses the question how you can implement any coroutine that can be reentered from more than one place. Not being able to do that would be a severe restriction.
For example, take quorum, which handles requests by spawning a coroutine for every child and then yielding until
acb->count==s->num_children. This means that the main request coroutine can be reentered from the child request coroutines in any order and timing.What saves us currently is that they are all in the same AioContext, so we won't actually end up in
aio_co_schedule(), but I'm sure that sooner or later we'll find cases where we're waiting for several workers that are spread across different I/O threads.
Quoted 8 lines
I don't think "don't do that" is a good answer to this.
And yes, reentering
co_aio_sleep_ns()early is a real requirement, too. The test case that used speed=1 and would have waited a few hours before actually cancelling the job is an extreme example, but even just delaying cancellation for a second is bad if this is a second of migration downtime.
At least this last part should be doable; reentering co_aio_sleep_ns() really should mean that we want to short-circuit the QEMU timer for the sleep cb. Any reason we can't allow a reenter by removing the timer for that sleep, and then doing an aio_co_wake?
Re: [Qemu-devel] [PATCH for-2.11 1/4] Revert "coroutine: abort if we try to schedule or enter a pending coroutine"
In reply to Kevin WolfQuoted 14 lines · Kevin Wolf
Am 28.11.2017 um 18:01 hat Paolo Bonzini geschrieben:
Quoted 3 lines
Basically, once you do
aio_co_scheduleoraio_co_wakethe coroutine is not any more yours. It's owned by the context that will run it and you should not do anything with it.Well, but that poses the question how you can implement any coroutine that can be reentered from more than one place. Not being able to do that would be a severe restriction.
For example, take quorum, which handles requests by spawning a coroutine for every child and then yielding until
acb->count==s->num_children. This means that the main request coroutine can be reentered from the child request coroutines in any order and timing.
Quoted 4 lines
What saves us currently is that they are all in the same AioContext, so we won't actually end up in
aio_co_schedule(), but I'm sure that sooner or later we'll find cases where we're waiting for several workers that are spread across different I/O threads.
That can work as long as you add synchronization among those "more than one place" (quorum doesn't need it because as you say there's only one AioContext involved). Take the previous example of
f(); g(); qemu_coroutine_yield();
If f() and g() do
lock a mutex x--; wake = (x == 0) unlock a mutex if (wake) aio_co_wake(co)
then that's fine, because aio_co_wake is only entered in one place.
Quoted 7 lines
I don't think "don't do that" is a good answer to this.
And yes, reentering
co_aio_sleep_ns()early is a real requirement, too. The test case that used speed=1 and would have waited a few hours before actually cancelling the job is an extreme example, but even just delaying cancellation for a second is bad if this is a second of migration downtime.
Ok, that can be fixed along the lines of what Jeff has just written. I'll take a look.
Thanks,
Paolo
PATCH 2/4 Revert "blockjob: do not allow coroutine double entry or entry-after-completion"
In reply to Kevin WolfThis reverts commit 4afeffc8572f40d8844b946a30c00b10da4442b1.
This fixed the symptom of a bug rather than the root cause. Waking up a sleeping coroutine is generally fine, we just need to make it work correctly across AioContexts, which we'll do in a minute.
--- include/block/blockjob_int.h | 3 +-- blockjob.c | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-)
include/block/blockjob_int.h +1 -2
@@ -143,8 +143,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,* @ns: How many nanoseconds to stop for. * * Put the job to sleep (assuming that it wasn't canceled) for @ns -* nanoseconds. Canceling the job will not interrupt the wait, so the -* cancel will not process until the coroutine wakes up. +* nanoseconds. Canceling the job will interrupt the wait immediately. */ void block_job_sleep_ns (BlockJob *job,QEMUClockType type,int64_t ns);
blockjob.c +2 -5
@@ -797,14 +797,11 @@ void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)return ; } -/* We need to leave job->busy set here, because when we have -* put a coroutine to 'sleep', we have scheduled it to run in -* the future. We cannot enter that same coroutine again before -* it wakes and runs, otherwise we risk double-entry or entry after -* completion. */ + job->busy = false;if (!block_job_should_pause (job)) {co_aio_sleep_ns (blk_get_aio_context (job->blk), type, ns); } + job->busy = true;block_job_pause_point (job); }
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 2/4] Revert "blockjob: do not allow coroutine double entry or entry-after-completion"
Quoted 7 lines · Kevin Wolf
This reverts commit
4afeffc8572f40d8844b946a30c00b10da4442b1.This fixed the symptom of a bug rather than the root cause. Waking up a sleeping coroutine is generally fine, we just need to make it work correctly across AioContexts, which we'll do in a minute.
Signed-off-by Kevin Wolf <[email protected]>
Quoted 43 lines
--- include/block/blockjob_int.h | 3 +-- blockjob.c | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-)include/block/blockjob_int.h +1
-2@@ -143,8 +143,7 @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver,* @ns: How many nanoseconds to stop for. * * Put the job to sleep (assuming that it wasn't canceled) for @ns -* nanoseconds. Canceling the job will not interrupt the wait, so the -* cancel will not process until the coroutine wakes up. +* nanoseconds. Canceling the job will interrupt the wait immediately. */ void block_job_sleep_ns (BlockJob *job,QEMUClockType type,int64_t ns);blockjob.c +2
-5@@ -797,14 +797,11 @@ void block_job_sleep_ns(BlockJob *job, QEMUClockType type, int64_t ns)return ; } -/* We need to leave job->busy set here, because when we have -* put a coroutine to 'sleep', we have scheduled it to run in -* the future. We cannot enter that same coroutine again before -* it wakes and runs, otherwise we risk double-entry or entry after -* completion. */ + job->busy = false;if (!block_job_should_pause (job)) {co_aio_sleep_ns (blk_get_aio_context (job->blk), type, ns); } + job->busy = true;block_job_pause_point (job); }Signature · 1 line
2.13.6
PATCH 3/4 coroutine: Cancel aio_co_schedule() on direct entry
In reply to Kevin WolfIf a coroutine can be reentered from multiple possible sources, we need to be careful in the case that two sources try to reenter it at the same time.
There are two different cases where this can happen:
1. A coroutine spawns multiple asynchronous jobs and waits for all of them to complete. In this case, multiple reentries are expected and the coroutine is probably looping around a yield, so entering it twice is generally fine (but entering it just once after all jobs have completed would be enough, too).
Exception: When the loop condition becomes false and the first reenter already leaves the loop, we must not do a second reenter.
2. A coroutine yields and provides multiple alternative ways to be reentered. In this case, we must always only process the first reenter.
Direct entry is not a problem. It requires that the AioContext locks for the coroutine context are held, which means that the coroutine has enough time to set some state that simply prevents the second caller from reentering the coroutine, too.
Things get more interesting with aio_co_schedule() because the coroutine may be scheduled before it is (directly) entered from a second place. Then changing some state inside the coroutine doesn't help because it is already scheduled and the state won't be checked again.
For this case, we'll cancel any pending aio_co_schedule() when the coroutine is actually entered. Reentering it once is enough for all cases explained above, and a requirement for part of them.
--- include/qemu/coroutine_int.h | 1 + util/async.c | 15 ++++++++++++++- util/qemu-coroutine.c | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-)
include/qemu/coroutine_int.h +1 -0
@@ -40,6 +40,7 @@ struct Coroutine {CoroutineEntry *entry;void *entry_arg;Coroutine *caller; +AioContext *scheduled;/* Only used when the coroutine has terminated. */ QSLIST_ENTRY (Coroutine) pool_next;
util/async.c +14 -1
@@ -372,6 +372,7 @@ static bool event_notifier_poll(void *opaque)static void co_schedule_bh_cb (void *opaque) {AioContext *ctx = opaque; +AioContext *scheduled_ctx; QSLIST_HEAD(, Coroutine) straight, reversed;QSLIST_MOVE_ATOMIC (&reversed, &ctx->scheduled_coroutines);
@@ -388,7 +389,16 @@ static void co_schedule_bh_cb(void *opaque)QSLIST_REMOVE_HEAD (&straight, co_scheduled_next);trace_aio_co_schedule_bh_cb (ctx, co);aio_context_acquire (ctx); -qemu_coroutine_enter (co); + scheduled_ctx =atomic_mb_read (&co->scheduled); +if (scheduled_ctx == ctx) { +qemu_coroutine_enter (co); + }else { +/* This must be a cancelled aio_co_schedule() because the coroutine +* was already entered before this BH had a chance to run. If a +* coroutine is scheduled for two different AioContexts, something +* is very wrong. */ +assert (scheduled_ctx ==NULL ); + }aio_context_release (ctx); } }
@@ -438,6 +448,9 @@ fail:void aio_co_schedule (AioContext *ctx,Coroutine *co) {trace_aio_co_schedule (ctx, co); + +/* Set co->scheduled before the coroutine becomes visible in the list */ +atomic_mb_set (&co->scheduled, ctx);QSLIST_INSERT_HEAD_ATOMIC (&ctx->scheduled_coroutines, co, co_scheduled_next);qemu_bh_schedule (ctx->co_schedule_bh);
util/qemu-coroutine.c +17 -0
@@ -122,6 +122,23 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)*/ smp_wmb (); +/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. This means that the coroutine just needs to +* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL ); + ret =qemu_coroutine_switch (self, co,COROUTINE_ENTER );qemu_co_queue_run_restart (co);
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Quoted 83 lines · Kevin Wolf
If a coroutine can be reentered from multiple possible sources, we need to be careful in the case that two sources try to reenter it at the same time.
There are two different cases where this can happen:
1. A coroutine spawns multiple asynchronous jobs and waits for all of them to complete. In this case, multiple reentries are expected and the coroutine is probably looping around a yield, so entering it twice is generally fine (but entering it just once after all jobs have completed would be enough, too).
Exception: When the loop condition becomes false and the first reenter already leaves the loop, we must not do a second reenter.
2. A coroutine yields and provides multiple alternative ways to be reentered. In this case, we must always only process the first reenter.
Direct entry is not a problem. It requires that the AioContext locks for the coroutine context are held, which means that the coroutine has enough time to set some state that simply prevents the second caller from reentering the coroutine, too.
Things get more interesting with
aio_co_schedule()because the coroutine may be scheduled before it is (directly) entered from a second place. Then changing some state inside the coroutine doesn't help because it is already scheduled and the state won't be checked again.For this case, we'll cancel any pending
aio_co_schedule()when the coroutine is actually entered. Reentering it once is enough for all cases explained above, and a requirement for part of them.Signed-off-by Kevin Wolf <[email protected]>--- include/qemu/coroutine_int.h | 1 + util/async.c | 15 ++++++++++++++- util/qemu-coroutine.c | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-)include/qemu/coroutine_int.h +1
-0@@ -40,6 +40,7 @@ struct Coroutine {CoroutineEntry *entry;void *entry_arg;Coroutine *caller; +AioContext *scheduled;/* Only used when the coroutine has terminated. */ QSLIST_ENTRY (Coroutine) pool_next;util/async.c +11
-1@@ -372,6 +372,7 @@ static bool event_notifier_poll(void *opaque)static void co_schedule_bh_cb (void *opaque) {AioContext *ctx = opaque; +AioContext *scheduled_ctx; QSLIST_HEAD(, Coroutine) straight, reversed;QSLIST_MOVE_ATOMIC (&reversed, &ctx->scheduled_coroutines);@@ -388,7 +389,16 @@ static void co_schedule_bh_cb(void *opaque)QSLIST_REMOVE_HEAD (&straight, co_scheduled_next);trace_aio_co_schedule_bh_cb (ctx, co);aio_context_acquire (ctx); -qemu_coroutine_enter (co); + scheduled_ctx =atomic_mb_read (&co->scheduled); +if (scheduled_ctx == ctx) { +qemu_coroutine_enter (co); + }else { +/* This must be a cancelled aio_co_schedule() because the coroutine +* was already entered before this BH had a chance to run. If a +* coroutine is scheduled for two different AioContexts, something +* is very wrong. */ +assert (scheduled_ctx ==NULL ); + }aio_context_release (ctx); } }
util/async.c fail: · line 451
Quoted 4 lines
util/async.c +1
-0@@ -438,6 +448,9 @@ fail:void aio_co_schedule (AioContext *ctx,Coroutine *co) {trace_aio_co_schedule (ctx, co); +
Do we want to assert(!co->scheduled || co->scheduled == ctx) here?
Quoted 20 lines
util/async.c +2
-0+/* Set co->scheduled before the coroutine becomes visible in the list */ +atomic_mb_set (&co->scheduled, ctx); QSLIST_INSERT_HEAD_ATOMIC(&ctx->scheduled_coroutines,co, co_scheduled_next); qemu_bh_schedule(ctx->co_schedule_bh);util/qemu-coroutine.c +5
-0@@ -122,6 +122,23 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)*/ smp_wmb (); +/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +*
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 139
Quoted 10 lines
util/qemu-coroutine.c +10
-0+* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. This means that the coroutine just needs to +* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */
Nice comment
Quoted 8 lines
util/qemu-coroutine.c +2
-0+atomic_set (&co->scheduled,NULL ); + ret =qemu_coroutine_switch (self, co,COROUTINE_ENTER );qemu_co_queue_run_restart (co);Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
In reply to Kevin Wolfutil/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines · Kevin Wolf
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same.
No, this is not true. aio_co_schedule is thread-safe.
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 140
Quoted 8 lines
This means that the coroutine just needs to
util/qemu-coroutine.c +7
-0+* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL );
This doesn't work. The coroutine is still in the list, and if someone calls aio_co_schedule again now, any coroutines linked from "co" via co_scheduled_next are lost.
(Also, the AioContext lock is by design not protecting any state in AioContext itself; the AioContext lock is only protecting things that run in an AioContext but do not have their own lock).
Paolo
Quoted 5 lines
+
ret = qemu_coroutine_switch(self, co, COROUTINE_ENTER); qemu_co_queue_run_restart(co);
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Am 28.11.2017 um 17:14 hat Paolo Bonzini geschrieben:
Quoted 11 lines · Kevin Wolf
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same.
Quoted 1 line
No, this is not true.
aio_co_scheduleis thread-safe.
Hm... With the reproducer we were specfically looking at qmp_block_job_cancel(), which does take the AioContext locks. But it might not be as universal as I thought.
To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
So if it can indeed happen in practice, we need to think a bit more about this.
Quoted 9 lines
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 140
Quoted 8 lines
This means that the coroutine just needs to
util/qemu-coroutine.c +7
-0+* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL );
Quoted 3 lines
This doesn't work. The coroutine is still in the list, and if someone calls
aio_co_scheduleagain now, any coroutines linked from "co" viaco_scheduled_nextare lost.
Why would they? We still iterate the whole list in co_schedule_bh_cb(), we just skip the single qemu_coroutine_enter().
Quoted 3 lines
(Also, the AioContext lock is by design not protecting any state in AioContext itself; the AioContext lock is only protecting things that run in an AioContext but do not have their own lock).
Such as the coroutine we want to enter, no?
Kevin
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Quoted 18 lines · Kevin Wolf
Am 28.11.2017 um 17:14 hat Paolo Bonzini geschrieben:
Quoted 11 lines · Kevin Wolf
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. Quoted 1 line
No, this is not true.
aio_co_scheduleis thread-safe.Hm... With the reproducer we were specfically looking at
qmp_block_job_cancel(), which does take the AioContext locks. But it might not be as universal as I thought.
Quoted 7 lines
To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
So if it can indeed happen in practice, we need to think a bit more about this.
It would be nice if, on coroutine termination, we could unschedule all pending executions for that coroutine. I think use-after-free is the main concern for someone else calling aio_co_schedule() while the coroutine is currently running.
Quoted 24 lines
Quoted 10 lines
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 140
Quoted 9 lines
This means that the coroutine just needs to
util/qemu-coroutine.c +7
-0+* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL );Quoted 3 lines
This doesn't work. The coroutine is still in the list, and if someone calls
aio_co_scheduleagain now, any coroutines linked from "co" viaco_scheduled_nextare lost.Why would they? We still iterate the whole list in
co_schedule_bh_cb(), we just skip the singleqemu_coroutine_enter().Quoted 3 lines
(Also, the AioContext lock is by design not protecting any state in AioContext itself; the AioContext lock is only protecting things that run in an AioContext but do not have their own lock).
Such as the coroutine we want to enter, no?
Kevin
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Quoted 24 lines · Jeff Cody
Quoted 23 lines · Kevin Wolf
Am 28.11.2017 um 17:14 hat Paolo Bonzini geschrieben:
Quoted 11 lines · Kevin Wolf
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. Quoted 1 line
No, this is not true.
aio_co_scheduleis thread-safe.Hm... With the reproducer we were specfically looking at
qmp_block_job_cancel(), which does take the AioContext locks. But it might not be as universal as I thought.To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
Quoted 7 lines
Quoted 2 lines
So if it can indeed happen in practice, we need to think a bit more about this.
It would be nice if, on coroutine termination, we could unschedule all pending executions for that coroutine. I think use-after-free is the main concern for someone else calling
aio_co_schedule()while the coroutine is currently running.
Yes, terminating a scheduled coroutine is a bug; same for scheduling a terminated coroutine, both orders are wrong. However, "unscheduling" is not the solution; you would just be papering over the issue.
aio_co_schedule() on a running coroutine can only happen when the coroutine is going to yield soon.
Paolo
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Quoted 28 lines · Paolo Bonzini
Quoted 27 lines · Jeff Cody
Quoted 25 lines · Kevin Wolf
Am 28.11.2017 um 17:14 hat Paolo Bonzini geschrieben:
Quoted 11 lines · Kevin Wolf
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. Quoted 1 line
No, this is not true.
aio_co_scheduleis thread-safe.Hm... With the reproducer we were specfically looking at
qmp_block_job_cancel(), which does take the AioContext locks. But it might not be as universal as I thought.To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
So if it can indeed happen in practice, we need to think a bit more about this.
Quoted 9 lines
Quoted 4 lines
It would be nice if, on coroutine termination, we could unschedule all pending executions for that coroutine. I think use-after-free is the main concern for someone else calling
aio_co_schedule()while the coroutine is currently running.Yes, terminating a scheduled coroutine is a bug; same for scheduling a terminated coroutine, both orders are wrong. However, "unscheduling" is not the solution; you would just be papering over the issue.
Maybe we should at least add an abort on coroutine termination if there are still outstanding schedules, as that is preferable to operating in the weeds.
Quoted 3 lines
aio_co_schedule()on a running coroutine can only happen when the coroutine is going to yield soon.
That is a bit vague. What is "soon", and how does an external caller know if a coroutine is going to yield in this timeframe?
Jeff
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
Quoted 7 lines · Jeff Cody
Quoted 3 lines
Yes, terminating a scheduled coroutine is a bug; same for scheduling a terminated coroutine, both orders are wrong. However, "unscheduling" is not the solution; you would just be papering over the issue.
Maybe we should at least add an abort on coroutine termination if there are still outstanding schedules, as that is preferable to operating in the weeds.
Sure, why not. I'm all for adding more assertions (not less :)).
Quoted 5 lines
Quoted 3 lines
aio_co_schedule()on a running coroutine can only happen when the coroutine is going to yield soon.That is a bit vague. What is "soon", and how does an external caller know if a coroutine is going to yield in this timeframe?
Soon really means "eventually"; basically if you do
f(); qemu_coroutine_yield();
then f() can call aio_co_wake() or aio_co_schedule() and knows that it will be entirely thread-safe.
However, remember that only one aio_co_schedule() can be pending at a single time; so if you have
f(); g(); qemu_coroutine_yield();
either f() or g() can wake you up but not both.
Thanks,
Paolo
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
In reply to Jeff CodyAm 28.11.2017 um 17:42 hat Jeff Cody geschrieben:
Quoted 24 lines · Kevin Wolf
Quoted 23 lines
Am 28.11.2017 um 17:14 hat Paolo Bonzini geschrieben:
Quoted 11 lines · Kevin Wolf
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)
Quoted 9 lines
util/qemu-coroutine.c +9
-0+/* Make sure that a coroutine that can alternatively reentered from two +* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. Quoted 1 line
No, this is not true.
aio_co_scheduleis thread-safe.Hm... With the reproducer we were specfically looking at
qmp_block_job_cancel(), which does take the AioContext locks. But it might not be as universal as I thought.To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
Quoted 7 lines
Quoted 2 lines
So if it can indeed happen in practice, we need to think a bit more about this.
It would be nice if, on coroutine termination, we could unschedule all pending executions for that coroutine. I think use-after-free is the main concern for someone else calling
aio_co_schedule()while the coroutine is currently running.
I'd rather assert that this never happens.
Because if you have the same situation, but the coroutine doesn't terminate, but just yields for another reason, then reentering it with the assumption that it's still in the previous yield is a bad bug that you definitely want to catch.
Kevin
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
In reply to Kevin WolfQuoted 17 lines · Kevin Wolf
To be honest, I just wasn't sure what to do with this case anyway. It means that the coroutine is already running when someone else schedules it. We don't really know whether we have to enter it a second time or not.
So if it can indeed happen in practice, we need to think a bit more about this.
Quoted 9 lines
Quoted 8 lines
This means that the coroutine just needs to
util/qemu-coroutine.c +7
-0+* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL );
Quoted 6 lines
Quoted 3 lines
This doesn't work. The coroutine is still in the list, and if someone calls
aio_co_scheduleagain now, any coroutines linked from "co" viaco_scheduled_nextare lost.Why would they? We still iterate the whole list in
co_schedule_bh_cb(), we just skip the singleqemu_coroutine_enter().
Because you modify co_scheduled_next (in QSLIST_INSERT_HEAD_ATOMIC) before it has been consumed.
Quoted 5 lines
Quoted 3 lines
(Also, the AioContext lock is by design not protecting any state in AioContext itself; the AioContext lock is only protecting things that run in an AioContext but do not have their own lock).
Such as the coroutine we want to enter, no?
If you mean the Coroutine object then no, coroutines are entirely thread-safe. All you need to do is: 1) re-enter them with aio_co_wake or aio_co_schedule after the first time you've entered them; 2) make sure you don't enter/schedule them twice.
Paolo
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
In reply to Kevin WolfQuoted 97 lines · Kevin Wolf
If a coroutine can be reentered from multiple possible sources, we need to be careful in the case that two sources try to reenter it at the same time.
There are two different cases where this can happen:
1. A coroutine spawns multiple asynchronous jobs and waits for all of them to complete. In this case, multiple reentries are expected and the coroutine is probably looping around a yield, so entering it twice is generally fine (but entering it just once after all jobs have completed would be enough, too).
Exception: When the loop condition becomes false and the first reenter already leaves the loop, we must not do a second reenter.
2. A coroutine yields and provides multiple alternative ways to be reentered. In this case, we must always only process the first reenter.
Direct entry is not a problem. It requires that the AioContext locks for the coroutine context are held, which means that the coroutine has enough time to set some state that simply prevents the second caller from reentering the coroutine, too.
Things get more interesting with
aio_co_schedule()because the coroutine may be scheduled before it is (directly) entered from a second place. Then changing some state inside the coroutine doesn't help because it is already scheduled and the state won't be checked again.For this case, we'll cancel any pending
aio_co_schedule()when the coroutine is actually entered. Reentering it once is enough for all cases explained above, and a requirement for part of them.Signed-off-by Kevin Wolf <[email protected]>--- include/qemu/coroutine_int.h | 1 + util/async.c | 15 ++++++++++++++- util/qemu-coroutine.c | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-)include/qemu/coroutine_int.h +1
-0@@ -40,6 +40,7 @@ struct Coroutine {CoroutineEntry *entry;void *entry_arg;Coroutine *caller; +AioContext *scheduled;/* Only used when the coroutine has terminated. */ QSLIST_ENTRY (Coroutine) pool_next;util/async.c +14
-1@@ -372,6 +372,7 @@ static bool event_notifier_poll(void *opaque)static void co_schedule_bh_cb (void *opaque) {AioContext *ctx = opaque; +AioContext *scheduled_ctx; QSLIST_HEAD(, Coroutine) straight, reversed;QSLIST_MOVE_ATOMIC (&reversed, &ctx->scheduled_coroutines);@@ -388,7 +389,16 @@ static void co_schedule_bh_cb(void *opaque)QSLIST_REMOVE_HEAD (&straight, co_scheduled_next);trace_aio_co_schedule_bh_cb (ctx, co);aio_context_acquire (ctx); -qemu_coroutine_enter (co); + scheduled_ctx =atomic_mb_read (&co->scheduled); +if (scheduled_ctx == ctx) { +qemu_coroutine_enter (co); + }else { +/* This must be a cancelled aio_co_schedule() because the coroutine +* was already entered before this BH had a chance to run. If a +* coroutine is scheduled for two different AioContexts, something +* is very wrong. */ +assert (scheduled_ctx ==NULL ); + }aio_context_release (ctx); } }@@ -438,6 +448,9 @@ fail:void aio_co_schedule (AioContext *ctx,Coroutine *co) {trace_aio_co_schedule (ctx, co); + +/* Set co->scheduled before the coroutine becomes visible in the list */ +atomic_mb_set (&co->scheduled, ctx);QSLIST_INSERT_HEAD_ATOMIC (&ctx->scheduled_coroutines, co, co_scheduled_next);qemu_bh_schedule (ctx->co_schedule_bh);util/qemu-coroutine.c +0
-0
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 125
Quoted 4 lines
util/qemu-coroutine.c +1
-0@@ -122,6 +122,23 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)*/ smp_wmb (); +/* Make sure that a coroutine that can alternatively reentered from two
"that can be"?
Quoted 5 lines
util/qemu-coroutine.c +5
-0+* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly. +* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 140
Quoted 10 lines
util/qemu-coroutine.c +10
-0+* point, this would be a problem, too, but in practice it doesn't happen +* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. This means that the coroutine just needs to +* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true). +* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL );
Looks like accesses to co->scheduled are fully protected by AioContext lock, why are atomic ops necessary? To catch bugs?
Quoted 7 lines
util/qemu-coroutine.c +1
-0+ ret =qemu_coroutine_switch (self, co,COROUTINE_ENTER );qemu_co_queue_run_restart (co);Signature · 1 line
2.13.6
Fam
Re: [Qemu-devel] [PATCH for-2.11 3/4] coroutine: Cancel aio_co_schedule() on direct entry
In reply to Kevin WolfQuoted 15 lines · Kevin Wolf
If a coroutine can be reentered from multiple possible sources, we need to be careful in the case that two sources try to reenter it at the same time.
For this case, we'll cancel any pending
aio_co_schedule()when the coroutine is actually entered. Reentering it once is enough for all cases explained above, and a requirement for part of them.Signed-off-by Kevin Wolf <[email protected]>--- include/qemu/coroutine_int.h | 1 ++++
b/util/qemu-coroutine.c
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 125
Quoted 4 lines
util/qemu-coroutine.c +1
-0@@ -122,6 +122,23 @@ void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co)*/ smp_wmb (); +/* Make sure that a coroutine that can alternatively reentered from two
s/reentered/be reentered/
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 127
Quoted 2 lines
util/qemu-coroutine.c +2
-0+* different sources isn't reentered more than once when the first caller +* uses aio_co_schedule() and the other one enters to coroutine directly.
s/enters to/enters the/
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 131
Quoted 4 lines
util/qemu-coroutine.c +4
-0+* This is achieved by cancelling the pending aio_co_schedule(). +* +* The other way round, if aio_co_schedule() would be called after this +* point, this would be a problem, too, but in practice it doesn't happen
s/this //
util/qemu-coroutine.c void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) · line 135
Quoted 4 lines
util/qemu-coroutine.c +4
-0+* because we're holding the AioContext lock here and aio_co_schedule() +* callers must do the same. This means that the coroutine just needs to +* prevent other callers from calling aio_co_schedule() before it yields +* (e.g. block job coroutines by setting job->busy = true).
Is 'block job coroutines' the set of coroutines owned by 'block jobs', or is it the verb that we are blocking all 'job coroutines'? I don't know if a wording tweak would help avoid ambiguity here.
Quoted 10 lines
util/qemu-coroutine.c +6
-0+* +* We still want to ensure that the second case doesn't happen, so reset +* co->scheduled only after setting co->caller to make the above check +* effective for the co_schedule_bh_cb() case. */ +atomic_set (&co->scheduled,NULL ); + ret =qemu_coroutine_switch (self, co,COROUTINE_ENTER );qemu_co_queue_run_restart (co);
Signature · 3 lines
Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
PATCH 4/4 block: Expect graph changes in bdrv_parent_drained_begin/end
In reply to Kevin WolfThe .drained_begin/end callbacks can (directly or indirectly via aio_poll()) cause block nodes to be removed or the current BdrvChild to point to a different child node.
Use QLIST_FOREACH_SAFE() to make sure we don't access invalid BlockDriverStates or accidentally continue iterating the parents of the new child node instead of the node we actually came from.
--- block/io.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
block/io.c +4 -4
@@ -42,9 +42,9 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,void bdrv_parent_drained_begin (BlockDriverState *bs) { -BdrvChild *c; +BdrvChild *c, *next; - QLIST_FOREACH(c, &bs->parents, next_parent) { + QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {if (c->role->drained_begin) { c->role->drained_begin (c); }
@@ -53,9 +53,9 @@ void bdrv_parent_drained_begin(BlockDriverState *bs)void bdrv_parent_drained_end (BlockDriverState *bs) { -BdrvChild *c; +BdrvChild *c, *next; - QLIST_FOREACH(c, &bs->parents, next_parent) { + QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {if (c->role->drained_end) { c->role->drained_end (c); }
Signature · 1 line
2.13.6
Re: [Qemu-devel] [PATCH for-2.11 4/4] block: Expect graph changes in bdrv_parent_drained_begin/end
Quoted 44 lines · Kevin Wolf
The .drained_begin/end callbacks can (directly or indirectly via
aio_poll()) cause block nodes to be removed or the current BdrvChild to point to a different child node.Use
QLIST_FOREACH_SAFE()to make sure we don't access invalid BlockDriverStates or accidentally continue iterating the parents of the new child node instead of the node we actually came from.Signed-off-by Kevin Wolf <[email protected]>--- block/io.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)block/io.c +4
-4@@ -42,9 +42,9 @@ static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs,void bdrv_parent_drained_begin (BlockDriverState *bs) { -BdrvChild *c; +BdrvChild *c, *next; - QLIST_FOREACH(c, &bs->parents, next_parent) { + QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {if (c->role->drained_begin) { c->role->drained_begin (c); }@@ -53,9 +53,9 @@ void bdrv_parent_drained_begin(BlockDriverState *bs)void bdrv_parent_drained_end (BlockDriverState *bs) { -BdrvChild *c; +BdrvChild *c, *next; - QLIST_FOREACH(c, &bs->parents, next_parent) { + QLIST_FOREACH_SAFE(c, &bs->parents, next_parent, next) {if (c->role->drained_end) { c->role->drained_end (c); }Signature · 1 line
2.13.6
Quoted 16 lines · Kevin Wolf
This reverts the recent fix for the case of qemu-iotests 200 (block job completion races when using a separate I/O thread) that only fixed a symptom and caused many regressions, and replaces it with a new solution that hopefully attacks the right root cause.
The series also contains a bonus fix for a bug that was uncovered by the different timing with the patches that are reverted here.
Kevin Wolf (4):
Revert "coroutine: abort if we try to schedule or enter a pending
coroutine"
Revert "blockjob: do not allow coroutine double entry or
entry-after-completion"
coroutine: Cancelaio_co_schedule()on direct entry
block: Expect graph changes in bdrv_parent_drained_begin/end
Quoted 12 lines
include/block/blockjob_int.h | 3 +-- include/qemu/coroutine_int.h | 14 ++++---------- block/io.c | 8 ++++---- blockjob.c | 7 ++----- util/async.c | 26 +++++++++++++------------- util/qemu-coroutine-sleep.c | 12 ------------ util/qemu-coroutine.c | 31 +++++++++++++++++-------------- 7 files changed, 41 insertions(+), 60 deletions(-)Signature · 1 line
2.13.6
Quoted 16 lines · Kevin Wolf
This reverts the recent fix for the case of qemu-iotests 200 (block job completion races when using a separate I/O thread) that only fixed a symptom and caused many regressions, and replaces it with a new solution that hopefully attacks the right root cause.
The series also contains a bonus fix for a bug that was uncovered by the different timing with the patches that are reverted here.
Kevin Wolf (4):
Revert "coroutine: abort if we try to schedule or enter a pending
coroutine"
Revert "blockjob: do not allow coroutine double entry or
entry-after-completion"
coroutine: Cancelaio_co_schedule()on direct entry
block: Expect graph changes in bdrv_parent_drained_begin/end
Quoted 12 lines
include/block/blockjob_int.h | 3 +-- include/qemu/coroutine_int.h | 14 ++++---------- block/io.c | 8 ++++---- blockjob.c | 7 ++----- util/async.c | 26 +++++++++++++------------- util/qemu-coroutine-sleep.c | 12 ------------ util/qemu-coroutine.c | 31 +++++++++++++++++-------------- 7 files changed, 41 insertions(+), 60 deletions(-)Signature · 1 line
2.13.6