blob: 4707bf00bc67a6cd716526c9a852d4f7483f783e [file] [log] [blame]
// Copyright 2016 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 "object/futex_context.h"
#include <assert.h>
#include <lib/kconcurrent/chainlock_transaction.h>
#include <lib/ktrace.h>
#include <lib/zircon-internal/macros.h>
#include <trace.h>
#include <zircon/types.h>
#include <fbl/null_lock.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/scheduler.h>
#include <object/process_dispatcher.h>
#include <object/thread_dispatcher.h>
#define LOCAL_TRACE 0
#ifndef FUTEX_TRACING_ENABLED
#define FUTEX_TRACING_ENABLED false
#endif
namespace { // file scope only
constexpr bool kFutexBlockTracingEnabled = FUTEX_BLOCK_TRACING_ENABLED;
// Gets a reference to the thread that the user is asserting is the new owner of
// the futex. The thread must have the same futex context as the caller since
// futexes may not be owned by threads with different futex contexts. In addition,
// the new potential owner thread must have been started. Threads which have not
// started yet may not be the owner of a futex.
//
// Do this before we enter any potentially blocking locks. Right now, this
// operation can block on BRW locks involved in protecting the global handle
// table, and the penalty for doing so can be severe due to other issues.
// Until these are resolved, we would rather pay the price to do validation
// here instead of while holding the lock.
//
// This said, we cannot bail out with an error just yet. We need to make it
// into the futex's lock and perform futex state validation first. See Bug
// #34382 for details.
zx_status_t ValidateFutexOwner(zx_handle_t new_owner_handle,
fbl::RefPtr<ThreadDispatcher>* thread_dispatcher) {
if (new_owner_handle == ZX_HANDLE_INVALID) {
return ZX_OK;
}
auto up = ProcessDispatcher::GetCurrent();
zx_status_t status = up->handle_table().GetDispatcherWithRightsNoPolicyCheck(
new_owner_handle, 0, thread_dispatcher, nullptr);
if (status != ZX_OK) {
return status;
}
// Make sure that the proposed owner of the futex shares our futex context,
// and that it has been started.
const auto& new_owner = *thread_dispatcher;
if ((&new_owner->process()->futex_context() != &up->futex_context()) ||
!new_owner->HasStarted()) {
thread_dispatcher->reset();
return ZX_ERR_INVALID_ARGS;
}
// TODO(johngro): Is this where we signal to the lower level thread that it
// cannot exit and free itself just yet because we are considering making this
// thread an owner. Perhaps thread ref counting needs to come back?
//
// If the thread is already DEAD or DYING, don't bother attempting to assign
// it as a new owner for the futex.
if (new_owner->IsDyingOrDead()) {
thread_dispatcher->reset();
}
return ZX_OK;
}
inline zx_status_t ValidateFutexPointer(user_in_ptr<const zx_futex_t> value_ptr) {
if (!value_ptr || (reinterpret_cast<uintptr_t>(value_ptr.get()) % sizeof(zx_futex_t))) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
} // namespace
void FutexContext::WakeHook::OnWakeOrRequeue(Thread& t) {
// Why is this safe? Because there are two places that a blocking
// futex ID gets set. By a thread while it is running and enters
// FutexWait, or when a thread has been successfully dequeued from an
// OWQ and is in the process of being woken or requeued.
//
// For a thread to block in any queue (FutexWait), owned or not, it must
// hold its lock exclusively, and will drop it as it finishes the operation
// and its CPU switches to a different thread. By the time it is possible
// for us to hold the thread's lock and observe it in the blocked state, any
// mutations to the blocking futex id must have "happened before" we were
// able to observe and wake the thread.
//
// Likewise, to unblock the thread during the wake operation, we need to be
// holding thread's lock exclusively (as we are doing here). In order for
// the thread to run again after this, it must obtain its own lock as it
// becomes rescheduled, meaning that any mutations we perform to the
// blocking_futex_id must be visible to it.
//
// TODO(johngro): Consider simply moving the blocking futex ID down to
// the kernel thread level of things in order to simplify all of this
// reasoning? That way, it could easily just be protected by the thread's
// lock.
if (t.user_thread() != nullptr) {
t.user_thread()->blocking_futex_id_ = FutexId::Null();
}
}
void FutexContext::RequeueHook::OnWakeOrRequeue(Thread& t) {
if (t.user_thread() != nullptr) {
t.user_thread()->blocking_futex_id_ = new_id;
}
// As we requeue threads, we need to transfer their pending operation
// counts from the FutexState that they went to sleep on, over to the
// FutexState they are being requeued to.
//
// Sadly, this needs to be done from within the context of the requeued
// thread's lock. Failure to do this means that it would be possible for us
// to requeue a thread from futex A over to futex B, then have that thread
// time out from the futex before we have move the pending operation
// references from A to B. If the thread manages wake up and attempts to
// drop its pending operation count on futex B before we have transferred
// the count, it would result in a bookkeeping error.
requeue_futex_ref.TakeRefs(&wake_futex_ref, 1);
}
struct ResetBlockingFutexIdState {
ResetBlockingFutexIdState() = default;
// No move, no copy.
ResetBlockingFutexIdState(const ResetBlockingFutexIdState&) = delete;
ResetBlockingFutexIdState(ResetBlockingFutexIdState&&) = delete;
ResetBlockingFutexIdState& operator=(const ResetBlockingFutexIdState&) = delete;
ResetBlockingFutexIdState& operator=(ResetBlockingFutexIdState&&) = delete;
uint32_t count = 0;
};
struct SetBlockingFutexIdState {
explicit SetBlockingFutexIdState(FutexId new_id) : id(new_id) {}
// No move, no copy.
SetBlockingFutexIdState(const SetBlockingFutexIdState&) = delete;
SetBlockingFutexIdState(SetBlockingFutexIdState&&) = delete;
SetBlockingFutexIdState& operator=(const SetBlockingFutexIdState&) = delete;
SetBlockingFutexIdState& operator=(SetBlockingFutexIdState&&) = delete;
const FutexId id;
uint32_t count = 0;
};
FutexContext::FutexState::~FutexState() {}
FutexContext::FutexContext() { LTRACE_ENTRY; }
FutexContext::~FutexContext() {
LTRACE_ENTRY;
// All of the threads should have removed themselves from wait queues and
// destroyed themselves by the time the process has exited.
DEBUG_ASSERT(active_futexes_.is_empty());
DEBUG_ASSERT(free_futexes_.is_empty());
}
zx_status_t FutexContext::GrowFutexStatePool() {
fbl::AllocChecker ac;
ktl::unique_ptr<FutexState> new_state1{new (&ac) FutexState};
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
ktl::unique_ptr<FutexState> new_state2{new (&ac) FutexState};
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
Guard<SpinLock, IrqSave> pool_lock_guard{&pool_lock_};
free_futexes_.push_front(ktl::move(new_state1));
free_futexes_.push_front(ktl::move(new_state2));
return ZX_OK;
}
void FutexContext::ShrinkFutexStatePool() {
ktl::unique_ptr<FutexState> state1, state2;
{ // Do not let the futex state become released inside of the lock.
Guard<SpinLock, IrqSave> pool_lock_guard{&pool_lock_};
DEBUG_ASSERT(free_futexes_.is_empty() == false);
state1 = free_futexes_.pop_front();
state2 = free_futexes_.pop_front();
}
}
// NullableDispatcherGuard is a mutex guard type. Its purpose is to allow the use of clang
// thread-safety static analysis in situations where we have a (possibly null) pointer to a
// ThreadDispatcher that we want to lock, but only when the pointer is non-null. When the pointer
// is null, we lie to the compiler about holding its lock.
class TA_SCOPED_CAP FutexContext::NullableDispatcherGuard {
public:
explicit NullableDispatcherGuard(ThreadDispatcher* t) TA_ACQ(t->get_lock()) : t_{t} {
if (t_ != nullptr) {
t_->get_lock()->lock().Acquire();
}
}
~NullableDispatcherGuard() TA_REL() { Release(); }
NullableDispatcherGuard(const NullableDispatcherGuard&) = delete;
NullableDispatcherGuard(NullableDispatcherGuard&&) = delete;
NullableDispatcherGuard& operator=(const NullableDispatcherGuard&) = delete;
NullableDispatcherGuard& operator=(NullableDispatcherGuard&&) = delete;
void Release() TA_REL() {
if (t_ != nullptr) {
t_->get_lock()->lock().Release();
t_ = nullptr;
}
}
private:
ThreadDispatcher* t_;
};
// FutexWait verifies that the integer pointed to by |value_ptr| still equals
// |current_value|. If the test fails, FutexWait returns FAILED_PRECONDITION.
// Otherwise it will block the current thread until the |deadline| passes, or
// until the thread is woken by a FutexWake or FutexRequeue operation on the
// same |value_ptr| futex.
zx_status_t FutexContext::FutexWait(user_in_ptr<const zx_futex_t> value_ptr,
zx_futex_t current_value, zx_handle_t new_futex_owner,
const Deadline& deadline) {
LTRACE_ENTRY;
// Make sure the futex pointer is following the basic rules.
zx_status_t result = ValidateFutexPointer(value_ptr);
if (result != ZX_OK) {
return result;
}
fbl::RefPtr<ThreadDispatcher> futex_owner_thread;
const zx_status_t validator_status = ValidateFutexOwner(new_futex_owner, &futex_owner_thread);
Thread* current_core_thread = Thread::Current::Get();
ThreadDispatcher* current_thread = current_core_thread->user_thread();
FutexId futex_id(value_ptr);
{
// Obtain the FutexState for the ID we are interested in, activating a free
// futex state in the process if needed. This operation should never fail
// (there should always be a FutexState available to us).
//
FutexState::PendingOpRef futex_ref = ActivateFutex(futex_id);
DEBUG_ASSERT(futex_ref != nullptr);
// Now that we have a hold of the FutexState, enter the futex specific lock
// and validate the user-mode futex state.
//
// FutexWait() checks that the address value_ptr still contains
// current_value, and if so it sleeps awaiting a FutexWake() on value_ptr.
// Those two steps must together be atomic with respect to FutexWake(). If
// a FutexWake() operation could occur between them, a user-land mutex
// operation built on top of futexes would have a race condition that could
// miss wakeups.
//
// Note that we disable involuntary preemption while we are inside of this
// lock. The price of blocking while holding this lock is high, and we
// should not (in theory) _ever_ be inside of this lock for very long at
// all. The vast majority of the time, we just need validate the state,
// then trade this lock for the thread lock, and then block. Even if we are
// operating at the very end of our slice, it is best to disable preemption
// until we manage to join the wait queue, or abort because of state
// validation issues.
while (1) {
// The lock ordering in this loop body is a bit subtle. Here are the locks we acquire in the
// order we acquire them, along with some rationale as to why they're acquired and released
// in that order:
//
// 1. We start by acquiring the futex_owner_thread's ThreadDispatcher lock, if the futex
// owner is not nullptr. We need to hold this lock to verify that the thread has not died
// during this Wait operation. It needs to be acquired before the FutexState's spinlock,
// as acquiring a mutex while holding a spinlock is not allowed.
// 2. Then, we acquire the FutexState's spinlock. We need to hold this lock to verify that the
// value of the futex hasn't changed during this Wait operation. Acquiring this spinlock
// requires disabling interrupts. We do so explicitly with an `InterruptDisableGuard`
// instead of using the `IrqSave` option in the `Guard` because we acquire the thread lock
// later in this method, and we want IRQs to be disabled for this entire duration.
// 3. We then validate the futex's state and the futex owner's state.
// 4. Once validation is complete, we observe the "core thread" of the proposed new owner.
// This is the Thread* member of the ThreadDispatcher. If there is still a valid proposed
// new owner, we will obtain the dispatcher's "core thread" lock, which will ensure that
// the underlying Thread cannot exit even after we drop the ThreadDispatcher lock.
// 5. Next, we can obtain the set of ChainLocks needed to block the thread an assign our
// (optional) new owner.
// 6. Once we have these locks, we can drop the FutexState lock and proceed with the block
// operation.
NullableDispatcherGuard futex_owner_guard(futex_owner_thread.get());
InterruptDisableGuard irqd;
Guard<SpinLock, NoIrqSave> futex_state_guard{&futex_ref->lock_};
// Sanity check, bookkeeping should not indicate that we are blocked on
// a futex at this point in time.
DEBUG_ASSERT(current_thread->blocking_futex_id_ == FutexId::Null());
int value;
UserCopyCaptureFaultsResult copy_result = arch_copy_from_user_capture_faults(
&value, value_ptr.get(), sizeof(int), CopyContext::kBlockingNotAllowed);
if (copy_result.status != ZX_OK) {
// At this point we are committed to either returning from the function, or restarting the
// loop, so we:
// 1. Drop the futex state lock.
// 2. Re-enable interrupts.
// 3. Drop the futex owner lock.
// 4. Either return with an error or resolve the fault and continue the loop.
futex_state_guard.Release();
irqd.Reenable();
futex_owner_guard.Release();
if (auto fault = copy_result.fault_info) {
result = Thread::Current::SoftFault(fault->pf_va, fault->pf_flags);
if (result != ZX_OK) {
return result;
}
continue;
}
return copy_result.status;
}
if (value != current_value) {
return ZX_ERR_BAD_STATE;
}
if (validator_status != ZX_OK) {
if (validator_status == ZX_ERR_BAD_HANDLE) {
[[maybe_unused]] auto res =
ProcessDispatcher::GetCurrent()->EnforceBasicPolicy(ZX_POL_BAD_HANDLE);
}
return validator_status;
}
// Now that the futex state has been validated, we can validate the state of the futex owner
// thread (if one exists). We must do this after validating the futex state to avoid the race
// condition outlined in https://fxbug.dev/42109683.
// TODO(johngro): explain how the core thread observation keeps the Thread* core thread valid.
ThreadDispatcher::CoreThreadObservation new_owner_observation;
if (futex_owner_thread != nullptr) {
// When attempting to wait, the new owner of the futex (if any) may not be
// the thread which is attempting to wait.
if (futex_owner_thread.get() == ThreadDispatcher::GetCurrent()) {
return ZX_ERR_INVALID_ARGS;
}
// If we have a valid new owner, then verify that this thread is not already
// waiting on the target futex.
if (futex_owner_thread->blocking_futex_id_ == futex_id) {
return ZX_ERR_INVALID_ARGS;
}
// We need to resolve |futex_owner_thread|'s Thread*. First, we must revalidate that
// |futex_owner_thread| is not dead or dying as it may have changed state during the
// |SoftFault| call above.
if (!futex_owner_thread->IsDyingOrDeadLocked()) {
new_owner_observation = futex_owner_thread->ObserveCoreThread();
}
}
// We have now passed all of our checks and are committed to becoming
// blocked. We are currently holding a few different locks/guards, but do
// not yet hold the set of chain locks we will need to perform the final
// block and assign owner operation. We will exchange the spinlocks we
// are holding for the ChainLocks we need, but _before_ we can do that, we
// need to release the "futex_owner_guard", which is holding a mutex (if
// we still have a valid proposed owner).
//
// We need to release this mutex before we start obtaining ChainLocks; if
// we release the mutex and discover that we need to wake a waiter, we
// need to make sure we are not already in the middle of a
// ChainLockTransaction (since we will need to conduct a transaction in
// order to unblock the next waiter)
//
// We also need to make sure that local rescheduling (at least) has been
// disabled. We are currently holding spinlocks, and even if we do end up
// unblocking another thread, we cannot reschedule until we drop those
// spinlocks.
//
// So, disable all rescheduling for now, both local and "eager
// rescheduling" (meaning we will hold off on sending IPIs just now). We
// are going to block, and when we do, all of the pending reschedule
// operations (local and remote) will be automatically flushed for us.
AnnotatedAutoEagerReschedDisabler eager_resched_disabler;
futex_owner_guard.Release(); // This is now OK since we have suppressed
// local rescheduling.
// Now we can go ahead and obtain the ChainLocks we need, and once we have
// them, drop the spinlocks we are holding.
Thread* const current_kernel_thread = Thread::Current::Get();
ChainLockTransactionNoIrqSave clt{CLT_TAG("FutexContext::FutexWaitInternal")};
Thread* const new_owner = new_owner_observation.core_thread();
OwnedWaitQueue::BAAOLockingDetails locking_details =
futex_ref->waiters_.LockForBAAOOperation(current_kernel_thread, new_owner);
clt.Finalize();
// We now have all of the locks we need in order to block our thread.
// We can now drop the:
//
// 1) New owner core lock (if any). We have the new prospective owner
// Thread's ChainLock held, it cannot exit out from under us at this
// point.
// 2) The futex state guard. We have the futex's OwnedWaitQueue
// ChainLock, so it is not possible for another thread to wake
// threads from the Futex's OWQ before this thread manages to become
// blocked.
new_owner_observation.Release();
futex_state_guard.Release();
// Record the futex ID of the thread we are about to block on.
current_thread->blocking_futex_id_ = futex_id;
// Mark that we are now blocked-by-futex.
ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::FUTEX);
// Finally, drop into the block operation. This will take care of
// releasing the queue's lock, as well as any of the locks involved in
// ownership propagation. The current thread's lock will be held right
// up until the point where it is context switched away from, when it
// finally will be dropped. It will be re-obtained later on after the
// thread has woken and become rescheduled, and we will need to drop it
// ourselves as we unwind.
{
ktrace::Scope block_tracer = KTRACE_BEGIN_SCOPE_ENABLE(
kFutexBlockTracingEnabled, "kernel:sched", "futex_block", ("futex_id", futex_id.get()),
("blocking_tid", new_owner ? new_owner->tid() : ZX_KOID_INVALID));
result = futex_ref->waiters_.BlockAndAssignOwnerLocked(
current_kernel_thread, deadline, locking_details, ResourceOwnership::Normal,
Interruptible::Yes);
}
// Do _not_ allow the PendingOpRef helper to release our pending op
// reference. Having just woken up, either the thread which woke us will
// have released our pending op reference, or we will need to revalidate
// _which_ futex we were waiting on (because of FutexRequeue) and manage the
// release of the reference ourselves.
futex_ref.CancelRef();
// We came out of the block operation holding only our current thread's
// chain lock. Drop our own lock and break out of the retry loop.
current_kernel_thread->get_lock().Release();
break;
}
}
// If we were woken by another thread, then our block result will be ZX_OK.
// We know that the thread has handled releasing our pending op reference, and
// has reset our blocking futex ID to zero. No special action should be
// needed by us at this point.
if (result == ZX_OK) {
// The FutexWake operation should have already cleared our blocking
// futex ID.
DEBUG_ASSERT(current_thread->blocking_futex_id_ == FutexId::Null());
return ZX_OK;
}
// If the result is not ZX_OK, then additional actions may be required by
// us. This could be because
//
// 1) We hit the deadline (ZX_ERR_TIMED_OUT)
// 2) We were killed (ZX_ERR_INTERNAL_INTR_KILLED)
// 3) We were suspended (ZX_ERR_INTERNAL_INTR_RETRY)
//
// In any one of these situations, it is possible that we were the last
// waiter in our FutexState and need to return the FutexState to the free
// pool as a result. To complicate things just a bit further, becuse of
// zx_futex_requeue, the futex that we went to sleep on may not be the futex
// we just woke up from. We need to find the futex we were blocked by, and
// release our pending op reference to it (potentially returning the
// FutexState to the free pool in the process).
DEBUG_ASSERT(current_thread->blocking_futex_id_ != FutexId::Null());
FutexState::PendingOpRef futex_ref = FindActiveFutex(current_thread->blocking_futex_id_);
current_thread->blocking_futex_id_ = FutexId::Null();
DEBUG_ASSERT(futex_ref != nullptr);
// Record the fact that we are holding an extra reference. The first
// reference was placed on the FutexState at the start of this method as we
// fetched the FutexState from the pool. This reference was not removed by a
// waking thread because we just timed out, or were killed/suspended.
//
// The second reference was just added during the FindActiveFutex (above).
//
futex_ref.SetExtraRefs(1);
// Deal with ownership of the futex. It is possible that we were the last
// thread waiting on the futex, but that the futex's wait queue still has an
// owner assigned. We need to obtain the locks all of the proper locks, then
// (if the waiters_ OWQ has no threads blocked in it) reset the owner.
//
// Note: We should not need the actual FutexState lock at this point in time.
// We know that the FutexState cannot disappear out from under us (we are
// holding two pending operation references), and once we are inside of the
// thread lock, we no that no new threads can join the wait queue. If there
// is a thread racing with us to join the queue, then it will go ahead and
// explicitly update ownership as it joins the queue once it has made it
// inside of the thread lock.
futex_ref->waiters_.ResetOwnerIfNoWaiters();
return result;
}
zx_status_t FutexContext::FutexWake(user_in_ptr<const zx_futex_t> value_ptr, uint32_t wake_count,
OwnerAction owner_action) {
LTRACE_ENTRY;
zx_status_t result;
// Make sure the futex pointer is following the basic rules.
result = ValidateFutexPointer(value_ptr);
if (result != ZX_OK) {
return result;
}
// Try to find an active futex with the specified ID. If we cannot find one,
// then we are done. This wake operation had no threads to wake.
FutexId futex_id(value_ptr);
FutexState::PendingOpRef futex_ref = FindActiveFutex(futex_id);
if (futex_ref == nullptr) {
return ZX_OK;
}
// We found an "active" futex, meaning its pending operation count was
// non-zero when we went looking for it. Now enter the FutexState specific
// lock and see if there are any actual waiters to wake up.
{
// Optimize lock contention by delaying local/remote reschedules until the mutex is released.
AnnotatedAutoEagerReschedDisabler eager_resched_disabler;
Guard<SpinLock, IrqSave> guard{&futex_ref->lock_};
// Now actually wake up the threads. OwnedWakeQueue will handle the
// ownership bookkeeping for us.
{
// Attempt to wake |wake_count| threads. Count the number of thread that
// we have successfully woken, and assign each of their blocking futex IDs
// to 0 as we go. We need an accurate count in order to properly adjust
// the pending operation ref count on our way out of this function.
const OwnedWaitQueue::WakeThreadsResult wake_result = [&]() {
WakeHook hook;
if (owner_action == OwnerAction::RELEASE) {
return futex_ref->waiters_.WakeThreads(wake_count, hook);
} else {
DEBUG_ASSERT(owner_action == OwnerAction::ASSIGN_WOKEN);
DEBUG_ASSERT(wake_count == 1);
return futex_ref->waiters_.WakeThreadAndAssignOwner(hook);
}
}();
// Adjust the number of pending operation refs we are about to release. In
// addition to the ref we were holding when we started the wake operation, we
// are also now responsible for the refs which were being held by each of the
// threads which we have successfully woken. Those threads are exiting along
// the FutexWait hot-path, and they have expected us to manage their
// blocking_futex_id and pending operation references for them.
futex_ref.SetExtraRefs(wake_result.woken);
}
}
return ZX_OK;
}
zx_status_t FutexContext::FutexRequeue(user_in_ptr<const zx_futex_t> wake_ptr, uint32_t wake_count,
int current_value, OwnerAction owner_action,
user_in_ptr<const zx_futex_t> requeue_ptr,
uint32_t requeue_count,
zx_handle_t new_requeue_owner_handle) {
LTRACE_ENTRY;
zx_status_t result;
// Make sure the futex pointers are following the basic rules.
result = ValidateFutexPointer(wake_ptr);
if (result != ZX_OK) {
return result;
}
result = ValidateFutexPointer(requeue_ptr);
if (result != ZX_OK) {
return result;
}
if (wake_ptr.get() == requeue_ptr.get()) {
return ZX_ERR_INVALID_ARGS;
}
// Validate the proposed new owner outside of any FutexState locks, but take
// no action just yet. See the comment in FutexWait for details.
fbl::RefPtr<ThreadDispatcher> requeue_owner_thread;
const zx_status_t validator_status =
ValidateFutexOwner(new_requeue_owner_handle, &requeue_owner_thread);
// Find the FutexState for the wake and requeue futexes.
FutexId wake_id(wake_ptr);
FutexId requeue_id(requeue_ptr);
Guard<SpinLock, IrqSave> ref_lookup_guard{&pool_lock_};
FutexState::PendingOpRef wake_futex_ref = ActivateFutexLocked(wake_id);
FutexState::PendingOpRef requeue_futex_ref = ActivateFutexLocked(requeue_id);
DEBUG_ASSERT(wake_futex_ref != nullptr);
DEBUG_ASSERT(requeue_futex_ref != nullptr);
// Manually release the ref lookup guard. While we would typically do this
// using scope, the PendingOpRefs need to live outside of just the locking
// scope. We cannot declare the PendingOpRefs outside of the scope because we
// do not allow default construction of PendingOpRefs, nor do we allow move
// assignment. This is done on purpose; pending op refs should only ever be
// constructed during lookup operations, and they really should not be moved
// around. We need to have a move constructor, but there is no reason for a
// move assignment.
ref_lookup_guard.Release();
while (1) {
// See the comment in FutexWait about the structure of lock ordering in this method.
NullableDispatcherGuard requeue_owner_guard(requeue_owner_thread.get());
AnnotatedAutoEagerReschedDisabler eager_resched_disabler;
GuardMultiple<2, SpinLock, IrqSave> futex_guards{&wake_futex_ref->lock_,
&requeue_futex_ref->lock_};
// Validate the futex storage state.
int value;
UserCopyCaptureFaultsResult copy_result = arch_copy_from_user_capture_faults(
&value, wake_ptr.get(), sizeof(int), CopyContext::kBlockingNotAllowed);
if (copy_result.status != ZX_OK) {
// At this point we are committed to either returning from the function, or restarting the
// loop, so we can drop the locks and resched disable that are local to this loop iteration.
futex_guards.Release();
requeue_owner_guard.Release();
eager_resched_disabler.Enable();
if (auto fault = copy_result.fault_info) {
result = Thread::Current::SoftFault(fault->pf_va, fault->pf_flags);
if (result != ZX_OK) {
return result;
}
continue;
}
return copy_result.status;
}
if (value != current_value) {
return ZX_ERR_BAD_STATE;
}
// If owner validation failed earlier, then bail out now (after we have passed the state check).
if (validator_status != ZX_OK) {
if (validator_status == ZX_ERR_BAD_HANDLE) {
[[maybe_unused]] auto res =
ProcessDispatcher::GetCurrent()->EnforceBasicPolicy(ZX_POL_BAD_HANDLE);
}
return validator_status;
}
Thread* new_requeue_owner = nullptr;
if (requeue_owner_thread) {
// Verify that the thread we are attempting to make the requeue target's
// owner (if any) is not waiting on either the wake futex or the requeue
// futex.
if ((requeue_owner_thread->blocking_futex_id_ == wake_id) ||
(requeue_owner_thread->blocking_futex_id_ == requeue_id)) {
return ZX_ERR_INVALID_ARGS;
}
// We need to resolve |requeue_owner_thread|'s Thread*. First, we must revalidate that
// |requeue_owner_thread| is not dead or dying as it may have changed state during the
// |SoftFault| call above.
//
// Once we've verified it's not dead or dying, we must ensure the Thread* remains valid. See
// related comment below just after |new_owner_guard| is released.
if (!requeue_owner_thread->IsDyingOrDeadLocked()) {
new_requeue_owner = requeue_owner_thread->core_thread_;
}
}
// Now that all of our sanity checks are complete, it is time to do the
// actual manipulation of the various wait queues.
{
DEBUG_ASSERT(wake_futex_ref != nullptr);
// TODO(johngro): we are holding the proposed new owner's
// ThreadDispatcher; do we need to, or is it possible to drop it?
WakeHook wake_hook;
RequeueHook requeue_hook{wake_futex_ref, requeue_futex_ref, requeue_id};
OwnedWaitQueue::WakeOption wake_option = (owner_action == OwnerAction::RELEASE)
? OwnedWaitQueue::WakeOption::None
: OwnedWaitQueue::WakeOption::AssignOwner;
const OwnedWaitQueue::WakeThreadsResult wake_threads_result =
wake_futex_ref->waiters_.WakeAndRequeue(requeue_futex_ref->waiters_, new_requeue_owner,
wake_count, requeue_count, wake_hook,
requeue_hook, wake_option);
// Now, if we successfully woke any threads from the wake_futex, then we need
// to adjust the number of references we are holding by that number of
// threads. They are on the hot-path out of FutexWake, and we are responsible
// for their pending op refs.
wake_futex_ref.SetExtraRefs(wake_threads_result.woken);
}
// If we got to here then we have no user copy faults that need retrying, so we should break out
// of the infinite loop.
break;
}
// Now just return. The futex states will return to the pool as needed.
return ZX_OK;
}
// Get the KOID of the current owner of the specified futex, if any, or ZX_KOID_INVALID if there
// is no known owner.
zx_status_t FutexContext::FutexGetOwner(user_in_ptr<const zx_futex_t> value_ptr,
user_out_ptr<zx_koid_t> koid_out) {
zx_status_t result;
// Make sure the futex pointer is following the basic rules.
result = ValidateFutexPointer(value_ptr);
if (result != ZX_OK) {
return result;
}
// Attempt to find the futex. If it is not in the active set, then there is no owner.
zx_koid_t koid = ZX_KOID_INVALID;
FutexId futex_id(value_ptr);
FutexState::PendingOpRef futex_ref = FindActiveFutex(futex_id);
// We found a FutexState in the active set. It may have an owner, but we need
// to enter the queue's lock in order to check.
if (futex_ref != nullptr) {
{ // explicit lock scope
SingletonChainLockGuardIrqSave guard{futex_ref->waiters_.get_lock(),
CLT_TAG("FutexContext::FutexGetOwner")};
if (const Thread* owner = futex_ref->waiters_.owner(); owner != nullptr) {
// Any thread which owns a FutexState's wait queue *must* be a
// user mode thread.
DEBUG_ASSERT(owner->user_thread() != nullptr);
koid = owner->user_thread()->get_koid();
}
}
}
return koid_out.copy_to_user(koid);
}