From: Wenchao Xia <xiawenc@linux.vnet.ibm.com>
To: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Kevin Wolf <kwolf@redhat.com>,
Paolo Bonzini <pbonzini@redhat.com>,
qemu-devel@nongnu.org, Michael Roth <mdroth@linux.vnet.ibm.com>
Subject: Re: [Qemu-devel] [PATCH 1/2] rfifolock: add recursive FIFO lock
Date: Fri, 11 Oct 2013 16:55:31 +0800 [thread overview]
Message-ID: <5257BD03.6010808@linux.vnet.ibm.com> (raw)
In-Reply-To: <1381312531-28723-2-git-send-email-stefanha@redhat.com>
Logic looks fine to me, only a minor comments.
> QemuMutex does not guarantee fairness and cannot be acquired
> recursively:
>
> Fairness means each locker gets a turn and the scheduler cannot cause
> starvation.
>
> Recursive locking is useful for composition, it allows a sequence of
> locking operations to be invoked atomically by acquiring the lock around
> them.
>
> This patch adds RFifoLock, a recursive lock that guarantees FIFO order.
> Its first user is added in the next patch.
>
> RFifoLock has one additional feature: it can be initialized with an
> optional contention callback. The callback is invoked whenever a thread
> must wait for the lock. For example, it can be used to poke the current
> owner so that they release the lock soon.
>
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> include/qemu/rfifolock.h | 54 +++++++++++++++++++++++++++++
> tests/Makefile | 2 ++
> tests/test-rfifolock.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++
> util/Makefile.objs | 1 +
> util/rfifolock.c | 79 ++++++++++++++++++++++++++++++++++++++++++
> 5 files changed, 226 insertions(+)
> create mode 100644 include/qemu/rfifolock.h
> create mode 100644 tests/test-rfifolock.c
> create mode 100644 util/rfifolock.c
>
> diff --git a/include/qemu/rfifolock.h b/include/qemu/rfifolock.h
> new file mode 100644
> index 0000000..b23ab53
> --- /dev/null
> +++ b/include/qemu/rfifolock.h
> @@ -0,0 +1,54 @@
> +/*
> + * Recursive FIFO lock
> + *
> + * Copyright Red Hat, Inc. 2013
> + *
> + * Authors:
> + * Stefan Hajnoczi <stefanha@redhat.com>
> + *
> + * 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 QEMU_RFIFOLOCK_H
> +#define QEMU_RFIFOLOCK_H
> +
> +#include "qemu/thread.h"
> +
> +/* Recursive FIFO lock
> + *
> + * This lock provides more features than a plain mutex:
> + *
> + * 1. Fairness - enforces FIFO order.
> + * 2. Nesting - can be taken recursively.
> + * 3. Contention callback - optional, called when thread must wait.
> + *
> + * The recursive FIFO lock is heavyweight so prefer other synchronization
> + * primitives if you do not need its features.
> + */
> +typedef struct {
> + QemuMutex lock; /* protects all fields */
> +
> + /* FIFO order */
> + unsigned int head; /* active ticket number */
> + unsigned int tail; /* waiting ticket number */
> + QemuCond cond; /* used to wait for our ticket number */
> +
> + /* Nesting */
> + QemuThread owner_thread; /* thread that currently has ownership */
> + unsigned int nesting; /* amount of nesting levels */
> +
> + /* Contention callback */
> + void (*cb)(void *); /* called when thread must wait, with ->lock
> + * held so it may not recursively lock/unlock
> + */
> + void *cb_opaque;
> +} RFifoLock;
> +
If you respin, the define can be moved to util/rfifolock.c, leave
typedef struct RFifoLock RFifoLock;
in header.
> +void rfifolock_init(RFifoLock *r, void (*cb)(void *), void *opaque);
> +void rfifolock_destroy(RFifoLock *r);
> +void rfifolock_lock(RFifoLock *r);
> +void rfifolock_unlock(RFifoLock *r);
> +
> +#endif /* QEMU_RFIFOLOCK_H */
> diff --git a/tests/Makefile b/tests/Makefile
> index 994fef1..2959ed1 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -31,6 +31,7 @@ check-unit-y += tests/test-visitor-serialization$(EXESUF)
> check-unit-y += tests/test-iov$(EXESUF)
> gcov-files-test-iov-y = util/iov.c
> check-unit-y += tests/test-aio$(EXESUF)
> +check-unit-y += tests/test-rfifolock$(EXESUF)
> 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
> @@ -121,6 +122,7 @@ tests/check-qfloat$(EXESUF): tests/check-qfloat.o libqemuutil.a
> tests/check-qjson$(EXESUF): tests/check-qjson.o libqemuutil.a libqemustub.a
> tests/test-coroutine$(EXESUF): tests/test-coroutine.o $(block-obj-y) libqemuutil.a libqemustub.a
> tests/test-aio$(EXESUF): tests/test-aio.o $(block-obj-y) libqemuutil.a libqemustub.a
> +tests/test-rfifolock$(EXESUF): tests/test-rfifolock.o libqemuutil.a libqemustub.a
> tests/test-throttle$(EXESUF): tests/test-throttle.o $(block-obj-y) libqemuutil.a libqemustub.a
> tests/test-thread-pool$(EXESUF): tests/test-thread-pool.o $(block-obj-y) libqemuutil.a libqemustub.a
> tests/test-iov$(EXESUF): tests/test-iov.o libqemuutil.a
> diff --git a/tests/test-rfifolock.c b/tests/test-rfifolock.c
> new file mode 100644
> index 0000000..440dbcb
> --- /dev/null
> +++ b/tests/test-rfifolock.c
> @@ -0,0 +1,90 @@
> +/*
> + * RFifoLock tests
> + *
> + * Copyright Red Hat, Inc. 2013
> + *
> + * Authors:
> + * Stefan Hajnoczi <stefanha@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2 or later.
> + * See the COPYING.LIB file in the top-level directory.
> + */
> +
> +#include <glib.h>
> +#include "qemu-common.h"
> +#include "qemu/rfifolock.h"
> +
> +static void test_nesting(void)
> +{
> + RFifoLock lock;
> +
> + /* Trivial test, ensure the lock is recursive */
> + rfifolock_init(&lock, NULL, NULL);
> + rfifolock_lock(&lock);
> + rfifolock_lock(&lock);
> + rfifolock_lock(&lock);
> + rfifolock_unlock(&lock);
> + rfifolock_unlock(&lock);
> + rfifolock_unlock(&lock);
> + rfifolock_destroy(&lock);
> +}
> +
> +typedef struct {
> + RFifoLock lock;
> + int fd[2];
> +} CallbackTestData;
> +
> +static void rfifolock_cb(void *opaque)
> +{
> + CallbackTestData *data = opaque;
> + int ret;
> + char c = 0;
> +
> + ret = write(data->fd[1], &c, sizeof(c));
> + g_assert(ret == 1);
> +}
> +
> +static void *callback_thread(void *opaque)
> +{
> + CallbackTestData *data = opaque;
> +
> + /* The other thread holds the lock so the contention callback will be
> + * invoked...
> + */
> + rfifolock_lock(&data->lock);
> + rfifolock_unlock(&data->lock);
> + return NULL;
> +}
> +
> +static void test_callback(void)
> +{
> + CallbackTestData data;
> + QemuThread thread;
> + int ret;
> + char c;
> +
> + rfifolock_init(&data.lock, rfifolock_cb, &data);
> + ret = qemu_pipe(data.fd);
> + g_assert(ret == 0);
> +
> + /* Hold lock but allow the callback to kick us by writing to the pipe */
> + rfifolock_lock(&data.lock);
> + qemu_thread_create(&thread, callback_thread, &data, QEMU_THREAD_JOINABLE);
> + ret = read(data.fd[0], &c, sizeof(c));
> + g_assert(ret == 1);
> + rfifolock_unlock(&data.lock);
> + /* If we got here then the callback was invoked, as expected */
> +
> + qemu_thread_join(&thread);
> + close(data.fd[0]);
> + close(data.fd[1]);
> + rfifolock_destroy(&data.lock);
> +}
> +
> +int main(int argc, char **argv)
> +{
> + g_test_init(&argc, &argv, NULL);
> + g_test_add_func("/nesting", test_nesting);
> + g_test_add_func("/callback", test_callback);
> + return g_test_run();
> +}
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 2bb13a2..9bc3515 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -12,3 +12,4 @@ util-obj-y += qemu-option.o qemu-progress.o
> util-obj-y += hexdump.o
> util-obj-y += crc32c.o
> util-obj-y += throttle.o
> +util-obj-y += rfifolock.o
> diff --git a/util/rfifolock.c b/util/rfifolock.c
> new file mode 100644
> index 0000000..13b08de
> --- /dev/null
> +++ b/util/rfifolock.c
> @@ -0,0 +1,79 @@
> +/*
> + * Recursive FIFO lock
> + *
> + * Copyright Red Hat, Inc. 2013
> + *
> + * Authors:
> + * Stefan Hajnoczi <stefanha@redhat.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2 or later.
> + * See the COPYING.LIB file in the top-level directory.
> + *
> + */
> +
> +#include <assert.h>
> +#include "qemu/rfifolock.h"
> +
> +void rfifolock_init(RFifoLock *r, void (*cb)(void *), void *opaque)
> +{
> + qemu_mutex_init(&r->lock);
> + r->head = 0;
> + r->tail = 0;
> + qemu_cond_init(&r->cond);
> + r->nesting = 0;
> + r->cb = cb;
> + r->cb_opaque = opaque;
> +}
> +
> +void rfifolock_destroy(RFifoLock *r)
> +{
> + qemu_cond_destroy(&r->cond);
> + qemu_mutex_destroy(&r->lock);
> +}
> +
> +/*
> + * Theory of operation:
> + *
> + * In order to ensure FIFO ordering, implement a ticketlock. Threads acquiring
> + * the lock enqueue themselves by incrementing the tail index. When the lock
> + * is unlocked, the head is incremented and waiting threads are notified.
> + *
> + * Recursive locking does not take a ticket since the head is only incremented
> + * when the outermost recursive caller unlocks.
> + */
> +void rfifolock_lock(RFifoLock *r)
> +{
> + qemu_mutex_lock(&r->lock);
> +
> + /* Take a ticket */
> + unsigned int ticket = r->tail++;
> +
> + if (r->nesting > 0) {
> + if (qemu_thread_is_self(&r->owner_thread)) {
> + r->tail--; /* put ticket back, we're nesting */
> + } else {
> + while (ticket != r->head) {
> + /* Invoke optional contention callback */
> + if (r->cb) {
> + r->cb(r->cb_opaque);
> + }
> + qemu_cond_wait(&r->cond, &r->lock);
> + }
> + }
> + }
> + qemu_thread_get_self(&r->owner_thread);
> + r->nesting++;
> + qemu_mutex_unlock(&r->lock);
> +}
> +
> +void rfifolock_unlock(RFifoLock *r)
> +{
> + qemu_mutex_lock(&r->lock);
> + assert(r->nesting > 0);
> + assert(qemu_thread_is_self(&r->owner_thread));
> + if (--r->nesting == 0) {
> + r->head++;
> + qemu_cond_broadcast(&r->cond);
> + }
> + qemu_mutex_unlock(&r->lock);
> +}
next prev parent reply other threads:[~2013-10-11 8:56 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-10-09 9:55 [Qemu-devel] [PATCH 0/2] aio: add aio_context_acquire() and aio_context_release() Stefan Hajnoczi
2013-10-09 9:55 ` [Qemu-devel] [PATCH 1/2] rfifolock: add recursive FIFO lock Stefan Hajnoczi
2013-10-09 10:05 ` Paolo Bonzini
2013-10-09 11:26 ` Stefan Hajnoczi
2013-10-09 11:36 ` Paolo Bonzini
2013-10-11 8:55 ` Wenchao Xia [this message]
2013-10-11 13:35 ` Stefan Hajnoczi
2013-10-12 2:48 ` Wenchao Xia
2013-10-09 9:55 ` [Qemu-devel] [PATCH 2/2] aio: add aio_context_acquire() and aio_context_release() Stefan Hajnoczi
2013-10-11 8:52 ` Wenchao Xia
2013-10-09 10:16 ` [Qemu-devel] [PATCH 0/2] " Paolo Bonzini
2013-10-09 11:32 ` Stefan Hajnoczi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5257BD03.6010808@linux.vnet.ibm.com \
--to=xiawenc@linux.vnet.ibm.com \
--cc=kwolf@redhat.com \
--cc=mdroth@linux.vnet.ibm.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.