diff options
author | Stefan Hajnoczi <stefanha@redhat.com> | 2018-03-22 15:28:33 +0000 |
---|---|---|
committer | Stefan Hajnoczi <stefanha@redhat.com> | 2018-03-27 13:05:28 +0100 |
commit | c40a2545700e9ad2ef67d5972484bbee4c83b2a6 (patch) | |
tree | 2c550ddaf9a59a58c3c07e40c02aded945384cd1 | |
parent | 67a74148d8828f611fc5a7a23277b8eceb4c9430 (diff) |
coroutine: avoid co_queue_wakeup recursion
qemu_aio_coroutine_enter() is (indirectly) called recursively when
processing co_queue_wakeup. This can lead to stack exhaustion.
This patch rewrites co_queue_wakeup in an iterative fashion (instead of
recursive) with bounded memory usage to prevent stack exhaustion.
qemu_co_queue_run_restart() is inlined into qemu_aio_coroutine_enter()
and the qemu_coroutine_enter() call is turned into a loop to avoid
recursion.
There is one change that is worth mentioning: Previously, when
coroutine A queued coroutine B, qemu_co_queue_run_restart() entered
coroutine B from coroutine A. If A was terminating then it would still
stay alive until B yielded. After this patch B is entered by A's parent
so that a A can be deleted immediately if it is terminating.
It is safe to make this change since B could never interact with A if it
was terminating anyway.
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-id: 20180322152834.12656-3-stefanha@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-rw-r--r-- | block/io.c | 3 | ||||
-rw-r--r-- | include/qemu/coroutine_int.h | 1 | ||||
-rw-r--r-- | util/qemu-coroutine-lock.c | 34 | ||||
-rw-r--r-- | util/qemu-coroutine.c | 94 |
4 files changed, 52 insertions, 80 deletions
diff --git a/block/io.c b/block/io.c index 2b09c656d0..bd9a19a9c4 100644 --- a/block/io.c +++ b/block/io.c @@ -249,8 +249,7 @@ static void coroutine_fn bdrv_co_yield_to_drain(BlockDriverState *bs, BdrvCoDrainData data; /* Calling bdrv_drain() from a BH ensures the current coroutine yields and - * other coroutines run if they were queued from - * qemu_co_queue_run_restart(). */ + * other coroutines run if they were queued by aio_co_enter(). */ assert(qemu_in_coroutine()); data = (BdrvCoDrainData) { diff --git a/include/qemu/coroutine_int.h b/include/qemu/coroutine_int.h index 59e8406398..bd6b0468e1 100644 --- a/include/qemu/coroutine_int.h +++ b/include/qemu/coroutine_int.h @@ -68,6 +68,5 @@ Coroutine *qemu_coroutine_new(void); void qemu_coroutine_delete(Coroutine *co); CoroutineAction qemu_coroutine_switch(Coroutine *from, Coroutine *to, CoroutineAction action); -void coroutine_fn qemu_co_queue_run_restart(Coroutine *co); #endif diff --git a/util/qemu-coroutine-lock.c b/util/qemu-coroutine-lock.c index 5a80c10690..27438a1858 100644 --- a/util/qemu-coroutine-lock.c +++ b/util/qemu-coroutine-lock.c @@ -68,40 +68,6 @@ void coroutine_fn qemu_co_queue_wait_impl(CoQueue *queue, QemuLockable *lock) } } -/** - * qemu_co_queue_run_restart: - * - * Enter each coroutine that was previously marked for restart by - * qemu_co_queue_next() or qemu_co_queue_restart_all(). This function is - * invoked by the core coroutine code when the current coroutine yields or - * terminates. - */ -void qemu_co_queue_run_restart(Coroutine *co) -{ - Coroutine *next; - QSIMPLEQ_HEAD(, Coroutine) tmp_queue_wakeup = - QSIMPLEQ_HEAD_INITIALIZER(tmp_queue_wakeup); - - trace_qemu_co_queue_run_restart(co); - - /* Because "co" has yielded, any coroutine that we wakeup can resume it. - * If this happens and "co" terminates, co->co_queue_wakeup becomes - * invalid memory. Therefore, use a temporary queue and do not touch - * the "co" coroutine as soon as you enter another one. - * - * In its turn resumed "co" can populate "co_queue_wakeup" queue with - * new coroutines to be woken up. The caller, who has resumed "co", - * will be responsible for traversing the same queue, which may cause - * a different wakeup order but not any missing wakeups. - */ - QSIMPLEQ_CONCAT(&tmp_queue_wakeup, &co->co_queue_wakeup); - - while ((next = QSIMPLEQ_FIRST(&tmp_queue_wakeup))) { - QSIMPLEQ_REMOVE_HEAD(&tmp_queue_wakeup, co_queue_next); - qemu_coroutine_enter(next); - } -} - static bool qemu_co_queue_do_restart(CoQueue *queue, bool single) { Coroutine *next; diff --git a/util/qemu-coroutine.c b/util/qemu-coroutine.c index 9eff7fd450..1ba4191b84 100644 --- a/util/qemu-coroutine.c +++ b/util/qemu-coroutine.c @@ -104,57 +104,65 @@ static void coroutine_delete(Coroutine *co) 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); + QSIMPLEQ_HEAD(, Coroutine) pending = QSIMPLEQ_HEAD_INITIALIZER(pending); + Coroutine *from = qemu_coroutine_self(); - trace_qemu_aio_coroutine_enter(ctx, self, co, co->entry_arg); + QSIMPLEQ_INSERT_TAIL(&pending, co, co_queue_next); - /* 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(); - } + /* Run co and any queued coroutines */ + while (!QSIMPLEQ_EMPTY(&pending)) { + Coroutine *to = QSIMPLEQ_FIRST(&pending); + CoroutineAction ret; - if (co->caller) { - fprintf(stderr, "Co-routine re-entered recursively\n"); - abort(); - } + /* Cannot rely on the read barrier for to in aio_co_wake(), as there are + * callers outside of aio_co_wake() */ + const char *scheduled = atomic_mb_read(&to->scheduled); - co->caller = self; - co->ctx = ctx; + QSIMPLEQ_REMOVE_HEAD(&pending, co_queue_next); - /* Store co->ctx before anything that stores co. Matches - * barrier in aio_co_wake and qemu_co_mutex_wake. - */ - smp_wmb(); + trace_qemu_aio_coroutine_enter(ctx, from, to, to->entry_arg); - ret = qemu_coroutine_switch(self, co, COROUTINE_ENTER); - - qemu_co_queue_run_restart(co); + /* 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(); + } - /* Beware, if ret == COROUTINE_YIELD and qemu_co_queue_run_restart() - * has started any other coroutine, "co" might have been reentered - * and even freed by now! So be careful and do not touch it. - */ + if (to->caller) { + fprintf(stderr, "Co-routine re-entered recursively\n"); + abort(); + } - switch (ret) { - case COROUTINE_YIELD: - return; - case COROUTINE_TERMINATE: - assert(!co->locks_held); - trace_qemu_coroutine_terminate(co); - coroutine_delete(co); - return; - default: - abort(); + to->caller = from; + to->ctx = ctx; + + /* Store to->ctx before anything that stores to. Matches + * barrier in aio_co_wake and qemu_co_mutex_wake. + */ + smp_wmb(); + + ret = qemu_coroutine_switch(from, to, COROUTINE_ENTER); + + /* Queued coroutines are run depth-first; previously pending coroutines + * run after those queued more recently. + */ + QSIMPLEQ_PREPEND(&pending, &to->co_queue_wakeup); + + switch (ret) { + case COROUTINE_YIELD: + break; + case COROUTINE_TERMINATE: + assert(!to->locks_held); + trace_qemu_coroutine_terminate(to); + coroutine_delete(to); + break; + default: + abort(); + } } } |