blob: 2edbf6123339f3bc3292e584ef47ce66ad0d4474 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <runtime/mutex.h>
#include <zircon/syscalls.h>
#include <stdatomic.h>
// This mutex implementation is based on Ulrich Drepper's paper "Futexes
// Are Tricky" (dated November 5, 2011; see
// http://www.akkadia.org/drepper/futex.pdf). We use the approach from
// "Mutex, Take 2", with one modification: We use an atomic swap in
// zxr_mutex_unlock() rather than an atomic decrement.
// The value of UNLOCKED must be 0 to match mutex.h and so that mutexes can
// be allocated in BSS segments (zero-initialized data).
enum {
UNLOCKED = 0,
LOCKED_WITHOUT_WAITERS = 1,
LOCKED_WITH_WAITERS = 2
};
// On success, this will leave the mutex in the LOCKED_WITH_WAITERS state.
static zx_status_t lock_slow_path(zxr_mutex_t* mutex, zx_time_t abstime,
int old_state) {
for (;;) {
// If the state shows there are already waiters, or we can update
// it to indicate that there are waiters, then wait.
if (old_state == LOCKED_WITH_WAITERS ||
(old_state == LOCKED_WITHOUT_WAITERS &&
atomic_compare_exchange_strong(&mutex->futex, &old_state,
LOCKED_WITH_WAITERS))) {
// TODO(kulakowski) Use ZX_CLOCK_UTC when available.
zx_status_t status = _zx_futex_wait(
&mutex->futex, LOCKED_WITH_WAITERS, abstime);
if (status == ZX_ERR_TIMED_OUT)
return ZX_ERR_TIMED_OUT;
}
// Try again to claim the mutex. On this try, we must set the
// mutex state to LOCKED_WITH_WAITERS rather than
// LOCKED_WITHOUT_WAITERS. This is because we could have been
// woken up when many threads are in the wait queue for the mutex.
old_state = UNLOCKED;
if (atomic_compare_exchange_strong(&mutex->futex, &old_state,
LOCKED_WITH_WAITERS)) {
return ZX_OK;
}
}
}
zx_status_t zxr_mutex_trylock(zxr_mutex_t* mutex) {
int old_state = UNLOCKED;
if (atomic_compare_exchange_strong(&mutex->futex, &old_state,
LOCKED_WITHOUT_WAITERS)) {
return ZX_OK;
}
return ZX_ERR_BAD_STATE;
}
zx_status_t __zxr_mutex_timedlock(zxr_mutex_t* mutex, zx_time_t abstime) {
// Try to claim the mutex. This compare-and-swap executes the full
// memory barrier that locking a mutex is required to execute.
int old_state = UNLOCKED;
if (atomic_compare_exchange_strong(&mutex->futex, &old_state,
LOCKED_WITHOUT_WAITERS)) {
return ZX_OK;
}
return lock_slow_path(mutex, abstime, old_state);
}
void zxr_mutex_lock(zxr_mutex_t* mutex) {
zx_status_t status = __zxr_mutex_timedlock(mutex, ZX_TIME_INFINITE);
if (status != ZX_OK)
__builtin_trap();
}
void zxr_mutex_lock_with_waiter(zxr_mutex_t* mutex) {
int old_state = UNLOCKED;
if (atomic_compare_exchange_strong(&mutex->futex, &old_state,
LOCKED_WITH_WAITERS)) {
return;
}
zx_status_t status = lock_slow_path(mutex, ZX_TIME_INFINITE, old_state);
if (status != ZX_OK)
__builtin_trap();
}
void zxr_mutex_unlock(zxr_mutex_t* mutex) {
// Attempt to release the mutex. This atomic swap executes the full
// memory barrier that unlocking a mutex is required to execute.
int old_state = atomic_exchange(&mutex->futex, UNLOCKED);
switch (old_state) {
case LOCKED_WITHOUT_WAITERS:
// There were no waiters, so there is nothing more to do.
break;
case LOCKED_WITH_WAITERS: {
zx_status_t status = _zx_futex_wake(&mutex->futex, 1);
if (status != ZX_OK)
__builtin_trap();
break;
}
case UNLOCKED:
default:
// Either the mutex was unlocked (in which case the unlock call
// was invalid), or the mutex was in an invalid state.
__builtin_trap();
break;
}
}