blob: c08ffa393856f64b561f76c68eec7094faa70073 [file] [log] [blame]
//===-- Utility condition variable class ------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H
#define LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/mutex.h" // lock_guard
#include "src/__support/CPP/optional.h"
#include "src/__support/OSUtil/syscall.h" // For syscall functions.
#include "src/__support/threads/linux/futex_utils.h"
#include "src/__support/threads/mutex.h"
#include <linux/futex.h> // For futex operations.
#include <stdint.h>
#include <sys/syscall.h> // For syscall numbers.
#include <threads.h> // For values like thrd_success etc.
namespace LIBC_NAMESPACE {
struct CndVar {
enum CndWaiterStatus : uint32_t {
WS_Waiting = 0xE,
WS_Signalled = 0x5,
};
struct CndWaiter {
Futex futex_word = WS_Waiting;
CndWaiter *next = nullptr;
};
CndWaiter *waitq_front;
CndWaiter *waitq_back;
Mutex qmtx;
static int init(CndVar *cv) {
cv->waitq_front = cv->waitq_back = nullptr;
auto err = Mutex::init(&cv->qmtx, false, false, false);
return err == MutexError::NONE ? thrd_success : thrd_error;
}
static void destroy(CndVar *cv) {
cv->waitq_front = cv->waitq_back = nullptr;
}
int wait(Mutex *m) {
// The goal is to perform "unlock |m| and wait" in an
// atomic operation. However, it is not possible to do it
// in the true sense so we do it in spirit. Before unlocking
// |m|, a new waiter object is added to the waiter queue with
// the waiter queue locked. Iff a signalling thread signals
// the waiter before the waiter actually starts waiting, the
// wait operation will not begin at all and the waiter immediately
// returns.
CndWaiter waiter;
{
cpp::lock_guard ml(qmtx);
CndWaiter *old_back = nullptr;
if (waitq_front == nullptr) {
waitq_front = waitq_back = &waiter;
} else {
old_back = waitq_back;
waitq_back->next = &waiter;
waitq_back = &waiter;
}
if (m->unlock() != MutexError::NONE) {
// If we do not remove the queued up waiter before returning,
// then another thread can potentially signal a non-existing
// waiter. Note also that we do this with |qmtx| locked. This
// ensures that another thread will not signal the withdrawing
// waiter.
waitq_back = old_back;
if (waitq_back == nullptr)
waitq_front = nullptr;
else
waitq_back->next = nullptr;
return thrd_error;
}
}
waiter.futex_word.wait(WS_Waiting, cpp::nullopt, true);
// At this point, if locking |m| fails, we can simply return as the
// queued up waiter would have been removed from the queue.
auto err = m->lock();
return err == MutexError::NONE ? thrd_success : thrd_error;
}
int notify_one() {
// We don't use an RAII locker in this method as we want to unlock
// |qmtx| and signal the waiter using a single FUTEX_WAKE_OP signal.
qmtx.lock();
if (waitq_front == nullptr) {
qmtx.unlock();
return thrd_success;
}
CndWaiter *first = waitq_front;
waitq_front = waitq_front->next;
if (waitq_front == nullptr)
waitq_back = nullptr;
qmtx.futex_word = FutexWordType(Mutex::LockState::Free);
// this is a special WAKE_OP, so we use syscall directly
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &qmtx.futex_word.val, FUTEX_WAKE_OP, 1, 1,
&first->futex_word.val,
FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
return thrd_success;
}
int broadcast() {
cpp::lock_guard ml(qmtx);
uint32_t dummy_futex_word;
CndWaiter *waiter = waitq_front;
waitq_front = waitq_back = nullptr;
while (waiter != nullptr) {
// FUTEX_WAKE_OP is used instead of just FUTEX_WAKE as it allows us to
// atomically update the waiter status to WS_Signalled before waking
// up the waiter. A dummy location is used for the other futex of
// FUTEX_WAKE_OP.
LIBC_NAMESPACE::syscall_impl<long>(
FUTEX_SYSCALL_ID, &dummy_futex_word, FUTEX_WAKE_OP, 1, 1,
&waiter->futex_word.val,
FUTEX_OP(FUTEX_OP_SET, WS_Signalled, FUTEX_OP_CMP_EQ, WS_Waiting));
waiter = waiter->next;
}
return thrd_success;
}
};
static_assert(sizeof(CndVar) == sizeof(cnd_t),
"Mismatch in the size of the "
"internal representation of condition variable and the public "
"cnd_t type.");
} // namespace LIBC_NAMESPACE
#endif // LLVM_LIBC_SRC_THREADS_LINUX_CNDVAR_H