blob: ad466550be842f6337f4a091f977050d7f596344 [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/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 <kernel/thread_lock.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
// By default, Futex KTracing is disabled as it introduces some overhead in user
// mode operations which might be performance sensitive. Developers who are
// debugging issues which could involve futex interactions may enable the
// tracing by setting this top level flag to true, provided that their
// investigation can tolerate the overhead.
constexpr bool kEnableFutexKTracing = FUTEX_TRACING_ENABLED;
constexpr bool kFutexBlockTracingEnabled = FUTEX_BLOCK_TRACING_ENABLED;
class KTraceBase {
public:
enum class FutexActive { Yes, No };
enum class RequeueOp { Yes, No };
protected:
static constexpr uint32_t kCountSaturate = 0xFE;
static constexpr uint32_t kUnlimitedCount = 0xFFFFFFFF;
};
template <bool Enabled>
class KTrace;
template <>
class KTrace<false> : public KTraceBase {
public:
KTrace() {}
void FutexWait(FutexId futex_id, Thread* new_owner) {}
void FutexWoke(FutexId futex_id, zx_status_t result) {}
void FutexWake(FutexId futex_id, FutexActive active, RequeueOp requeue_op, uint32_t count,
Thread* assigned_owner) {}
void FutexRequeue(FutexId futex_id, FutexActive active, uint32_t count, Thread* assigned_owner) {}
};
template <>
class KTrace<true> : public KTraceBase {
public:
KTrace() : ts_(ktrace_timestamp()) {}
void FutexWait(FutexId futex_id, Thread* new_owner) {
if (ktrace_thunks::category_enabled("kernel:sched"_category)) {
const fxt::Argument futex_id_arg{"futex_id"_intern, futex_id.get()};
const fxt::Argument futex_owner_arg{"new_owner_TID"_intern, fxt::Koid{new_owner->tid()}};
fxt_duration_complete(
"kernel:sched"_category, ts_, ThreadRefFromContext(TraceContext::Thread),
fxt::StringRef{"futex_wait"_intern}, ts_ + 50, futex_id_arg, futex_owner_arg);
}
}
void FutexWoke(FutexId futex_id, zx_status_t result) {
if (ktrace_thunks::category_enabled("kernel:sched"_category)) {
const fxt::Argument futex_id_arg{"futex_id"_intern, futex_id.get()};
const fxt::Argument futex_wait_result_arg{"wait_result"_intern, result};
fxt_duration_complete(
"kernel:sched"_category, ts_, ThreadRefFromContext(TraceContext::Thread),
fxt::StringRef{"futex_woke"_intern}, ts_ + 50, futex_id_arg, futex_wait_result_arg);
}
}
void FutexWake(FutexId futex_id, FutexActive active, RequeueOp requeue_op, uint32_t count,
Thread* assigned_owner) {
if (ktrace_thunks::category_enabled("kernel:sched"_category)) {
if ((count >= kCountSaturate) && (count != kUnlimitedCount)) {
count = kCountSaturate;
}
const fxt::Argument futex_id_arg{"futex_id"_intern, futex_id.get()};
const fxt::Argument futex_owner_arg{
"futex_owner"_intern,
fxt::Koid{assigned_owner ? assigned_owner->tid() : ZX_KOID_INVALID}};
const fxt::Argument futex_count_arg{"futex_count"_intern, count};
const fxt::Argument futex_was_requeue_arg{"futex_was_requeue"_intern,
requeue_op == RequeueOp::Yes};
const fxt::Argument futex_was_active_arg{"futex_was_active"_intern,
active == FutexActive::Yes};
fxt_duration_complete(
"kernel:sched"_category, ts_, ThreadRefFromContext(TraceContext::Thread),
fxt::StringRef{"futex_wake"_intern}, ts_ + 50, futex_id_arg, futex_owner_arg,
futex_count_arg, futex_was_requeue_arg, futex_was_active_arg);
}
}
void FutexRequeue(FutexId futex_id, FutexActive active, uint32_t count, Thread* assigned_owner) {
if (ktrace_thunks::category_enabled("kernel:sched"_category)) {
if ((count >= kCountSaturate) && (count != kUnlimitedCount)) {
count = kCountSaturate;
}
if ((count >= kCountSaturate) && (count != kUnlimitedCount)) {
count = kCountSaturate;
}
const fxt::Argument futex_id_arg{"futex_id"_intern, futex_id.get()};
const fxt::Argument futex_owner_arg{
"futex_owner"_intern,
fxt::Koid(assigned_owner ? assigned_owner->tid() : ZX_KOID_INVALID)};
const fxt::Argument futex_count_arg{"futex_count"_intern, count};
const fxt::Argument futex_was_active_arg{"futex_was_active"_intern,
active == FutexActive::Yes};
fxt_duration_complete("kernel:sched"_category, ts_,
ThreadRefFromContext(TraceContext::Thread),
fxt::StringRef{"futex_wake"_intern}, ts_ + 50, futex_id_arg,
futex_owner_arg, futex_count_arg, futex_was_active_arg);
}
}
private:
const uint64_t ts_;
};
// 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;
}
// 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;
}
using KTracer = KTrace<kEnableFutexKTracing>;
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
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;
};
template <OwnedWaitQueue::Hook::Action action>
OwnedWaitQueue::Hook::Action FutexContext::ResetBlockingFutexId(Thread* thrd, void* ctx) {
// Any thread involved in one of these operations is
// currently blocked on a futex's wait queue, and therefor
// *must* be a user mode thread.
DEBUG_ASSERT((thrd != nullptr) && (thrd->user_thread() != nullptr));
DEBUG_ASSERT(ctx != nullptr);
auto state = reinterpret_cast<ResetBlockingFutexIdState*>(ctx);
thrd->user_thread()->blocking_futex_id_ = FutexId::Null();
++state->count;
return action;
}
template <OwnedWaitQueue::Hook::Action action>
OwnedWaitQueue::Hook::Action FutexContext::SetBlockingFutexId(Thread* thrd, void* ctx) {
// Any thread involved in one of these operations is
// currently blocked on a futex's wait queue, and therefor
// *must* be a user mode thread.
DEBUG_ASSERT((thrd != nullptr) && (thrd->user_thread() != nullptr));
DEBUG_ASSERT(ctx != nullptr);
auto state = reinterpret_cast<SetBlockingFutexIdState*>(ctx);
thrd->user_thread()->blocking_futex_id_ = state->id;
++state->count;
return action;
}
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() TA_EXCL(thread_lock) {
if (t_ != nullptr) {
t_->get_lock()->lock().Release();
t_ = nullptr;
}
}
NullableDispatcherGuard(const NullableDispatcherGuard&) = delete;
NullableDispatcherGuard(NullableDispatcherGuard&&) = delete;
NullableDispatcherGuard& operator=(const NullableDispatcherGuard&) = delete;
NullableDispatcherGuard& operator=(NullableDispatcherGuard&&) = delete;
void ReleaseThreadLockHeld() TA_REL() TA_REQ(thread_lock) {
if (t_ != nullptr) {
t_->get_lock()->lock().ReleaseThreadLocked();
t_ = nullptr;
}
}
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);
KTracer wait_tracer;
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 acuire 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 acquire the ThreadLock, which allows us to release both
// the FutexState lock and the ThreadDispatcher lock.
// 4. We then block this thread and release the ThreadLock.
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.
Thread* new_owner = nullptr;
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.
//
// 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 (!futex_owner_thread->IsDyingOrDeadLocked()) {
new_owner = futex_owner_thread->core_thread_;
}
}
// Record the futex ID of the thread we are about to block on.
current_thread->blocking_futex_id_ = futex_id;
// Defer rescheduling, then enter the thread lock (exchanging the futex context lock and the
// ThreadDispatcher's object lock for the thread spin-lock in the process)
// and wait on the futex wait queue, assigning ownership properly in the
// process.
{
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));
AnnotatedAutoEagerReschedDisabler eager_resched_disabler;
Guard<MonitoredSpinLock, NoIrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::FUTEX);
futex_state_guard.Release();
futex_owner_guard.ReleaseThreadLockHeld();
// We have just released |futex_owner_thread|'s lock (|new_owner_guard|). At this point,
// |futex_owner_thread| is free to begin exiting. However, we are currently holding the
// global thread lock so even though it can begin to exit, it cannot complete the operation
// and, more importantly, cannot destroy its Thread (|new_owner|) until we release the
// thread lock. It's critical that it cannot destroy its thread while we hold the global
// thread lock. Otherwise, |new_owner| could become a dangling pointer.
wait_tracer.FutexWait(futex_id, new_owner);
result = futex_ref->waiters_.BlockAndAssignOwner(
deadline, new_owner, 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();
// 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;
}
}
}
// 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.
KTracer woke_tracer;
if (result == ZX_OK) {
// The FutexWake operation should have already cleared our blocking
// futex ID.
DEBUG_ASSERT(current_thread->blocking_futex_id_ == FutexId::Null());
woke_tracer.FutexWoke(futex_id, result);
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());
woke_tracer.FutexWoke(current_thread->blocking_futex_id_, result);
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);
// Enter the thread lock and 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. If that turns out to be the case
// once we are inside of the thread-lock, we need to clear the wait queue's
// 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.
{
AnnotatedAutoEagerReschedDisabler eager_resched_disabler;
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
if (futex_ref->waiters_.IsEmpty()) {
futex_ref->waiters_.AssignOwner(nullptr);
}
}
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;
KTracer tracer;
// 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) {
tracer.FutexWake(futex_id, KTracer::FutexActive::No, KTracer::RequeueOp::No, wake_count,
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.
ResetBlockingFutexIdState wake_op;
{
// 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, enter the thread lock and actually wake up the threads.
// OwnedWakeQueue will handle the ownership bookkeeping for us.
{
using Action = OwnedWaitQueue::Hook::Action;
Guard<MonitoredSpinLock, NoIrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
// 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.
auto hook = (owner_action == OwnerAction::RELEASE)
? ResetBlockingFutexId<Action::SelectAndKeepGoing>
: ResetBlockingFutexId<Action::SelectAndAssignOwner>;
futex_ref->waiters_.WakeThreads(wake_count, {hook, &wake_op});
// Either our owner action was RELEASE (in which case we should not have
// any owner), or our action was ASSIGN_WOKEN (in which case we should
// _only_ have an owner if there are still waiters remaining.
DEBUG_ASSERT(
((owner_action == OwnerAction::RELEASE) && (futex_ref->waiters_.owner() == nullptr)) ||
((owner_action == OwnerAction::ASSIGN_WOKEN) &&
(!futex_ref->waiters_.IsEmpty() || (futex_ref->waiters_.owner() == nullptr))));
tracer.FutexWake(futex_id, KTracer::FutexActive::Yes, KTracer::RequeueOp::No, wake_op.count,
futex_ref->waiters_.owner());
}
}
// 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_op.count);
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);
KTracer tracer;
// Find the FutexState for the wake and requeue futexes.
FutexId wake_id(wake_ptr);
FutexId requeue_id(requeue_ptr);
KTracer::FutexActive requeue_futex_was_active;
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);
// Check to see if the requeue target was active or not when we fetched it by
// looking at the pending operation ref count. If it is exactly 1, then we
// just activated it. Note that the only reason why we can get away with this
// is that we are still inside of the pool lock.
requeue_futex_was_active = (requeue_futex_ref->pending_operation_count() == 1)
? KTracer::FutexActive::No
: KTracer::FutexActive::Yes;
// 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();
ResetBlockingFutexIdState wake_op;
SetBlockingFutexIdState requeue_op(requeue_id);
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);
// Exchange ThreadDispatcher's object lock for the global ThreadLock.
Guard<MonitoredSpinLock, NoIrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
requeue_owner_guard.ReleaseThreadLockHeld();
// We have just released |requeue_owner_thread|'s lock (|new_owner_guard|). At this point,
// |requeue_owner_thread| is free to begin exiting. However, we are currently holding the
// global thread lock so even though it can begin to exit, it cannot complete the operation
// and, more importantly, cannot destroy its Thread (|new_requeue_owner|) until we release the
// thread lock. It's critical that it cannot destroy its thread while we hold the global
// thread lock. Otherwise, |new_requeue_owner| could become a dangling pointer.
using Action = OwnedWaitQueue::Hook::Action;
auto wake_hook = (owner_action == OwnerAction::RELEASE)
? ResetBlockingFutexId<Action::SelectAndKeepGoing>
: ResetBlockingFutexId<Action::SelectAndAssignOwner>;
auto requeue_hook = SetBlockingFutexId<Action::SelectAndKeepGoing>;
if (requeue_count) {
DEBUG_ASSERT(requeue_futex_ref != nullptr);
wake_futex_ref->waiters_.WakeAndRequeue(wake_count, &(requeue_futex_ref->waiters_),
requeue_count, new_requeue_owner,
{wake_hook, &wake_op}, {requeue_hook, &requeue_op});
} else {
wake_futex_ref->waiters_.WakeThreads(wake_count, {wake_hook, &wake_op});
// We made no attempt to requeue anyone, but we still need to update
// ownership. If it has waiters currently, make sure that we clear out
// any owner, no matter what the user requested. Futexes without
// waiters are not permitted to have owners.
if (requeue_futex_ref->waiters_.IsEmpty()) {
new_requeue_owner = nullptr;
}
requeue_futex_ref->waiters_.AssignOwner(new_requeue_owner);
}
// If we requeued any 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 thread
// 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, requeue_op.count);
tracer.FutexWake(wake_id, KTracer::FutexActive::Yes, KTracer::RequeueOp::Yes, wake_op.count,
wake_futex_ref->waiters_.owner());
tracer.FutexRequeue(requeue_id, requeue_futex_was_active, requeue_op.count,
new_requeue_owner);
}
// 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, 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_op.count);
// 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 thread lock in order to check.
if (futex_ref != nullptr) {
{ // explicit lock scope
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
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);
}