diff mbox

[13/17] coroutine-lock: introduce qemu_co_mutex_lock_unlock

Message ID 20170420120058.28404-14-pbonzini@redhat.com
State New
Headers show

Commit Message

Paolo Bonzini April 20, 2017, noon UTC
This primitive lets you lock/unlock a CoMutex, guaranteeing neither
blocking nor cacheline bouncing if there is no qemu_co_mutex_lock
critical section.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 include/qemu/coroutine.h   |  6 ++++++
 util/qemu-coroutine-lock.c | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+)

Comments

Fam Zheng May 4, 2017, 7:39 a.m. UTC | #1
On Thu, 04/20 14:00, Paolo Bonzini wrote:
> +    if (atomic_cmpxchg(&mutex->locked, waiters, waiters + 1) != waiters) {

Is it still useful to try the fast path again if there are now even more
waiters, i.e.  "atomic_cmpxchg(...) > waiters"?

> +        goto retry_fast_path;
> +    }
> +
> +    qemu_co_mutex_lock_slowpath(ctx, mutex);
> +    qemu_co_mutex_unlock(mutex);
> +}
> +
>  void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex)
>  {
>      Coroutine *self = qemu_coroutine_self();
> -- 
> 2.9.3
> 
> 
>
Paolo Bonzini May 4, 2017, 9:47 a.m. UTC | #2
On 04/05/2017 09:39, Fam Zheng wrote:
> On Thu, 04/20 14:00, Paolo Bonzini wrote:
>> +    if (atomic_cmpxchg(&mutex->locked, waiters, waiters + 1) != waiters) {
> 
> Is it still useful to try the fast path again if there are now even more
> waiters, i.e.  "atomic_cmpxchg(...) > waiters"?

Probably not.

Paolo

>> +        goto retry_fast_path;
>> +    }
>> +
>> +    qemu_co_mutex_lock_slowpath(ctx, mutex);
>> +    qemu_co_mutex_unlock(mutex);
>> +}
>> +
>>  void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex)
>>  {
>>      Coroutine *self = qemu_coroutine_self();
>> -- 
>> 2.9.3
>>
>>
>>
Paolo Bonzini May 4, 2017, 9:52 a.m. UTC | #3
On 04/05/2017 11:47, Paolo Bonzini wrote:
> 
> 
> On 04/05/2017 09:39, Fam Zheng wrote:
>> On Thu, 04/20 14:00, Paolo Bonzini wrote:
>>> +    if (atomic_cmpxchg(&mutex->locked, waiters, waiters + 1) != waiters) {
>>
>> Is it still useful to try the fast path again if there are now even more
>> waiters, i.e.  "atomic_cmpxchg(...) > waiters"?
> 
> Probably not.

... but when this happens, we don't enter the fast path loop:

 retry_fast_path:
    waiters = atomic_read(&mutex->locked);
    if (waiters == 0) {
        /* Provide same memory ordering semantics as mutex lock/unlock.  */
        smp_mb_acquire();
        smp_mb_release();
        return;
    }

    i = 0;
    while (waiters == 1 && ++i < 1000) {
        if (atomic_read(&mutex->ctx) == ctx) {
            break;
        }
        waiters = atomic_read(&mutex->locked);
        if (waiters == 0) {
            smp_mb_acquire();
            smp_mb_release();
            return;
        }
        cpu_relax();
    }

    if (atomic_cmpxchg(&mutex->locked, waiters, waiters + 1) != waiters) {
        goto retry_fast_path;
    }

    qemu_co_mutex_lock_slowpath(ctx, mutex);
    qemu_co_mutex_unlock(mutex);

The "if (waiters == 0)" fails, and the "while (waiters == 1 && ...)" won't
happen either if atomic_cmpxchg returns > 1.  So really what you get is
a retry of the atomic_cmpxchg.

We should introduce atomic_try_cmpxchg.  Linux added it recently, too,
and it simplifies the code because you don't need to redo the atomic_read.
Like this:

    waiters = atomic_read(&mutex->locked);
 retry_fast_path:
    if (waiters == 0) {
        ...
    }
    i = 0;
    while (waiters == 1 && ++i < 1000) {
        ...
    }
    if (!atomic_cmpxchg(&mutex->locked, &waiters, waiters + 1)) {
        goto retry_fast_path;
    }

    qemu_co_mutex_lock_slowpath(ctx, mutex);
    qemu_co_mutex_unlock(mutex);


Paolo


> Paolo
> 
>>> +        goto retry_fast_path;
>>> +    }
>>> +
>>> +    qemu_co_mutex_lock_slowpath(ctx, mutex);
>>> +    qemu_co_mutex_unlock(mutex);
>>> +}
>>> +
>>>  void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex)
>>>  {
>>>      Coroutine *self = qemu_coroutine_self();
>>> -- 
>>> 2.9.3
>>>
>>>
>>>
> 
>
Stefan Hajnoczi May 4, 2017, 2:12 p.m. UTC | #4
On Thu, Apr 20, 2017 at 02:00:54PM +0200, Paolo Bonzini wrote:
> This primitive lets you lock/unlock a CoMutex, guaranteeing neither
> blocking nor cacheline bouncing if there is no qemu_co_mutex_lock
> critical section.

"Guaranteeing neither X nor Y" means "does not guarantee X or Y".  Did
you mean "Guarantees not X and not Y"?

> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  include/qemu/coroutine.h   |  6 ++++++
>  util/qemu-coroutine-lock.c | 36 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 42 insertions(+)

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Paolo Bonzini May 4, 2017, 4:17 p.m. UTC | #5
On 04/05/2017 16:12, Stefan Hajnoczi wrote:
>> This primitive lets you lock/unlock a CoMutex, guaranteeing neither
>> blocking nor cacheline bouncing if there is no qemu_co_mutex_lock
>> critical section.
> "Guaranteeing neither X nor Y" means "does not guarantee X or Y".  Did
> you mean "Guarantees not X and not Y"?

Oops, thanks---I didn't know this!  "Guaranteeing no blocking and no
cacheline bouncing if there is no concurrent critical section".

Paolo
diff mbox

Patch

diff --git a/include/qemu/coroutine.h b/include/qemu/coroutine.h
index a4509bd..8d4416c 100644
--- a/include/qemu/coroutine.h
+++ b/include/qemu/coroutine.h
@@ -157,6 +157,12 @@  void qemu_co_mutex_init(CoMutex *mutex);
 void coroutine_fn qemu_co_mutex_lock(CoMutex *mutex);
 
 /**
+ * Locks the mutex and immediately unlock it.  This is faster than back-to-back
+ * lock/unlock if the mutex is not taken by anyone.
+ */
+void coroutine_fn qemu_co_mutex_lock_unlock(CoMutex *mutex);
+
+/**
  * Unlocks the mutex and schedules the next coroutine that was waiting for this
  * lock to be run.
  */
diff --git a/util/qemu-coroutine-lock.c b/util/qemu-coroutine-lock.c
index 6328eed..86f56cd 100644
--- a/util/qemu-coroutine-lock.c
+++ b/util/qemu-coroutine-lock.c
@@ -287,6 +287,42 @@  retry_fast_path:
     self->locks_held++;
 }
 
+void coroutine_fn qemu_co_mutex_lock_unlock(CoMutex *mutex)
+{
+    AioContext *ctx = qemu_get_current_aio_context();
+    int waiters, i;
+
+retry_fast_path:
+    waiters = atomic_read(&mutex->locked);
+    if (waiters == 0) {
+        /* Provide same memory ordering semantics as mutex lock/unlock.  */
+        smp_mb_acquire();
+        smp_mb_release();
+        return;
+    }
+
+    i = 0;
+    while (waiters == 1 && ++i < 1000) {
+        if (atomic_read(&mutex->ctx) == ctx) {
+            break;
+        }
+        waiters = atomic_read(&mutex->locked);
+        if (waiters == 0) {
+            smp_mb_acquire();
+            smp_mb_release();
+            return;
+        }
+        cpu_relax();
+    }
+
+    if (atomic_cmpxchg(&mutex->locked, waiters, waiters + 1) != waiters) {
+        goto retry_fast_path;
+    }
+
+    qemu_co_mutex_lock_slowpath(ctx, mutex);
+    qemu_co_mutex_unlock(mutex);
+}
+
 void coroutine_fn qemu_co_mutex_unlock(CoMutex *mutex)
 {
     Coroutine *self = qemu_coroutine_self();