blob: 8069336b5d53fece97fc35dc40515ee57ee9c527 [file]
//===-- Linux Semaphore implementation for POSIX semaphores ---------------===//
//
// 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_SEMAPHORE_LINUX_SEMAPHORE_H
#define LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H
#include "hdr/errno_macros.h"
#include "hdr/time_macros.h"
#include "hdr/types/clockid_t.h"
#include "hdr/types/mode_t.h"
#include "hdr/types/struct_timespec.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/common.h"
#include "src/__support/error_or.h"
#include "src/__support/libc_assert.h"
#include "src/__support/threads/futex_utils.h"
#include "src/__support/time/abs_timeout.h"
namespace LIBC_NAMESPACE_DECL {
// Define SEM_VALUE_MAX as INT_MAX
constexpr unsigned int SEM_VALUE_MAX =
static_cast<unsigned int>(cpp::numeric_limits<int>::max());
class Semaphore {
Futex value;
unsigned int canary;
// Whether the semaphore is shared between processes.
bool is_shared;
// A private constant canary used to detect use of uninitialized or
// destroyed semaphores. Chose "SEM1" in ASCII (0x53='S', 0x45='E',
// 0x4D='M', 0x31='1').
static constexpr unsigned int SEM_CANARY = 0x53454D31U;
// Helper function for timed blocking wait, wait for a required
// absolute timeout.
LIBC_INLINE int wait_until(internal::AbsTimeout timeout) {
while (trywait() != 0) {
auto wait_or = value.wait(/*expected=*/0, timeout, is_shared);
// returned error is not a benign race EAGAIN
if (!wait_or.has_value() && wait_or.error() != EAGAIN) {
// ETIMEDOUT still needs a final trywait() in case a post() raced with
// the deadline
if (wait_or.error() == ETIMEDOUT && trywait() == 0)
return 0;
// Any other error (e.g. EINVAL/EFAULT)
return wait_or.error();
}
}
return 0;
}
public:
LIBC_INLINE constexpr Semaphore(unsigned int value, bool shared)
: value(value), canary(SEM_CANARY), is_shared(shared) {}
// Sanity check to detect use of uninitialized or destroyed semaphores.
LIBC_INLINE bool is_valid() const { return canary == SEM_CANARY; }
LIBC_INLINE void destroy() {
// Destroying a semaphore while threads are blocked on it is undefined
// behavior. Similarly, using a destroyed semaphore is undefined.
// Therefore no concurrency safe destruction is required here,
// RELAXED memory ordering is sufficient.
value.store(0, cpp::MemoryOrder::RELAXED);
canary = 0;
}
LIBC_INLINE int getvalue() const {
// get value is informational, not a synchronization op.
// RELAXED ordering is enough.
// TODO: handle the case where the semaphore is locked.
return static_cast<int>(
const_cast<Futex &>(value).load(cpp::MemoryOrder::RELAXED));
}
// Atomically increments the semaphore value and
// wakes one blocked waiter if any.
LIBC_INLINE int post() {
FutexWordType v = value.load(cpp::MemoryOrder::RELAXED);
do {
if (v >= SEM_VALUE_MAX)
return EOVERFLOW;
// RELEASE on success, since post should publish prior writes
// RELAXED on failure, since no synchronization event occurs
} while (!value.compare_exchange_weak(v, v + 1, cpp::MemoryOrder::RELEASE,
cpp::MemoryOrder::RELAXED));
// Wake one waiter if any,
// waiter selection is left to linux futex implementation.
value.notify_one(is_shared);
return 0;
}
// Attempts to decrement the semaphore value without blocking.
LIBC_INLINE int trywait() {
FutexWordType v = value.load(cpp::MemoryOrder::RELAXED);
while (v > 0) {
// ACQUIRE on success to synchronize with the matching post().
if (value.compare_exchange_weak(v, v - 1, cpp::MemoryOrder::ACQUIRE,
cpp::MemoryOrder::RELAXED))
return 0;
}
return EAGAIN;
}
// Blocking wait, decrements the value when positive, or blocks until a
// post() makes it positive again.
LIBC_INLINE int wait() {
while (trywait() != 0) {
// Blocks when value was zero.
// Futex wait re-checks the value atomically,
// so a racing post() before sleep is not lost.
// Spurious wakeups just send back to trywait().
auto wait_or =
value.wait(/*expected=*/0, /*timeout=*/cpp::nullopt, is_shared);
// A successful wake or EAGAIN both just send it back to trywait().
// Any other error is unexpected.
if (!wait_or.has_value() && wait_or.error() != EAGAIN)
return wait_or.error();
}
return 0;
}
// Blocking wait against an absolute CLOCK_REALTIME deadline.
LIBC_INLINE int timedwait(const timespec *abstime) {
return clockwait(CLOCK_REALTIME, abstime);
}
// Blocking wait against a deadline on the given clock:
// CLOCK_REALTIME or CLOCK_MONOTONIC
LIBC_INLINE int clockwait(clockid_t clock_id, const timespec *abstime) {
if (clock_id != CLOCK_MONOTONIC && clock_id != CLOCK_REALTIME)
return EINVAL;
// If the semaphore can be locked immediately, succeed without consulting
// the clock or dereferencing abstime.
if (trywait() == 0)
return 0;
bool is_realtime = (clock_id == CLOCK_REALTIME);
auto timeout =
internal::AbsTimeout::from_timespec(*abstime, /*realtime=*/is_realtime);
if (LIBC_LIKELY(timeout.has_value()))
return wait_until(timeout.value());
internal::AbsTimeout::Error err = timeout.error();
LIBC_ASSERT(err == internal::AbsTimeout::Error::Invalid ||
err == internal::AbsTimeout::Error::BeforeEpoch);
switch (err) {
case internal::AbsTimeout::Error::Invalid:
return EINVAL;
case internal::AbsTimeout::Error::BeforeEpoch:
return ETIMEDOUT;
}
__builtin_trap();
}
// Named semaphore operations.
// creates or opens a named semaphore backed by a file in /dev/shm/.
// When O_CREAT is specified in oflag, mode and value are used for
// initialization.
static ErrorOr<Semaphore *> open(const char *name, int oflag, mode_t mode,
unsigned int value);
// unmaps a named semaphore.
static int close(Semaphore *sem);
// removes a named semaphore from the filesystem.
static int unlink(const char *name);
};
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC_SEMAPHORE_LINUX_SEMAPHORE_H