Skip to content

Commit

Permalink
aio: introduce aio_co_schedule and aio_co_wake
Browse files Browse the repository at this point in the history
aio_co_wake provides the infrastructure to start a coroutine on a "home"
AioContext.  It will be used by CoMutex and CoQueue, so that coroutines
don't jump from one context to another when they go to sleep on a
mutex or waitqueue.  However, it can also be used as a more efficient
alternative to one-shot bottom halves, and saves the effort of tracking
which AioContext a coroutine is running on.

aio_co_schedule is the part of aio_co_wake that starts a coroutine
on a remove AioContext, but it is also useful to implement e.g.
bdrv_set_aio_context callbacks.

The implementation of aio_co_schedule is based on a lock-free
multiple-producer, single-consumer queue.  The multiple producers use
cmpxchg to add to a LIFO stack.  The consumer (a per-AioContext bottom
half) grabs all items added so far, inverts the list to make it FIFO,
and goes through it one item at a time until it's empty.  The data
structure was inspired by OSv, which uses it in the very code we'll
"port" to QEMU for the thread-safe CoMutex.

Most of the new code is really tests.

Signed-off-by: Paolo Bonzini <[email protected]>
Reviewed-by: Fam Zheng <[email protected]>
Message-id: [email protected]
Signed-off-by: Stefan Hajnoczi <[email protected]>
  • Loading branch information
bonzini authored and stefanhaRH committed Feb 21, 2017
1 parent c2b38b2 commit 0c330a7
Show file tree
Hide file tree
Showing 9 changed files with 453 additions and 4 deletions.
32 changes: 32 additions & 0 deletions include/block/aio.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ typedef void QEMUBHFunc(void *opaque);
typedef bool AioPollFn(void *opaque);
typedef void IOHandler(void *opaque);

struct Coroutine;
struct ThreadPool;
struct LinuxAioState;

Expand Down Expand Up @@ -108,6 +109,9 @@ struct AioContext {
bool notified;
EventNotifier notifier;

QSLIST_HEAD(, Coroutine) scheduled_coroutines;
QEMUBH *co_schedule_bh;

/* Thread pool for performing work and receiving completion callbacks.
* Has its own locking.
*/
Expand Down Expand Up @@ -482,6 +486,34 @@ static inline bool aio_node_check(AioContext *ctx, bool is_external)
return !is_external || !atomic_read(&ctx->external_disable_cnt);
}

/**
* aio_co_schedule:
* @ctx: the aio context
* @co: the coroutine
*
* Start a coroutine on a remote AioContext.
*
* The coroutine must not be entered by anyone else while aio_co_schedule()
* is active. In addition the coroutine must have yielded unless ctx
* is the context in which the coroutine is running (i.e. the value of
* qemu_get_current_aio_context() from the coroutine itself).
*/
void aio_co_schedule(AioContext *ctx, struct Coroutine *co);

/**
* aio_co_wake:
* @co: the coroutine
*
* Restart a coroutine on the AioContext where it was running last, thus
* preventing coroutines from jumping from one context to another when they
* go to sleep.
*
* aio_co_wake may be executed either in coroutine or non-coroutine
* context. The coroutine must not be entered by anyone else while
* aio_co_wake() is active.
*/
void aio_co_wake(struct Coroutine *co);

/**
* Return the AioContext whose event loop runs in the current thread.
*
Expand Down
11 changes: 10 additions & 1 deletion include/qemu/coroutine_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ struct Coroutine {
CoroutineEntry *entry;
void *entry_arg;
Coroutine *caller;

/* Only used when the coroutine has terminated. */
QSLIST_ENTRY(Coroutine) pool_next;

size_t locks_held;

