-----BEGIN PGP SIGNATURE-----

iQEcBAABAgAGBQJaulgHAAoJEJykq7OBq3PI0owH/3isnzyO8efBzlv4HlWb1qV0
 F7m9urAKZm1QLCkJBlli/G5iNaNUfrXSum4qYK22XAiwV5WAoPAjwrOvtTRRTSfW
 vFGAucmf6NKkomuZRAiPQftqR+DL9VhBC7mcdRwfwWZ82AGZZnAQsDf8y9e3kal2
 YPwogmvQi+bn2Psv6q3zYtrTW9bC1VJWzYFP4xcSpcxGOY/WfLhPX9nrhjQz07mm
 h/y9mWqXA8PytmopMLj31xRmhTSndIwo+uvScLH95UXRuAOhjyHMvvD4HfI4nkR+
 WD94pE6UiUpIdCZ6fh8H/jaCpN8jPnXI4XJg9cQcRHZYkOrHwQ8G1xxoOintjW0=
 =55b0
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging

# gpg: Signature made Tue 27 Mar 2018 15:41:11 BST
# gpg:                using RSA key 9CA4ABB381AB73C8
# gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>"
# gpg:                 aka "Stefan Hajnoczi <stefanha@gmail.com>"
# Primary key fingerprint: 8695 A8BF D3F9 7CDA AC35  775A 9CA4 ABB3 81AB 73C8

* remotes/stefanha/tags/block-pull-request:
  MAINTAINERS: add include/block/aio-wait.h
  coroutine: add test-aio coroutine queue chaining test case
  coroutine: avoid co_queue_wakeup recursion
  queue: add QSIMPLEQ_PREPEND()

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-03-27 17:11:33 +01:00
commit 6cf38cbf29
7 changed files with 111 additions and 91 deletions

View File

@ -1351,6 +1351,7 @@ F: util/aio-*.c
F: block/io.c
F: migration/block*
F: include/block/aio.h
F: include/block/aio-wait.h
F: scripts/qemugdb/aio.py
T: git git://github.com/stefanha/qemu.git block

View File

@ -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) {

View File

@ -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

View File

@ -324,6 +324,14 @@ struct { \
} \
} while (/*CONSTCOND*/0)
#define QSIMPLEQ_PREPEND(head1, head2) do { \
if (!QSIMPLEQ_EMPTY((head2))) { \
*(head2)->sqh_last = (head1)->sqh_first; \
(head1)->sqh_first = (head2)->sqh_first; \
QSIMPLEQ_INIT((head2)); \
} \
} while (/*CONSTCOND*/0)
#define QSIMPLEQ_LAST(head, type, field) \
(QSIMPLEQ_EMPTY((head)) ? \
NULL : \

View File

@ -16,6 +16,8 @@
#include "qemu/timer.h"
#include "qemu/sockets.h"
#include "qemu/error-report.h"
#include "qemu/coroutine.h"
#include "qemu/main-loop.h"
static AioContext *ctx;
@ -827,24 +829,59 @@ static void test_source_timer_schedule(void)
timer_del(&data.timer);
}
/*
* Check that aio_co_enter() can chain many times
*
* Two coroutines should be able to invoke each other via aio_co_enter() many
* times without hitting a limit like stack exhaustion. In other words, the
* calls should be chained instead of nested.
*/
typedef struct {
Coroutine *other;
unsigned i;
unsigned max;
} ChainData;
static void coroutine_fn chain(void *opaque)
{
ChainData *data = opaque;
for (data->i = 0; data->i < data->max; data->i++) {
/* Queue up the other coroutine... */
aio_co_enter(ctx, data->other);
/* ...and give control to it */
qemu_coroutine_yield();
}
}
static void test_queue_chaining(void)
{
/* This number of iterations hit stack exhaustion in the past: */
ChainData data_a = { .max = 25000 };
ChainData data_b = { .max = 25000 };
data_b.other = qemu_coroutine_create(chain, &data_a);
data_a.other = qemu_coroutine_create(chain, &data_b);
qemu_coroutine_enter(data_b.other);
g_assert_cmpint(data_a.i, ==, data_a.max);
g_assert_cmpint(data_b.i, ==, data_b.max - 1);
/* Allow the second coroutine to terminate */
qemu_coroutine_enter(data_a.other);
g_assert_cmpint(data_b.i, ==, data_b.max);
}
/* End of tests. */
int main(int argc, char **argv)
{
Error *local_error = NULL;
GSource *src;
init_clocks(NULL);
ctx = aio_context_new(&local_error);
if (!ctx) {
error_reportf_err(local_error, "Failed to create AIO Context: ");
exit(1);
}
src = aio_get_g_source(ctx);
g_source_attach(src, NULL);
g_source_unref(src);
qemu_init_main_loop(&error_fatal);
ctx = qemu_get_aio_context();
while (g_main_context_iteration(NULL, false));
@ -864,6 +901,8 @@ int main(int argc, char **argv)
g_test_add_func("/aio/external-client", test_aio_external_client);
g_test_add_func("/aio/timer/schedule", test_timer_schedule);
g_test_add_func("/aio/coroutine/queue-chaining", test_queue_chaining);
g_test_add_func("/aio-gsource/flush", test_source_flush);
g_test_add_func("/aio-gsource/bh/schedule", test_source_bh_schedule);
g_test_add_func("/aio-gsource/bh/schedule10", test_source_bh_schedule10);

View File

@ -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;

View File

@ -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;
QSIMPLEQ_HEAD(, Coroutine) pending = QSIMPLEQ_HEAD_INITIALIZER(pending);
Coroutine *from = qemu_coroutine_self();
/* 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_INSERT_TAIL(&pending, co, co_queue_next);
trace_qemu_aio_coroutine_enter(ctx, self, co, co->entry_arg);
/* Run co and any queued coroutines */
while (!QSIMPLEQ_EMPTY(&pending)) {
Coroutine *to = QSIMPLEQ_FIRST(&pending);
CoroutineAction ret;
/* 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();
}
/* 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);
if (co->caller) {
fprintf(stderr, "Co-routine re-entered recursively\n");
abort();
}
QSIMPLEQ_REMOVE_HEAD(&pending, co_queue_next);
co->caller = self;
co->ctx = ctx;
trace_qemu_aio_coroutine_enter(ctx, from, to, to->entry_arg);
/* Store co->ctx before anything that stores co. Matches
* barrier in aio_co_wake and qemu_co_mutex_wake.
*/
smp_wmb();
/* 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();
}
ret = qemu_coroutine_switch(self, co, COROUTINE_ENTER);
if (to->caller) {
fprintf(stderr, "Co-routine re-entered recursively\n");
abort();
}
qemu_co_queue_run_restart(co);
to->caller = from;
to->ctx = ctx;
/* 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.
*/
/* Store to->ctx before anything that stores to. Matches
* barrier in aio_co_wake and qemu_co_mutex_wake.
*/
smp_wmb();
switch (ret) {
case COROUTINE_YIELD:
return;
case COROUTINE_TERMINATE:
assert(!co->locks_held);
trace_qemu_coroutine_terminate(co);
coroutine_delete(co);
return;
default:
abort();
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();
}
}
}