blob: b083bd15e269e30e0f7be293a65b0764b6072fe1 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "kernel/semaphore.h"
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <kernel/thread_lock.h>
// Every call to this method must wake one waiter unless the queue is empty.
//
// When leaving this method, we must have either
// - incremented a non-negative count or
// - unblocked one thread and
// - left an empty queue with a count of zero or
// - left a non-empty queue with negative count
void Semaphore::Post() {
// Is the count greater than or equal to zero? If so, increment and return.
// Take care to not increment a negative count.
int64_t old_count = count_.load(ktl::memory_order_relaxed);
while (old_count >= 0) {
if (count_.compare_exchange_weak(old_count, old_count + 1, ktl::memory_order_release,
ktl::memory_order_relaxed)) {
return;
}
}
// We observed a negative count and have not yet incremented it. There might
// be waiters waiting. We'll need to acquire the lock and check.
Guard<MonitoredSpinLock, IrqSave> guard{ThreadLock::Get(), SOURCE_TAG};
// Because we hold the lock we know that no other thread can transition the
// count from non-negative to negative or vice versa. If the count is
// non-negative, then there must be no watiers so just increment and return.
old_count = count_.load(ktl::memory_order_relaxed);
if (old_count >= 0) {
[[maybe_unused]] uint32_t q;
DEBUG_ASSERT_MSG((q = waitq_.Count()) == 0, "q=%u old_count=%ld\n", q, old_count);
count_.fetch_add(1, ktl::memory_order_release);
return;
}
// Because we hold the lock we know the number of waiters cannot change out
// from under us. At this point we know the count is negative. Check for
// waiters to wake.
const uint32_t num_in_queue = waitq_.Count();
if (num_in_queue == 0) {
// There are no waiters to wake. They must have timed out or been
// interrupted. Reset the count and perform our increment.
count_.store(1, ktl::memory_order_release);
return;
}
// At this point we know there's at least one waiter waiting. We're committed
// to waking. However, we need to determine what the count should be when
// drop the lock and return. After we wake, if there are no waiters left, the
// count should be reset to zero, otherwise it should remain negative.
//
// Because waking via |WakeOne| may reschedule the local CPU and allow another
// thread to "interrupt" our critical section, it's crucial that we adjust the
// count *before* calling |WakeOne|.
if (num_in_queue == 1) {
count_.store(0, ktl::memory_order_release);
}
[[maybe_unused]] const bool woke_one = waitq_.WakeOne(ZX_OK);
DEBUG_ASSERT(woke_one);
}
// Handling failed waits -- Waits can fail due to timeout or thread signal.
// When a Wait fails, the caller cannot proceed to the resource guarded by the
// semaphore. It's as if the waiter gave up and left. We want to ensure failed
// waits do not impact other waiters. While it seems like a failed wait should
// simply "undo" its decrement, it is not safe to do so in Wait. Instead, we
// "fix" the count in subsequent call to Post.
//
// To understand why we can't simply fix the count in Wait after returning from
// Block, let's look at an alternative (buggy) implementation of Post and Wait.
// In this hypothetical implementation, Post increments and Wait decrements
// before Block, but also increments if Block returns an error. With this
// hypothetical implementation in mind, consider the following sequence of
// operations:
//
// Q C
// 0 0
// 1W 1 -1 B
// 1T 0 -1
// 2P 0 0
// 3W 1 -1 B
// 1R 1 0
//
// The way to read the sequence above is that the wait queue (Q) starts empty
// and the count (C) starts at zero. Thread1 calls Wait (1W) and blocks (B).
// Thread1's times out (1T) and is removed from the queue, but has not yet
// resumed execution. Thread2 calls Post (2P), but does not unblock any threads
// because it finds an empty queue. Thread3 calls Wait (3W) and blocks (B).
// Thread1 returns from Block, resumes execution (1R), and increments the count.
// At this point Thread3 is blocked as it should be (two Posts and one failed
// Wait), however, a subsequent call to Post will not unblock it. We have a
// "lost wakeup".
zx_status_t Semaphore::Wait(const Deadline& deadline) {
// Is the count greater than zero? If so, decrement and return. Take care to
// not decrement zero or a negative value.
int64_t old_count = count_.load(ktl::memory_order_relaxed);
while (old_count > 0) {
if (count_.compare_exchange_weak(old_count, old_count - 1, ktl::memory_order_acquire,
ktl::memory_order_relaxed)) {
return ZX_OK;
}
}
// We either observed that count is zero or negative. We have not decremented
// yet. We may need to block.
Guard<MonitoredSpinLock, IrqSave> guard{ThreadLock::Get(), SOURCE_TAG};
// Because we hold the lock we know that no other thread can transition the
// count from non-negative to negative or vice versa. If we can decrement a
// positive count, then we're done and don't need to block.
old_count = count_.fetch_sub(1, ktl::memory_order_acquire);
if (old_count > 0) {
return ZX_OK;
}
// We either decremented zero or a negative value. We must block.
return waitq_.Block(deadline, Interruptible::Yes);
}