/* Coroutines that should be woken up when we yield or terminate */
/* 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;
};

Coroutine *qemu_coroutine_new(void);
Expand Down
8 changes: 5 additions & 3 deletions tests/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ check-unit-y += tests/test-aio$(EXESUF)
gcov-files-test-aio-y = util/async.c util/qemu-timer.o
gcov-files-test-aio-$(CONFIG_WIN32) += util/aio-win32.c
gcov-files-test-aio-$(CONFIG_POSIX) += util/aio-posix.c
check-unit-y += tests/test-aio-multithread$(EXESUF)
gcov-files-test-aio-multithread-y = $(gcov-files-test-aio-y)
gcov-files-test-aio-multithread-y += util/qemu-coroutine.c tests/iothread.c
check-unit-y += tests/test-throttle$(EXESUF)
gcov-files-test-aio-$(CONFIG_WIN32) = aio-win32.c
gcov-files-test-aio-$(CONFIG_POSIX) = aio-posix.c
check-unit-y += tests/test-thread-pool$(EXESUF)
gcov-files-test-thread-pool-y = thread-pool.c
gcov-files-test-hbitmap-y = util/hbitmap.c
Expand Down Expand Up @@ -508,7 +509,7 @@ test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
$(test-qom-obj-y)
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
test-block-obj-y = $(block-obj-y) $(test-io-obj-y)
test-block-obj-y = $(block-obj-y) $(test-io-obj-y) tests/iothread.o

tests/check-qint$(EXESUF): tests/check-qint.o $(test-util-obj-y)
tests/check-qstring$(EXESUF): tests/check-qstring.o $(test-util-obj-y)
Expand All @@ -523,6 +524,7 @@ tests/check-qom-proplist$(EXESUF): tests/check-qom-proplist.o $(test-qom-obj-y)
tests/test-char$(EXESUF): tests/test-char.o $(test-util-obj-y) $(qtest-obj-y) $(test-io-obj-y) $(chardev-obj-y)
tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(test-block-obj-y)
tests/test-aio$(EXESUF): tests/test-aio.o $(test-block-obj-y)
tests/test-aio-multithread$(EXESUF): tests/test-aio-multithread.o $(test-block-obj-y)
tests/test-throttle$(EXESUF): tests/test-throttle.o $(test-block-obj-y)
tests/test-blockjob$(EXESUF): tests/test-blockjob.o $(test-block-obj-y) $(test-util-obj-y)
tests/test-blockjob-txn$(EXESUF): tests/test-blockjob-txn.o $(test-block-obj-y) $(test-util-obj-y)
Expand Down
91 changes: 91 additions & 0 deletions tests/iothread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Event loop thread implementation for unit tests
*
* Copyright Red Hat Inc., 2013, 2016
*
* Authors:
* Stefan Hajnoczi <[email protected]>
* Paolo Bonzini <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/

#include "qemu/osdep.h"
#include "qapi/error.h"
#include "block/aio.h"
#include "qemu/main-loop.h"
#include "qemu/rcu.h"
#include "iothread.h"

struct IOThread {
AioContext *ctx;

QemuThread thread;
QemuMutex init_done_lock;
QemuCond init_done_cond; /* is thread initialization done? */
bool stopping;
};

static __thread IOThread *my_iothread;

AioContext *qemu_get_current_aio_context(void)
{
return my_iothread ? my_iothread->ctx : qemu_get_aio_context();
}

static void *iothread_run(void *opaque)
{
IOThread *iothread = opaque;

rcu_register_thread();

my_iothread = iothread;
qemu_mutex_lock(&iothread->init_done_lock);
iothread->ctx = aio_context_new(&error_abort);
qemu_cond_signal(&iothread->init_done_cond);
qemu_mutex_unlock(&iothread->init_done_lock);

while (!atomic_read(&iothread->stopping)) {
aio_poll(iothread->ctx, true);
}

rcu_unregister_thread();
return NULL;
}

void iothread_join(IOThread *iothread)
{
iothread->stopping = true;
aio_notify(iothread->ctx);
qemu_thread_join(&iothread->thread);
qemu_cond_destroy(&iothread->init_done_cond);
qemu_mutex_destroy(&iothread->init_done_lock);
aio_context_unref(iothread->ctx);
g_free(iothread);
}

IOThread *iothread_new(void)
{
IOThread *iothread = g_new0(IOThread, 1);

qemu_mutex_init(&iothread->init_done_lock);
qemu_cond_init(&iothread->init_done_cond);
qemu_thread_create(&iothread->thread, NULL, iothread_run,
iothread, QEMU_THREAD_JOINABLE);

/* Wait for initialization to complete */
qemu_mutex_lock(&iothread->init_done_lock);
while (iothread->ctx == NULL) {
qemu_cond_wait(&iothread->init_done_cond,
&iothread->init_done_lock);
}
qemu_mutex_unlock(&iothread->init_done_lock);
return iothread;
}

AioContext *iothread_get_aio_context(IOThread *iothread)
{
return iothread->ctx;
}
25 changes: 25 additions & 0 deletions tests/iothread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Event loop thread implementation for unit tests
*
* Copyright Red Hat Inc., 2013, 2016
*
* Authors:
* Stefan Hajnoczi <[email protected]>
* Paolo Bonzini <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef TEST_IOTHREAD_H
#define TEST_IOTHREAD_H

#include "block/aio.h"
#include "qemu/thread.h"

typedef struct IOThread IOThread;

IOThread *iothread_new(void);
void iothread_join(IOThread *iothread);
AioContext *iothread_get_aio_context(IOThread *iothread);

#endif
Loading

0 comments on commit 0c330a7

Please sign in to comment.