blob: 558b8d66b915f5ac4ca9826ccf3052c87274b320 [file] [log] [blame]
// Copyright 2018 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/owned_wait_queue.h"
#include <lib/counters.h>
#include <lib/fit/defer.h>
#include <lib/kconcurrent/chainlock.h>
#include <zircon/compiler.h>
#include <arch/mp.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/enum_bits.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/scheduler.h>
#include <kernel/wait_queue_internal.h>
#include <ktl/algorithm.h>
#include <ktl/bit.h>
#include <ktl/type_traits.h>
#include <object/thread_dispatcher.h>
#include <ktl/enforce.h>
// Notes on the defined kernel counters.
//
// Adjustments (aka promotions and demotions)
// The number of times that a thread either gained or lost inherited profile
// pressure as a result of a PI event.
//
// Note that the number of promotions does not have to equal the number of
// demotions in the system. For example, a thread could slowly gain weight as
// fair scheduled threads join a wait queue it owns, then suddenly drop
// back down to its base profile when the thread releases ownership of the
// queue.
//
// In addition to simple promotions and demotions, the number of threads whose
// effective profile changed as a result of another thread's base profile
// changing is also tracked, although whether these changes amount to a
// promotion or a demotion is not computed.
//
// Max chain traversal.
// The maximum traversed length of a PI chain during execution of the propagation
// algorithm.
//
// The length of a propagation chain is defined as the number of nodes in an
// inheritance graph which are affected by a propagation event. For example, if
// a thread (T1) blocks in an owned wait queue (OWQ1), adding an edge between
// them, and the wait queue has no owner, then the propagation event's chain
// length is 1. This is regardless of whether or not the blocking thread
// currently owns one or more wait queues upstream from it. The OWQ1's IPVs were
// updated, but no other nodes in the graph were affected. If the OWQ1 had been
// owned by a running/runnable thread (T2), then the chain length of the
// operation would have been two instead, since both OWQ1 and T2 needed to be
// visited and updated.
KCOUNTER(pi_promotions, "kernel.pi.adj.promotions")
KCOUNTER(pi_demotions, "kernel.pi.adj.demotions")
KCOUNTER(pi_bp_changed, "kernel.pi.adj.bp_changed")
KCOUNTER_DECLARE(max_pi_chain_traverse, "kernel.pi.max_chain_traverse", Max)
namespace {
enum class ChainLengthTrackerOpt : uint32_t {
None = 0,
RecordMaxLength = 1,
EnforceLengthGuard = 2,
};
FBL_ENABLE_ENUM_BITS(ChainLengthTrackerOpt)
// By default, we always maintain the max length counter, and we enforce the
// length guard in everything but release builds.
constexpr ChainLengthTrackerOpt kEnablePiChainGuards =
((LK_DEBUGLEVEL > 0) ? ChainLengthTrackerOpt::EnforceLengthGuard : ChainLengthTrackerOpt::None);
constexpr ChainLengthTrackerOpt kDefaultChainLengthTrackerOpt =
ChainLengthTrackerOpt::RecordMaxLength | kEnablePiChainGuards;
template <ChainLengthTrackerOpt Options = kDefaultChainLengthTrackerOpt>
class ChainLengthTracker {
public:
using Opt = ChainLengthTrackerOpt;
ChainLengthTracker() {
if constexpr (Options != Opt::None) {
nodes_visited_ = 0;
}
}
~ChainLengthTracker() {
if constexpr ((Options & Opt::EnforceLengthGuard) != Opt::None) {
// Note, the only real reason that this is an accurate max at all is
// because the counter is effectively protected by the thread lock
// (although there is no real good way to annotate that fact). When we
// finally remove the thread lock, we are going to need to do better than
// this.
auto old = max_pi_chain_traverse.ValueCurrCpu();
if (nodes_visited_ > old) {
max_pi_chain_traverse.Set(nodes_visited_);
}
}
}
void NodeVisited() {
if constexpr (Options != Opt::None) {
++nodes_visited_;
}
if constexpr ((Options & Opt::EnforceLengthGuard) != Opt::None) {
constexpr uint32_t kMaxChainLen = 2048;
ASSERT_MSG(nodes_visited_ <= kMaxChainLen, "visited %u", nodes_visited_);
}
}
private:
uint32_t nodes_visited_ = 0;
};
using AddSingleEdgeTag = decltype(OwnedWaitQueue::AddSingleEdgeOp);
using RemoveSingleEdgeTag = decltype(OwnedWaitQueue::RemoveSingleEdgeOp);
using BaseProfileChangedTag = decltype(OwnedWaitQueue::BaseProfileChangedOp);
inline bool IpvsAreConsequential(const SchedulerState::InheritedProfileValues* ipvs) {
return (ipvs != nullptr) && ((ipvs->total_weight != SchedWeight{0}) ||
(ipvs->uncapped_utilization != SchedUtilization{0}));
}
template <typename UpstreamType, typename DownstreamType>
void Propagate(UpstreamType& upstream, DownstreamType& downstream, AddSingleEdgeTag)
TA_REQ(chainlock_transaction_token, internal::GetPiNodeLock(upstream),
internal::GetPiNodeLock(downstream), preempt_disabled_token) {
Scheduler::JoinNodeToPiGraph(upstream, downstream);
if constexpr (ktl::is_same_v<DownstreamType, Thread>) {
pi_promotions.Add(1u);
}
}
template <typename UpstreamType, typename DownstreamType>
void Propagate(UpstreamType& upstream, DownstreamType& downstream, RemoveSingleEdgeTag)
TA_REQ(chainlock_transaction_token, internal::GetPiNodeLock(upstream),
internal::GetPiNodeLock(downstream), preempt_disabled_token) {
Scheduler::SplitNodeFromPiGraph(upstream, downstream);
if constexpr (ktl::is_same_v<DownstreamType, Thread>) {
pi_demotions.Add(1u);
}
}
template <typename UpstreamType, typename DownstreamType>
void Propagate(UpstreamType& upstream, DownstreamType& downstream, BaseProfileChangedTag)
TA_REQ(chainlock_transaction_token, internal::GetPiNodeLock(upstream),
internal::GetPiNodeLock(downstream), preempt_disabled_token) {
Scheduler::UpstreamThreadBaseProfileChanged(upstream, downstream);
if constexpr (ktl::is_same_v<DownstreamType, Thread>) {
pi_bp_changed.Add(1u);
}
}
} // namespace
OwnedWaitQueue::~OwnedWaitQueue() {
// Something is very very wrong if we have been allowed to destruct while we
// still have an owner.
DEBUG_ASSERT(owner_ == nullptr);
}
void OwnedWaitQueue::DisownAllQueues(Thread* t) {
ChainLockTransactionIrqSave clt{CLT_TAG("OwnedWaitQueue::DisownAllQueues")};
for (;; clt.Relax()) {
// We should have cleared the "can own wait queues" flag by now, which
// should guarantee that we cannot possibly become the owner of any new
// queues. Our state, however, should still be "RUNNING". Once we have
// finally emptied the our list of owned queues, the exiting thread can
// fully transition to the DEATH state.
UnconditionalChainLockGuard guard{t->lock_};
DEBUG_ASSERT(t->can_own_wait_queues_ == false);
DEBUG_ASSERT(t->scheduler_state().state() == THREAD_RUNNING);
DEBUG_ASSERT(t->wait_queue_state_.blocking_wait_queue_ == nullptr);
while (!t->wait_queue_state_.owned_wait_queues_.is_empty()) {
OwnedWaitQueue& queue = t->wait_queue_state_.owned_wait_queues_.front();
if (queue.get_lock().Acquire() == ChainLock::LockResult::kBackoff) {
break;
}
queue.get_lock().AssertAcquired();
t->wait_queue_state_.owned_wait_queues_.pop_front();
queue.owner_ = nullptr;
queue.get_lock().Release();
}
if (t->wait_queue_state_.owned_wait_queues_.is_empty()) {
break;
}
}
clt.Finalize();
}
OwnedWaitQueue::WakeThreadsResult OwnedWaitQueue::WakeThreadsLocked(Thread::UnblockList threads,
IWakeRequeueHook& wake_hooks,
WakeOption option) {
DEBUG_ASSERT(magic() == kOwnedMagic);
uint32_t woken{0};
// Start by removing any existing owner. We will either wake a thread an make
// it the new owner, or we are simply going to remove the owner entirely.
// Once we have removed the old owner, we can unlock the PI chain starting
// from the old owner (if any)
if (Thread* const old_owner = owner_; old_owner != nullptr) {
AssignOwnerInternal(nullptr);
if (old_owner) {
old_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*old_owner);
}
}
// Now wake all of the threads we had selected
while (!threads.is_empty()) {
Thread* const t = threads.pop_front();
t->get_lock().AssertAcquired();
// Call the user's hook, allowing them to maintain any internal bookkeeping
// they need to maintain.
wake_hooks.OnWakeOrRequeue(*t);
// Remove this thread's contributions to our IPVs, removing it from our
// collection in the process.
DequeueThread(t, ZX_OK);
UpdateSchedStateStorageThreadRemoved(*t);
BeginPropagate(*t, *this, RemoveSingleEdgeOp);
// If we still have blocked threads, and we are supposed to make this thread
// our owner, do so now. We removed our existing owner at the start of this
// operation, and it is illegal to attempt to wake multiple threads and make
// them each our owner, so our owner should still be nullptr.
//
// TODO(johngro): We may need a tweak to the propagation. Our thread is in
// an intermediate state right now. It has been removed from its wait
// queue, but its state is still blocked since it has not been through
// Scheduler::Unblock yet. The easiest thing to do might be to just
// directly assign our IPVs from our current wait queue stats and set owner_
// directly.
if (!IsEmpty() && (option == WakeOption::AssignOwner)) {
DEBUG_ASSERT(owner_ == nullptr);
AssignOwnerInternal(t);
}
// Finally, unblock the thread, drop its lock in the process.
Scheduler::Unblock(t);
++woken;
}
// If our WakeOption was None, we should no longer have an owner. Otherwise,
// it was AssignOwner, in which case we should only have an owner if we still
// have blocked threads.
DEBUG_ASSERT(((option == WakeOption::None) && (owner_ == nullptr)) ||
((option == WakeOption::AssignOwner) && (!IsEmpty() || (owner_ == nullptr))));
ValidateSchedStateStorage();
return WakeThreadsResult{.woken = woken, .still_waiting = Count(), .owner = owner_};
}
void OwnedWaitQueue::ValidateSchedStateStorageUnconditional() {
if (inherited_scheduler_state_storage_ != nullptr) {
bool found = false;
for (const Thread& t : this->collection_.threads()) {
AssertInWaitQueue(t, *this);
if (&t.wait_queue_state().inherited_scheduler_state_storage_ ==
inherited_scheduler_state_storage_) {
found = true;
break;
}
}
DEBUG_ASSERT(found);
} else {
DEBUG_ASSERT(this->IsEmpty());
}
}
SchedulerState::InheritedProfileValues OwnedWaitQueue::SnapshotThreadIpv(Thread& thread) {
const SchedulerState& tss = thread.scheduler_state();
SchedulerState::InheritedProfileValues ret = tss.inherited_profile_values_;
const SchedulerState::BaseProfile& bp = tss.base_profile_;
if (bp.inheritable) {
if (bp.discipline == SchedDiscipline::Fair) {
ret.total_weight += bp.fair.weight;
} else {
DEBUG_ASSERT(ret.min_deadline != SchedDuration{0});
ret.uncapped_utilization += bp.deadline.utilization;
ret.min_deadline = ktl::min(ret.min_deadline, bp.deadline.deadline_ns);
}
}
return ret;
}
void OwnedWaitQueue::ApplyIpvDeltaToThread(const SchedulerState::InheritedProfileValues* old_ipv,
const SchedulerState::InheritedProfileValues* new_ipv,
Thread& thread) {
DEBUG_ASSERT((old_ipv != nullptr) || (new_ipv != nullptr));
SchedWeight weight_delta = new_ipv ? new_ipv->total_weight : SchedWeight{0};
SchedUtilization util_delta = new_ipv ? new_ipv->uncapped_utilization : SchedUtilization{0};
if (old_ipv != nullptr) {
weight_delta -= old_ipv->total_weight;
util_delta -= old_ipv->uncapped_utilization;
}
SchedulerState& tss = thread.scheduler_state();
SchedulerState::InheritedProfileValues& thread_ipv = tss.inherited_profile_values_;
tss.effective_profile_.MarkInheritedProfileChanged();
thread_ipv.total_weight += weight_delta;
thread_ipv.uncapped_utilization += util_delta;
DEBUG_ASSERT(thread_ipv.total_weight >= SchedWeight{0});
DEBUG_ASSERT(thread_ipv.uncapped_utilization >= SchedUtilization{0});
// If a set of IPVs is going away, and the value which is going away was the
// minimum, then we need to recompute the new minimum by checking the
// minimum across all of this thread's owned wait queues.
//
// TODO(johngro): Consider keeping the set of owned wait queues as a WAVL
// tree, indexed by minimum relative deadline, so that this can be
// maintained in O(1) time instead of O(N).
//
// Notes on locking:
//
// We need to iterate over our set of owned queues, and find the minimum
// deadline across all of the queues. It is not immediately obvious why it is
// OK to do this. We are holding our thread's lock, meaning that queues we
// own cannot leave our collection (mutating the collection requires holding
// both the thread's lock as well as the lock of the queue which is being
// added or removed), but we are not explicitly holding the queue's lock.
//
// So, why is it OK for us to examine the queue's state? Specifically, how
// many threads are blocked in it, and what the minimum deadline across those
// threads is currently? It is because, any modification to the upstream
// queue's state which would mutate the inherited minimum deadline would also
// require the mutator to hold the entire PI locking chain from (at least) our
// upstream queue to the eventual target, which (since we currently own this
// queue) must pass through us as well.
//
// So, any previous change made to this value (while we were the queue's
// owner) had to be done holding this thread's lock as well (since the entire
// path is locked). This include any change which adds or removes us as the
// queue's owner. Since we are now holding this thread's lock, any of those
// previous mutations must have happened-before us, and no new mutations can
// take place until we drop our lock.
//
if ((new_ipv != nullptr) && (new_ipv->min_deadline <= thread_ipv.min_deadline)) {
thread_ipv.min_deadline = ktl::min(thread_ipv.min_deadline, new_ipv->min_deadline);
} else {
if ((old_ipv != nullptr) && (old_ipv->min_deadline <= thread_ipv.min_deadline)) {
SchedDuration new_min_deadline{SchedDuration::Max()};
for (auto& other_queue : thread.wait_queue_state().owned_wait_queues_) {
AssertOwnsWaitQueue(thread, other_queue);
if (!other_queue.IsEmpty()) {
DEBUG_ASSERT(other_queue.inherited_scheduler_state_storage_ != nullptr);
const SchedulerState::InheritedProfileValues& other_ipvs =
other_queue.inherited_scheduler_state_storage_->ipvs;
new_min_deadline = ktl::min(new_min_deadline, other_ipvs.min_deadline);
}
}
thread_ipv.min_deadline = new_min_deadline;
}
if (new_ipv != nullptr) {
thread_ipv.min_deadline = ktl::min(thread_ipv.min_deadline, new_ipv->min_deadline);
}
}
DEBUG_ASSERT(thread_ipv.min_deadline > SchedDuration{0});
}
void OwnedWaitQueue::ApplyIpvDeltaToOwq(const SchedulerState::InheritedProfileValues* old_ipv,
const SchedulerState::InheritedProfileValues* new_ipv,
OwnedWaitQueue& owq) {
SchedWeight weight_delta = new_ipv ? new_ipv->total_weight : SchedWeight{0};
SchedUtilization util_delta = new_ipv ? new_ipv->uncapped_utilization : SchedUtilization{0};
if (old_ipv != nullptr) {
weight_delta -= old_ipv->total_weight;
util_delta -= old_ipv->uncapped_utilization;
}
DEBUG_ASSERT(!owq.IsEmpty());
DEBUG_ASSERT(owq.inherited_scheduler_state_storage_ != nullptr);
SchedulerState::WaitQueueInheritedSchedulerState& iss = *owq.inherited_scheduler_state_storage_;
iss.ipvs.total_weight += weight_delta;
iss.ipvs.uncapped_utilization += util_delta;
iss.ipvs.min_deadline = owq.collection_.MinInheritableRelativeDeadline();
DEBUG_ASSERT(iss.ipvs.total_weight >= SchedWeight{0});
DEBUG_ASSERT(iss.ipvs.uncapped_utilization >= SchedUtilization{0});
DEBUG_ASSERT(iss.ipvs.min_deadline > SchedDuration{0});
}
template <OwnedWaitQueue::PropagateOp OpType>
void OwnedWaitQueue::BeginPropagate(Thread& upstream_node, OwnedWaitQueue& downstream_node,
PropagateOpTag<OpType> op) {
// When needed, base profile changes will directly call FinishPropagate.
static_assert(OpType != PropagateOp::BaseProfileChanged);
SchedulerState::InheritedProfileValues ipv_snapshot;
// Are we starting from a thread during an edge remove operation? If so,
// and we were the last thread to leave the queue, then there is no longer
// any IPV storage for our downstream wait queue which needs to be updated.
// If the wait queue has no owner either, then we are done with propagation.
if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
if (downstream_node.IsEmpty() && (downstream_node.owner_ == nullptr)) {
return;
}
}
ipv_snapshot = SnapshotThreadIpv(upstream_node);
if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
FinishPropagate(upstream_node, downstream_node, nullptr, &ipv_snapshot, op);
} else if constexpr (OpType == PropagateOp::AddSingleEdge) {
FinishPropagate(upstream_node, downstream_node, &ipv_snapshot, nullptr, op);
}
}
template <OwnedWaitQueue::PropagateOp OpType>
void OwnedWaitQueue::BeginPropagate(OwnedWaitQueue& upstream_node, Thread& downstream_node,
PropagateOpTag<OpType> op) {
// When needed, base profile changes will directly call FinishPropagate.
static_assert(OpType != PropagateOp::BaseProfileChanged);
if constexpr (OpType == PropagateOp::AddSingleEdge) {
// If we are adding an owner to this OWQ, we should be able to assert that
// it does not currently have one.
DEBUG_ASSERT(upstream_node.owner_ == nullptr);
DEBUG_ASSERT(!upstream_node.InContainer());
upstream_node.owner_ = &downstream_node;
downstream_node.wait_queue_state().owned_wait_queues_.push_back(&upstream_node);
} else if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
// If we are removing the owner of this OWQ, or we are updating the base
// profile of the immediately upstream thread, we should be able to assert
// that the owq's current owner is the thread passed to this method.
DEBUG_ASSERT(upstream_node.owner_ == &downstream_node);
DEBUG_ASSERT(upstream_node.InContainer());
downstream_node.wait_queue_state().owned_wait_queues_.erase(upstream_node);
upstream_node.owner_ = nullptr;
}
// If the OWQ we are starting from has no active waiters, then there are no
// IPV deltas to propagate. After updating the links, we are finished.
if (upstream_node.IsEmpty()) {
return;
}
DEBUG_ASSERT(upstream_node.inherited_scheduler_state_storage_ != nullptr);
SchedulerState::InheritedProfileValues& ipvs =
upstream_node.inherited_scheduler_state_storage_->ipvs;
if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
FinishPropagate(upstream_node, downstream_node, nullptr, &ipvs, op);
} else if constexpr (OpType == PropagateOp::AddSingleEdge) {
FinishPropagate(upstream_node, downstream_node, &ipvs, nullptr, op);
}
}
template <OwnedWaitQueue::PropagateOp OpType, typename UpstreamNodeType,
typename DownstreamNodeType>
void OwnedWaitQueue::FinishPropagate(UpstreamNodeType& upstream_node,
DownstreamNodeType& downstream_node,
const SchedulerState::InheritedProfileValues* added_ipv,
const SchedulerState::InheritedProfileValues* lost_ipv,
PropagateOpTag<OpType> op) {
// Propagation must start from a(n) (OWQ|Thread) and proceed to a(n) (Thread|OWQ).
static_assert((ktl::is_same_v<UpstreamNodeType, OwnedWaitQueue> &&
ktl::is_same_v<DownstreamNodeType, Thread>) ||
(ktl::is_same_v<UpstreamNodeType, Thread> &&
ktl::is_same_v<DownstreamNodeType, OwnedWaitQueue>),
"Bad types for FinishPropagate. Must be either OWQ -> Thread, or Thread -> OWQ");
constexpr bool kStartingFromThread = ktl::is_same_v<UpstreamNodeType, Thread>;
// If neither the IPVs we are adding, nor the IPVs we are removing, are
// "consequential" (meaning, the have either some fair weight, or some
// deadline capacity, or both), then we can just get out now. There are no
// effective changes to propagate.
if (!IpvsAreConsequential(added_ipv) && !IpvsAreConsequential(lost_ipv)) {
return;
}
// Set up the pointers we will use as iterators for traversing the inheritance
// graph. Snapshot the starting node's current inherited profile values which
// we need to propagate.
OwnedWaitQueue* owq_iter;
Thread* thread_iter;
if constexpr (kStartingFromThread) {
thread_iter = &upstream_node;
owq_iter = &downstream_node;
// Is this a base profile changed operation? If so, we should already have
// a link between our thread and the downstream owned wait queue. Go ahead
// and ASSERT this. We don't need to bother to check the other
// combinations; those have already been asserted during
// BeginPropagate.
if constexpr (OpType == PropagateOp::BaseProfileChanged) {
thread_iter->get_lock().AssertHeld();
DEBUG_ASSERT_MSG(
thread_iter->wait_queue_state().blocking_wait_queue_ == static_cast<WaitQueue*>(owq_iter),
"blocking wait queue %p owq_iter %p",
thread_iter->wait_queue_state().blocking_wait_queue_, static_cast<WaitQueue*>(owq_iter));
}
} else {
// Base profile changes should never start from OWQs.
static_assert(OpType != PropagateOp::BaseProfileChanged);
owq_iter = &upstream_node;
thread_iter = &downstream_node;
owq_iter->get_lock().AssertHeld();
DEBUG_ASSERT(!owq_iter->IsEmpty());
}
if constexpr (OpType == PropagateOp::AddSingleEdge) {
DEBUG_ASSERT(added_ipv != nullptr);
DEBUG_ASSERT(lost_ipv == nullptr);
} else if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
DEBUG_ASSERT(added_ipv == nullptr);
DEBUG_ASSERT(lost_ipv != nullptr);
} else if constexpr (OpType == PropagateOp::BaseProfileChanged) {
static_assert(
kStartingFromThread,
"Base profile propagation changes may only start from Threads, not OwnedWaitQueues");
DEBUG_ASSERT(added_ipv != nullptr);
DEBUG_ASSERT(lost_ipv != nullptr);
} else {
static_assert(OpType != OpType, "Unrecognized propagation operation");
}
// When we have finally finished updating everything, make sure to update
// our max traversal statistic.
ChainLengthTracker len_tracker;
// OK - we are finally ready to get to work. Use a slightly-evil(tm) goto in
// order to start our propagate loop with the proper phase (either
// thread-to-OWQ first, or OWQ-to-thread first)
if constexpr (kStartingFromThread == false) {
goto start_from_owq;
} else if constexpr (OpType == PropagateOp::RemoveSingleEdge) {
// Are we starting from a thread during an edge remove operation? If so,
// and if we were the last thread to leave our wait queue, then we don't
// need to bother to update its IPVs anymore (it cannot have any IPVs if it
// has no waiters), so we can just skip it an move on to its owner thread.
//
// Additionally, we know that it must have an owner thread at this point in
// time. If if didn't, BeginPropagate would have already bailed out.
owq_iter->get_lock().AssertHeld();
if (owq_iter->IsEmpty()) {
thread_iter = owq_iter->owner_;
DEBUG_ASSERT(thread_iter != nullptr);
goto start_from_owq;
}
}
while (true) {
{
// We should not be here if this OWQ has no waiters, or if we have not
// found a place to store our ISS. That special case was handled above.
owq_iter->get_lock().AssertHeld();
DEBUG_ASSERT(owq_iter->Count() > 0);
DEBUG_ASSERT(owq_iter->inherited_scheduler_state_storage_ != nullptr);
// Record what our deadline pressure was before we accumulate the upstream
// pressure into this node. We will need it to reason about what to do
// with our dynamic scheduling parameters after IPV accumulation.
//
// OWQs which are receiving deadline pressure have defined dynamic
// scheduler parameters (start_time, end_time, time_slice_ns) finish
// times, which they inherited from their upstream deadline threads. Fair
// threads do not have things like a defined start time while they are
// blocked, they will get a new set of dynamic parameters the next time
// they unblock and are scheduled to run.
//
// After we have finished accumulating the IPV deltas, we a few different
// potential situations:
//
// 1) The utilization (deadline pressure) has not changed. Therefore,
// nothing about the dynamic parameters needs to change either.
// 2) The utilization has changed, and it was 0 before. This is the first
// deadline thread to join the queue, so we can just copy its dynamic
// parameters.
// 3) The utilization has changed, and it is now 0. The final deadline
// thread has left this queue, and our dynamic params are now
// undefined. Strictly speaking, we don't have to do anything, but in
// builds with extra checks enabled, we reset the dynamic parameters
// so that they have a deterministic value.
// 4) The utilization has changed, but it was not zero before, and isn't
// zero now either. We call into the scheduler code to compute what
// the new dynamic parameters should be.
//
SchedulerState::WaitQueueInheritedSchedulerState& owq_iss =
*owq_iter->inherited_scheduler_state_storage_;
const SchedUtilization utilization_before = owq_iss.ipvs.uncapped_utilization;
ApplyIpvDeltaToOwq(lost_ipv, added_ipv, *owq_iter);
const SchedUtilization utilization_after = owq_iss.ipvs.uncapped_utilization;
if (utilization_before != utilization_after) {
if (utilization_before == SchedUtilization{0}) {
// First deadline thread just arrived, copy its parameters.
thread_iter->get_lock().AssertHeld();
const SchedulerState& ss = thread_iter->scheduler_state();
owq_iss.start_time = ss.start_time_;
owq_iss.finish_time = ss.finish_time_;
owq_iss.time_slice_ns = ss.time_slice_ns_;
} else if (utilization_after == SchedUtilization{0}) {
// Last deadline thread just left, reset our dynamic params.
owq_iss.ResetDynamicParameters();
} else {
// The overall utilization has changed, but there was deadline
// pressure both before and after. We need to recompute the dynamic
// scheduler parameters.
Propagate(upstream_node, *owq_iter, op);
}
}
// If we no longer have any deadline pressure, our parameters should now
// be reset to initialization defaults.
if (utilization_after == SchedUtilization{0}) {
owq_iss.AssertDynamicParametersAreReset();
}
// Advance to the next thread, if any. If there isn't another thread,
// then we are finished, simply break out of the propagation loop.
len_tracker.NodeVisited();
thread_iter = owq_iter->owner_;
if (thread_iter == nullptr) {
break;
}
}
// clang-format off
[[maybe_unused]] start_from_owq:
// clang-format on
{
// Propagate from the current owq_iter to the current thread_iter.
// Apply the change in pressure to the next thread in the chain.
thread_iter->get_lock().AssertHeld();
ApplyIpvDeltaToThread(lost_ipv, added_ipv, *thread_iter);
Propagate(upstream_node, *thread_iter, op);
len_tracker.NodeVisited();
owq_iter = DowncastToOwq(thread_iter->wait_queue_state().blocking_wait_queue_);
if (owq_iter == nullptr) {
break;
}
}
}
}
Thread::UnblockList OwnedWaitQueue::LockForWakeOperation(uint32_t max_wake,
IWakeRequeueHook& wake_hooks) {
ChainLockTransaction& active_clt = ChainLockTransaction::ActiveRef();
for (;; active_clt.Relax()) {
// Grab our lock first.
get_lock().AcquireUnconditionally();
// Try to lock all of the other things we need to lock.
ktl::optional<Thread::UnblockList> maybe_unblock_list =
LockForWakeOperationLocked(max_wake, wake_hooks);
if (!maybe_unblock_list.has_value()) {
// Failure; unlock, back off, and try again.
get_lock().Release();
continue;
}
// Success! Return the list of locked threads ready to be woken.
return ktl::move(maybe_unblock_list).value();
}
}
ktl::optional<Thread::UnblockList> OwnedWaitQueue::LockForWakeOperationLocked(
uint32_t max_wake, IWakeRequeueHook& wake_hooks) {
using LockResult = ChainLock::LockResult;
// TODO(https://fxbug.dev/333747818): Find a good way to assert that
// `max_wake` is greater than zero while still supporting a zero count coming
// from the futex APIs (zx_futex_wake and zx_futex_requeue).
// If we have an owner, locking the path starting from it. We are either going
// to set the ownership of this queue to (singular) thread that we wake, or
// we are going to be removing ownership entirely. Either way, we need to
// hold the locks along the path of the existing owner (if any).
const LockResult lock_res = LockPiChain(*this);
if (lock_res == LockResult::kBackoff) {
return ktl::nullopt;
}
DEBUG_ASSERT(lock_res == LockResult::kOk);
// Lock as many threads as we can, moving them out of our wait collection
// and onto a temporary unblock list as we go.
const zx_time_t now = current_time();
ktl::optional<Thread::UnblockList> maybe_unblock_list =
LockAndMakeWaiterListLocked(now, max_wake, wake_hooks);
// If we didn't get a list back (even an empty one), then we need to back off
// and try again. Drop the locks we obtained starting from our owner (if
// any).
if (!maybe_unblock_list.has_value()) {
if (owner_ != nullptr) {
owner_->get_lock().AssertAcquired();
UnlockPiChainCommon(*owner_);
}
return ktl::nullopt;
}
// Success! We now have all of the locks we need; return the list of
// threads we have locked and now need to wake.
return ktl::move(maybe_unblock_list).value();
}
OwnedWaitQueue::RequeueLockingDetails OwnedWaitQueue::LockForRequeueOperation(
OwnedWaitQueue& requeue_target, Thread* new_requeue_target_owner, uint32_t max_wake,
uint32_t max_requeue, IWakeRequeueHook& wake_hooks, IWakeRequeueHook& requeue_hooks) {
using LockResult = ChainLock::LockResult;
ChainLockTransaction& active_clt = ChainLockTransaction::ActiveRef();
for (;; active_clt.Relax()) {
// Start by locking the this queue and the requeue target.
if (const LockResult lock_res =
AcquireChainLockSet(ktl::array{&get_lock(), &requeue_target.get_lock()});
lock_res == LockResult::kBackoff) {
continue;
}
get_lock().AssertAcquired();
requeue_target.get_lock().AssertAcquired();
// Next, the threads we plan to wake/requeue, placing them on two different lists.
RequeueLockingDetails res;
{
ktl::optional<WakeRequeueThreadDetails> maybe_threads =
LockAndMakeWakeRequeueThreadListsLocked(current_time(), max_wake, wake_hooks, max_requeue,
requeue_hooks);
if (!maybe_threads.has_value()) {
get_lock().Release();
requeue_target.get_lock().Release();
continue;
}
res.threads = ktl::move(maybe_threads).value();
}
// Helpers which will release the locks for the threads we just locked,
// taking them off their unblock lists in the process, if we have to backoff
// and retry.
auto UnlockAndClearThreadList = [](Thread::UnblockList list)
TA_REQ(chainlock_transaction_token) {
while (!list.is_empty()) {
Thread* t = list.pop_front();
t->get_lock().AssertAcquired();
t->get_lock().Release();
}
};
auto UnlockLockedThreads = [&]() TA_REQ(chainlock_transaction_token) {
UnlockAndClearThreadList(ktl::move(res.threads.wake_threads));
UnlockAndClearThreadList(ktl::move(res.threads.requeue_threads));
};
// A helper which we can use to unlock owner chains we may be holding during
// a backoff situation.
auto UnlockOwnerChain = [](Thread* owner, const void* stop)
TA_REQ(chainlock_transaction_token) {
if ((owner != nullptr) && (owner != stop)) {
owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*owner, stop);
}
};
// Next, lock the proposed new owner of the requeue target (if any),
// validating the choice of owner in the process. We need to reject this
// new owner selection if it is not allowed to own wait queues (in the
// process of exiting) or if it would form a cycle after this operation. A
// cycle can be formed if the new owner chain passes through a thread
// already in the requeue target (stopping at the requeue target queue
// itself), or if it passes through any of the threads we intend to requeue.
if (new_requeue_target_owner) {
const auto variant_res =
LockPiChainCommon<LockingBehavior::StopAtCycle>(*new_requeue_target_owner);
// Since we chose the "StopAtCycle" behavior, if we got a LockResult back,
// then we either succeeded locking the chain, or we need to back off.
if (ktl::holds_alternative<LockResult>(variant_res)) {
if (ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kBackoff) {
UnlockLockedThreads();
get_lock().Release();
requeue_target.get_lock().Release();
continue;
}
res.new_requeue_target_owner = new_requeue_target_owner;
DEBUG_ASSERT(ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kOk);
} else {
// if we found a stopping point, and that stopping point is not the
// wake-queue, or a thread we plan to wake, we need to deny the new
// owner, dropping the locks we just obtained in the process.
const void* const stop = ktl::get<const void*>(variant_res);
const bool accept = [&]() {
// If we hit nothing, or we hit the wake-queue, we can accept this owner.
if ((stop == nullptr) || (stop == this)) {
return true;
}
// If we anything in the set of threads to wake, we can accept this
// owner.
for (const Thread& t : res.threads.wake_threads) {
if (stop == &t) {
return true;
}
}
// We hit a lock we already hold, but it was not the wake-queue or a
// thread to be woken. It must have been either a thread being
// requeued or the requeue target itself, and we cannot accept this
// new requeue owner.
return false;
}();
// If we can accept this new owner, stash it and its stop point in the
// result we plan to return. Otherwise, unlock what we have locked and
// stash nothing. Note: We don't want to unlock anything if the stop
// point is the same as the proposed new owner. This can happen if the
// proposed new owner is one of the threads which is being requeued.
if (accept) {
res.new_requeue_target_owner = new_requeue_target_owner;
res.new_requeue_target_owner_stop_point = stop;
} else {
UnlockOwnerChain(new_requeue_target_owner, stop);
}
}
// Reject the proposed new owner if it isn't allowed to own wait queues.
if (res.new_requeue_target_owner) {
res.new_requeue_target_owner->get_lock().AssertHeld();
if (res.new_requeue_target_owner->can_own_wait_queues_ == false) {
UnlockOwnerChain(res.new_requeue_target_owner, res.new_requeue_target_owner_stop_point);
res.new_requeue_target_owner = nullptr;
res.new_requeue_target_owner_stop_point = nullptr;
}
}
}
// Now, if the requeue-target has a current owner, and that owner is
// changing, lock its chain. It is OK if this chain hits any of the locks we
// already hold.
if ((requeue_target.owner_ != nullptr) &&
(requeue_target.owner_ != res.new_requeue_target_owner)) {
const auto variant_res =
LockPiChainCommon<LockingBehavior::StopAtCycle>(*requeue_target.owner_);
// If we have a result, we either succeeded or need to back off.
if (ktl::holds_alternative<LockResult>(variant_res)) {
if (ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kBackoff) {
UnlockOwnerChain(res.new_requeue_target_owner, res.new_requeue_target_owner_stop_point);
UnlockLockedThreads();
get_lock().Release();
requeue_target.get_lock().Release();
continue;
}
DEBUG_ASSERT(ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kOk);
} else {
// Record the stopping point.
res.old_requeue_target_owner_stop_point = ktl::get<const void*>(variant_res);
}
}
// Finally, lock the exiting owner chain for the wake-queue. It is OK if
// this chain hits any of the locks we already hold.
if (this->owner_ != nullptr) {
const auto variant_res = LockPiChainCommon<LockingBehavior::StopAtCycle>(*this->owner_);
// If we have a result, we either succeeded or need to back off.
if (ktl::holds_alternative<LockResult>(variant_res)) {
if (ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kBackoff) {
UnlockOwnerChain(requeue_target.owner_, res.old_requeue_target_owner_stop_point);
UnlockOwnerChain(res.new_requeue_target_owner, res.new_requeue_target_owner_stop_point);
UnlockLockedThreads();
get_lock().Release();
requeue_target.get_lock().Release();
continue;
}
DEBUG_ASSERT(ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kOk);
} else {
// Record the stopping point.
res.old_wake_queue_owner_stop_point = ktl::get<const void*>(variant_res);
}
}
// Success!
return res;
}
}
OwnedWaitQueue::BAAOLockingDetails OwnedWaitQueue::LockForBAAOOperation(
Thread* const current_thread, Thread* new_owner) {
DEBUG_ASSERT(current_thread == Thread::Current::Get());
ChainLockTransaction& active_clt = ChainLockTransaction::ActiveRef();
for (;; active_clt.Relax()) {
// Start by unconditionally locking this queue.
get_lock().AcquireUnconditionally();
// Now try to lock the rest of the things we need to lock, adjusting the
// proposed owner as needed to prevent any cycles which might otherwise form.
ktl::optional<BAAOLockingDetails> maybe_lock_details =
LockForBAAOOperationLocked(current_thread, new_owner);
// If we got details back, then we are done. We should be holding all of the
// locks we need to hold (including the current thread's lock).
if (maybe_lock_details.has_value()) {
current_thread->get_lock().AssertAcquired();
return maybe_lock_details.value();
}
// We encountered some form of backoff error. Drop the queue lock and try again.
get_lock().Release();
}
}
ktl::optional<OwnedWaitQueue::BAAOLockingDetails> OwnedWaitQueue::LockForBAAOOperationLocked(
Thread* const current_thread, Thread* new_owner) {
using LockResult = ChainLock::LockResult;
DEBUG_ASSERT(current_thread == Thread::Current::Get());
// Start by trying to obtain the current thread's lock.
if (const LockResult lock_res = current_thread->get_lock().Acquire();
lock_res == LockResult::kBackoff) {
return ktl::nullopt;
}
// Note: Ideally this would be an AssertAcquired, not an AssertHeld.
// Unfortunately, this method may or may not end up acquiring the current
// thread's lock (in addition to others). Because of this, there are no
// annotations guaranteeing whether or not we successfully acquired the lock.
//
// If we AssertAcquired here, then things work well for the failure case; we
// are forced to release our lock as we should be forced to. In the success
// case, however, where we are supposed to obtain the current thread's lock,
// we will trigger an error in the static analysis if we attempt to do this;
// specifically that we are holding the current threads lock when we exit
// (which is as it should be). So, for now, we just AssertHeld instead of
// AssertAcquired. This will allow us to release the lock when we need to
// back off and try again, but will not trigger an error in the case that we
// succeed and exit holding the lock. The downside of this approach is that
// we can forget to release the lock in the failure case, and the compiler
// will not call us out on the mistake.
//
// TODO(johngro): Look into a better way to do this. Can we use the implicit
// coercion of an optional into a bool to use the TRY_ACQUIRE annotation?
// Failing that, perhaps we could add a "fake release" method to the ChainLock
// which will allow us to lie to the compiler and tell it that we released the
// lock on the success path when we actually didn't?
current_thread->get_lock().AssertHeld();
// Now attempt to lock the rest of the old owner/new owner chain, backing
// off if we have to. This common code path with automatically deal with
// nack'ing the proposed new owner when appropriate.
const auto variant_res = LockForOwnerReplacement(new_owner, current_thread);
if (ktl::holds_alternative<LockResult>(variant_res)) {
// The only valid lock result we can get back from this operation is
// Backoff. LockForOwnerReplacement should handle any cycles, and if the
// locking operation succeeds, it is going to return locking details
// instead of an "OK" LockResult.
DEBUG_ASSERT(ktl::get<LockResult>(variant_res) == ChainLock::LockResult::kBackoff);
current_thread->get_lock().Release();
return ktl::nullopt;
}
// Success, return the locking details.
return ktl::get<ReplaceOwnerLockingDetails>(variant_res);
}
ktl::variant<ChainLock::LockResult, OwnedWaitQueue::ReplaceOwnerLockingDetails>
OwnedWaitQueue::LockForOwnerReplacement(Thread* _new_owner, const Thread* blocking_thread,
bool propagate_new_owner_cycle_error) {
// We are holding the queue lock, and we have a proposed new owner. We need
// to hold all of appropriate locks, and potentially nak the new owner
// proposal.
//
// Start with the most trivial reason we might disallow a chosen new owner.
// The new owner of a wait queue cannot be the same as the thread who is
// blocking as this would obviously generate an immediate cycle. We can
// trivially check for that and rule it out first thing.
//
// After that, we need to consider a few different special cases.
//
// In each of these cases, it is possible for us to detect a cycle which would
// be created if the operation were allowed to proceed as requested.
// Currently, we do not allow these operations to fail (users must always be
// able to block their threads), and we do not have a defined job policy which
// would allow us to terminate a process which attempts to create a PI cycle.
// So, for now, instead we simply change the new owner to become "nothing",
// preventing the cycle from forming in the first place.
//
// Case 1 : new owner == old owner
//
// If the old owner and the new owner are the same, we simply attempt to
// lock the path starting from the current owner. If this detects a cycle,
// we will start again, but this time with new owner == nullptr != old
// owner. This can happen if there is a thread blocking which the new owner
// is currently blocked upstream from. For example, in the diagram below, if
// T3 attempts to block in Q1, declaring T1 to be the queue owner.
//
// +----+ +----+ +----+ +----+ +----+ +----+
// | Q1 | --> | T1 | --> | Q2 | --> | T2 | --> | Q3 | --> | T3 |
// +----+ +----+ +----+ +----+ +----+ +----+
//
// Case 2 : new owner != old owner && new owner != nullptr
//
// We are either adding a new owner, or replacing an exiting owner with a
// new one. Start by locking the new owner path. If this detects a cycle,
// we set the newly proposed owner to nullptr and start again. Otherwise,
// once the new owner path is locked, we proceed as in case #2 but with a
// small modification. If following the old owner path leads to a detected
// cycle, it could be for one of two reasons. If this is a BAAO operation, it
// may be that the addition of the blocking thread to the target wait queue
// would introduce a cycle (as in case #2), or it could be that the old owner
// path converges with the new owner path. Neither one of these cases is
// illegal; we just need to stop locking the old path when we hit this
// intersection point, and remember that when it comes time to unlock the old
// path after removing the old owner, we need to stop when we encounter this
// intersection point.
//
// Case 3 : new owner != old owner && new owner == nullptr
//
// If we are removing an existing owner, then we can simply lock the old owner
// path, tolerating any cycles we encounter (but marking where they are) in
// the process. If we end up detecting a cycle, it can only be because we are
// involved in a BAAO operation, and the old owner is was blocked behind the
// thread who is currently blocking. All we need to do is assert this, and
// make sure that when we are unlocking the old owner path that we stop at the
// blocking thread and do not go any further.
//
// If we detect a cycle for any other reason, there must be something wrong.
// The old owner cannot be involved in a path which does not pass through a
// thread which is blocking. If it were, it would imply that there was
// already a cycle in the graph before the proposed BAAO operation (which
// would violate the PI graph invariants).
using LockResult = ChainLock::LockResult;
ReplaceOwnerLockingDetails res{_new_owner};
Thread*& new_owner = res.new_owner;
Thread* const old_owner = owner_;
const bool old_owner_is_blocking = (blocking_thread == old_owner);
// Disallow the trivial case of "the new owner cannot also be the blocking
// thread" before getting in handling the more complicated cases.
if (new_owner == blocking_thread) {
new_owner = nullptr;
}
// Case 1, try to validate our existing owner (if any)
if (new_owner == old_owner) {
// If we have no owner, and we are not assigning a new owner, then we are
// done.
if (old_owner == nullptr) {
return res;
}
// Lock the chain starting from the current owner. This is case 1 where the old
// owner and proposed new owner are the same. Since the new owner cannot be
// the blocking thread, this means that we can be sure that the old owner is
// also not the blocking thread.
DEBUG_ASSERT(old_owner_is_blocking == false);
const auto variant_res = LockPiChainCommon<LockingBehavior::StopAtCycle>(*new_owner);
const void* stop_point = nullptr;
// If we didn't detect a cycle, and the existing owner is allowed to own
// wait queues (not in the process of exiting) we are finished. We either
// succeeded and can simply get out holding the locks we have, or we failed
// an need to back-off (which we can do immediately since we didn't obtain
// any new locks).
if (ktl::holds_alternative<LockResult>(variant_res)) {
LockResult lock_res = ktl::get<LockResult>(variant_res);
if (lock_res != LockResult::kOk) {
DEBUG_ASSERT(lock_res == LockResult::kBackoff);
return lock_res;
}
new_owner->get_lock().AssertHeld();
if (new_owner->can_own_wait_queues_ == true) {
return res;
}
} else {
// We found a cycle which would have been formed by attempting to block a
// thread who is currently downstream of this queue's owner. Assert this,
// then drop the locks we acquired and force the new owner to be nullptr in
// order to break the cycle. Then proceed to the new_owner != old_owner
// logic (since, they no longer match).
//
// Note: we may not have locked anything at all. It is possible that
// someone attempted to block this queue's current owner in the queue
// itself. We need to make sure to check for this special case before
// calling UnlockPiChainCommon.
stop_point = ktl::get<const void*>(variant_res);
DEBUG_ASSERT_MSG(
stop_point == static_cast<const void*>(blocking_thread),
"Detected cycle point is not the blocking thread. (BT %p this %p stop_point %p)",
blocking_thread, this, stop_point);
}
// If we made it to this point, we either detected an unacceptable cycle, or
// our new owner was rejected because it is exiting and is not allowed to
// take ownership of the queue. Unlock any locks that we held, reject the
// new owner proposal, and fall into case 3.
if (new_owner->get_lock().MarkNeedsReleaseIfHeld()) {
UnlockPiChainCommon(*new_owner, stop_point);
}
new_owner = nullptr;
}
// If we made it this far, we must be in case 2 or 3 (the owner is changing)
DEBUG_ASSERT(new_owner != old_owner);
// Case 2, we are replacing the old owner (if any) with a new one;
if (new_owner != nullptr) {
// Start by attempting to lock the new owner path.
const LockResult new_owner_lock_res =
ktl::get<LockResult>(LockPiChainCommon<LockingBehavior::RefuseCycle>(*new_owner));
if (new_owner_lock_res == LockResult::kOk) {
// Things went well and we got the locks. Check to make sure that our new
// owner is actually allowed to own wait queues before proceeding.
const bool can_own_wait_queues = [new_owner]() TA_REQ(chainlock_transaction_token) {
new_owner->get_lock().AssertHeld();
return new_owner->can_own_wait_queues_;
}();
if (can_own_wait_queues == false) {
// Our new owner is not allowed to own queues. Unlock it, and reject the
// proposal.
new_owner->get_lock().MarkNeedsRelease();
UnlockPiChainCommon(*new_owner);
new_owner = nullptr;
if (old_owner == nullptr) {
return res;
}
}
// We now either hold the new owner chain, or have rejected the new owned
// because it is not allowed to own queues. If there is no current owner
// we are finished.
if (old_owner == nullptr) {
return res;
}
// If we didn't reject the new owner, we now need to lock the old owner
// chain.
if (new_owner != nullptr) {
// Lock the old owner path, but tolerate a detected cycle. There are
// two special edge cases to consider here:
//
// First, the old owner may be the thread who is blocking (and is
// declaring a different owner in the process). The blocking thread is
// currently running, and therefore cannot itself be blocked (there is
// nothing downstream of it), and we already hold its lock. So, if the
// blocking thread is the same as the old owner, there are no new locks
// to obtain. We just need to specify the blocking thread as the unlock
// stopping point for the operation.
//
// Second, while locking the old_owner path (old owner != blocking
// thread), we may run into the blocking thread (as in case 2), or we
// may run into a node which is also on the new owner path. Either is
// OK since we are replacing the old owner with a (now validated) new
// one, we just need to remember where to stop unlocking after we have
// replaced the old owner with a new one.
const auto variant_res =
(old_owner_is_blocking == false)
? LockPiChainCommon<LockingBehavior::StopAtCycle>(*old_owner)
: ktl::variant<ChainLock::LockResult, const void*>{blocking_thread};
if (ktl::holds_alternative<LockResult>(variant_res)) {
const LockResult old_owner_lock_res = ktl::get<LockResult>(variant_res);
// Since we chose StopAtCycle behavior for locking the old_owner path,
// we know the result cannot be CycleDetected. It is either OK (and
// we are done) or Backoff (and we need to try again).
DEBUG_ASSERT(old_owner_lock_res != LockResult::kCycleDetected);
if (old_owner_lock_res == LockResult::kOk) {
// Things went well, we are done.
return res;
}
// Need to back off and try again. Drop the locks we were holding
// before restarting.
DEBUG_ASSERT(old_owner_lock_res == LockResult::kBackoff);
new_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*new_owner);
return old_owner_lock_res;
} else {
// Success (although our paths intersect in some way). Record our
// stopping point and get out.
DEBUG_ASSERT(ktl::holds_alternative<const void*>(variant_res));
res.stop_point = ktl::get<const void*>(variant_res);
DEBUG_ASSERT(res.stop_point != static_cast<const void*>(this));
return res;
}
}
} else if (new_owner_lock_res == LockResult::kBackoff) {
// Need to back off and try again. We should not be holding any new
// locks, so we can just propagate the error up and out.
return new_owner_lock_res;
} else {
// We found a cycle, so we have to nack this new owner. If the caller asked us to propagate
// the error up the stack, we can just do so now. We are not holding any extra locks (yet).
// Otherwise, reject the new owner proposal (storing nullptr in the details structure we are
// going to return) and proceed as if the caller is going remove the current owner entirely,
// instead of replacing it. If we don't have a current owner, then we are finished. If we do,
// we can simply drop into the Case 2 handler (below).
DEBUG_ASSERT(new_owner_lock_res == LockResult::kCycleDetected);
if (propagate_new_owner_cycle_error) {
return LockResult::kCycleDetected;
}
new_owner = nullptr;
if (old_owner == nullptr) {
return res;
}
}
}
// The only option left is that we are now in case 3; we are removing the
// existing owner. We either started in that situation, or managed to get
// there via one of our cycle detection mitigations. Check the old owner path
// to see if there is a temporary cycle that we need to deal with.
DEBUG_ASSERT((new_owner == nullptr) && (old_owner != nullptr));
// See case 2 (above). If the old owner is the blocking thread, there is
// nothing more to lock, and we need to remember to stop unlocking at the old
// owner during owner reassignment.
const auto variant_res = (old_owner_is_blocking == false)
? LockPiChainCommon<LockingBehavior::StopAtCycle>(*old_owner)
: ktl::variant<ChainLock::LockResult, const void*>{blocking_thread};
if (ktl::holds_alternative<LockResult>(variant_res)) {
const LockResult lock_res = ktl::get<LockResult>(variant_res);
// Since we chose StopAtCycle behavior for locking the old_owner path, we
// know the result cannot be CycleDetected. It is either OK (and we are
// done) or Backoff (and we need to try again).
DEBUG_ASSERT(lock_res != LockResult::kCycleDetected);
if (lock_res == LockResult::kOk) {
// Things went well, we are done.
return res;
}
// Need to back off and try again. We are not holding any new locks, so we
// can just propagate the error.
DEBUG_ASSERT(lock_res == LockResult::kBackoff);
return lock_res;
} else {
// We must have found a cycle. If we did, we successfully locked up until
// the point in the graph where that cycle was detected, and marked that as
// our stop point. Since we are removing the old owner, we know that there
// must be a blocking thread, and the cycle we detected must have been found
// at that point. ASSERT this, then Record our stop point in the results
// and we are done.
DEBUG_ASSERT(ktl::holds_alternative<const void*>(variant_res));
res.stop_point = ktl::get<const void*>(variant_res);
DEBUG_ASSERT_MSG(
res.stop_point == static_cast<const void*>(blocking_thread),
"Detected cycle point is not the blocking thread. (BT %p this %p stop_point %p)",
blocking_thread, this, res.stop_point);
return res;
}
}
ktl::optional<Thread::UnblockList> OwnedWaitQueue::LockAndMakeWaiterListLocked(
zx_time_t now, uint32_t max_count, IWakeRequeueHook& hooks) {
// Try to lock a set of threads to wake. If we succeeded, make sure we place
// them back into the collection before returning the list.
ktl::optional<Thread::UnblockList> res = TryLockAndMakeWaiterListLocked(now, max_count, hooks);
if (res.has_value()) {
for (Thread& t : res.value()) {
t.get_lock().AssertHeld();
collection_.Insert(&t);
}
}
return res;
}
ktl::optional<OwnedWaitQueue::WakeRequeueThreadDetails>
OwnedWaitQueue::LockAndMakeWakeRequeueThreadListsLocked(zx_time_t now, uint32_t max_wake_count,
IWakeRequeueHook& wake_hooks,
uint32_t max_requeue_count,
IWakeRequeueHook& requeue_hooks) {
// Start by trying to lock the set of threads to wake.
ktl::optional<Thread::UnblockList> wake_res =
TryLockAndMakeWaiterListLocked(now, max_wake_count, wake_hooks);
if (!wake_res.has_value()) {
return ktl::nullopt;
}
// Success! The threads we have selected for waking have been temporarily
// removed from the wake-queue, allowing us to now select the set of threads
// to requeue. If we fail, make sure we unlock the threads we selected for
// wake and return them to the collection.
ktl::optional<Thread::UnblockList> requeue_res =
TryLockAndMakeWaiterListLocked(now, max_requeue_count, requeue_hooks);
if (!requeue_res.has_value()) {
UnlockAndClearWaiterListLocked(ktl::move(wake_res).value());
return ktl::nullopt;
}
// Success, we have managed to lock both sets of threads. Add them back to
// the collection before proceeding.
WakeRequeueThreadDetails ret{
.wake_threads = ktl::move(wake_res).value(),
.requeue_threads = ktl::move(requeue_res).value(),
};
for (Thread::UnblockList* list : ktl::array{&ret.wake_threads, &ret.requeue_threads}) {
for (Thread& t : *list) {
t.get_lock().AssertHeld();
collection_.Insert(&t);
}
}
return ret;
}
ktl::optional<Thread::UnblockList> OwnedWaitQueue::TryLockAndMakeWaiterListLocked(
zx_time_t now, uint32_t max_count, IWakeRequeueHook& hooks) {
// Lock as many threads as we can, placing them on a temporary unblock list as
// we go. We remove the threads from the collection as we go, but we do not
// remove the bookkeeping (see the note on optimization below).
using LockResult = ChainLock::LockResult;
Thread::UnblockList list;
// TODO(johngro): Figure out a way to optimize this.
//
// We need to go over the wait queue and select the "next N best threads to
// wake up". The total order of the wait queue, however, is complicated.
// Which thread is the "best" thread depends on what time it is, if there are
// deadline threads in the queue, and whether or not that thread's deadline is
// in the future or the past.
//
// The queue is arranged to make it easy to find the "best thread to wake"
// quickly, but is not arranged to easily iterate in the proper order (best to
// worst to wake) based on a given time.
//
// One way around this is to simply remove the threads from the collection as
// we lock then, and then peek again using the same `now` time as before.
// There are two annoying issues with this, however. If we fail to grab the
// locks, we now have to drop all of the locks and put the threads back into
// the collection as we unwind. This is a lot of pointless re-balancing of
// the tree structure for no good reason.
//
// The second issue is currently that OWQ code involved in waking and
// requeueing threads expects those threads to be a proper member of the
// collection, right up until the point that the threads are actually woken or
// transferred to another queue. This allows for some optimizations ("I don't
// need to propagate X because there are now no more waiters") as well as some
// heavy-duty debug checks (I can add up the IPVs of all the threads in the
// queue, and it should equal my current IPV totals) which become confused if
// we have speculatively removed threads from the collection before actually
// removing them and their bookkeeping (something which requires all of the
// locks to be held).
//
// So, for now, we have a solution which is basically the worst-of-all-worlds.
// During the locking, we _do_ actually remove the threads from the wait queue
// collection as we add them into our temporary unblock list. If we fail and
// need to unwind, we need to unlock and put the threads back into the
// collection. But, _additionally_, if we succeed, we need to go over the list
// again and put the threads back into the collection, only so the rest of the
// OWQ can take them back out again at the proper point in time.
//
// Ideally, we would find an efficient wait to optimize the iteration of the
// queue given a specific time so that we didn't have to fall back on such
// heavy handed approaches. Failing that, it might be best to special case
// various scenarios. The most common scenarios are ones where we wake either
// a single thread (in which case the search works fine), or all of the
// threads, in which case the enumeration order is less important, although
// could cause some scheduler thrash if done in the wrong order.
// Additionally, the total order is just the enumeration order of the tree if
// all of the deadline threads have absolute deadlines in the future (this
// includes where there are no deadline threads in the queue), so that is a
// simple case as well. The only tricky case is when there exist deadline
// threads in the queue whose deadline is in the past, and we want to wake
// more than one thread.
//
for (uint32_t count = 0; count < max_count; ++count) {
Thread* t = Peek(now);
if (t == nullptr) {
break;
}
// Does our caller approve of this thread? If not, stop here.
const bool stop = [&]() TA_REQ(this->get_lock()) {
AssertInWaitQueue(*t, *this);
return !hooks.Allow(*t);
}();
if (stop) {
break;
}
// We need to back off. Drop any locks we have acquired so far.
if (t->get_lock().Acquire() == LockResult::kBackoff) {
if (!list.is_empty()) {
UnlockAndClearWaiterListLocked(ktl::move(list));
}
return ktl::nullopt;
}
// Cycles should be impossible at this point, so assert that we are holding
// the lock, then add the thread to the list of threads we have locked.
// Then, move on to locking more threads if we need to.
t->get_lock().AssertHeld();
collection_.Remove(t);
list.push_back(t);
}
// Success! Do not attempt to put our threads back just yet. If we are
// making two lists for a WakeAndRequeue operation, we need to keep the
// threads we have selected for wake out of the collection while we select the
// threads for requeue.
return list;
}
void OwnedWaitQueue::UnlockAndClearWaiterListLocked(Thread::UnblockList list) {
// If we encountered a back-off error, we need to unlock each thread we had on
// our list, placing threads back into the collection and clearing the list as
// we go.
while (!list.is_empty()) {
Thread* t = list.pop_front();
t->get_lock().AssertAcquired();
collection_.Insert(t);
t->get_lock().Release();
}
}
template <OwnedWaitQueue::LockingBehavior Behavior, typename StartNodeType>
ktl::variant<ChainLock::LockResult, const void*> OwnedWaitQueue::LockPiChainCommon(
StartNodeType& start) {
Thread* next_thread{nullptr};
WaitQueue* next_wq{nullptr};
if constexpr (ktl::is_same_v<StartNodeType, Thread>) {
next_thread = &start;
} else {
static_assert(ktl::is_same_v<StartNodeType, WaitQueue> ||
ktl::is_same_v<StartNodeType, OwnedWaitQueue>);
next_wq = &start;
goto start_from_next_wq;
}
while (true) {
// If we hit the end of the chain, we are done.
if (next_thread == nullptr) {
return ChainLock::LockResult::kOk;
}
// Try to lock the next thread; if we fail, implement the specified locking
// behavior. We always propagate Backoff error codes. In the case of a
// detected cycle, we either propagate the error, or we leave the current
// path locked and report the location of the cycle in our return code.
if (const ChainLock::LockResult res = next_thread->get_lock().Acquire();
res != ChainLock::LockResult::kOk) {
if constexpr (Behavior == LockingBehavior::StopAtCycle) {
if (res == ChainLock::LockResult::kCycleDetected) {
return static_cast<const void*>(next_thread);
}
}
if (internal::GetPiNodeLock(start).MarkNeedsReleaseIfHeld()) {
UnlockPiChainCommon(start, next_thread);
}
return res;
}
// We just checked for success, skip the assert and just mark this lock as
// held for the static analyzer's sake.
next_thread->get_lock().MarkHeld();
next_wq = next_thread->wait_queue_state().blocking_wait_queue_;
next_thread = nullptr;
[[maybe_unused]] start_from_next_wq:
// If we hit the end of the chain, we are done.
if (next_wq == nullptr) {
return ChainLock::LockResult::kOk;
}
if (const ChainLock::LockResult res = next_wq->get_lock().Acquire();
res != ChainLock::LockResult::kOk) {
if constexpr (Behavior == LockingBehavior::StopAtCycle) {
if (res == ChainLock::LockResult::kCycleDetected) {
return static_cast<const void*>(next_wq);
}
}
if (internal::GetPiNodeLock(start).MarkNeedsReleaseIfHeld()) {
UnlockPiChainCommon(start, next_wq);
}
return res;
}
// We just checked for success, skip the assert and just mark this lock as
// held for the static analyzer's sake.
next_wq->get_lock().MarkHeld();
next_thread = GetQueueOwner(next_wq);
next_wq = nullptr;
}
}
template <typename StartNodeType>
void OwnedWaitQueue::UnlockPiChainCommon(StartNodeType& start, const void* stop_point) {
Thread* next_thread{nullptr};
WaitQueue* next_wq{nullptr};
auto ShouldStopThread = [stop_point](const Thread* t) TA_REQ(chainlock_transaction_token) {
if (stop_point != nullptr) {
const bool stop_point_match = (stop_point == t);
DEBUG_ASSERT(t != nullptr);
DEBUG_ASSERT(stop_point_match || t->get_lock().is_held());
return stop_point_match;
} else {
return (t == nullptr) || (t->get_lock().is_held() == false);
}
};
auto ShouldStopWaitQueue = [stop_point](const WaitQueue* wq) TA_REQ(chainlock_transaction_token) {
if (stop_point != nullptr) {
const bool stop_point_match = (stop_point == wq);
DEBUG_ASSERT(wq != nullptr);
DEBUG_ASSERT(stop_point_match || wq->get_lock().is_held());
return stop_point_match;
} else {
return (wq == nullptr) || (wq->get_lock().is_held() == false);
}
};
// We must currently be holding the starting node's lock. If we found our
// stopping point, have no next node, or we are not currently holding the next
// node's lock, we are done.
DEBUG_ASSERT(&start != stop_point);
if constexpr (ktl::is_same_v<StartNodeType, Thread>) {
next_wq = start.wait_queue_state().blocking_wait_queue_;
// Make sure that we check our stop condition before drop our current node
// lock (here and below). Otherwise, it is possible that the next node is
// not locked, and can go away as soon as we drop the lock on our upstream
// node (here and below).
const bool stop = ShouldStopWaitQueue(next_wq);
start.get_lock().Release();
if (stop) {
return;
}
goto start_from_next_wq;
} else {
static_assert(ktl::is_same_v<StartNodeType, WaitQueue> ||
ktl::is_same_v<StartNodeType, OwnedWaitQueue>);
next_thread = GetQueueOwner(&start);
const bool stop = ShouldStopThread(next_thread);
start.get_lock().Release();
if (stop) {
return;
}
}
while (true) {
// At this point, we should always have a next thread, and it should always
// be locked (we are always checking `is_held` for dropping the previous
// node's lock).
DEBUG_ASSERT(next_thread != nullptr);
next_thread->get_lock().MarkNeedsRelease();
// If we do not have a next node, or that next node is not locked, we can
// drop the thread's lock and get out. Note: it is important to make the
// check to see if the next node is locked before dropping the thread's
// lock. Otherwise, it would be possible for us to realize that we have a
// next node, drop the thread's lock, then have that next node destroyed
// before we are able to check to see if we have it locked.
next_wq = next_thread->wait_queue_state().blocking_wait_queue_;
{
const bool stop = ShouldStopWaitQueue(next_wq);
next_thread->get_lock().Release();
if (stop) {
return;
}
}
[[maybe_unused]] start_from_next_wq:
// At this point, we should always have a next wait queue, and it should
// always be locked. Lock the wait queue in the chain, if any.
DEBUG_ASSERT(next_wq != nullptr);
next_wq->get_lock().MarkNeedsRelease();
// See the note above, we need to check to see if there is a next node, and
// that we have it locked _before_ dropping the locked WQ's lock.
next_thread = GetQueueOwner(next_wq);
{
const bool stop = ShouldStopThread(next_thread);
next_wq->get_lock().Release();
if (stop) {
return;
}
}
}
}
void OwnedWaitQueue::SetThreadBaseProfileAndPropagate(Thread& thread,
const SchedulerState::BaseProfile& profile) {
// To change this thread's base profile, we need to start by locking the
// entire PI chain starting from this thread.
ChainLockTransactionEagerReschedDisableAndIrqSave clt{
CLT_TAG("OwnedWaitQueue::SetThreadBaseProfileAndPropagate")};
for (;; clt.Relax()) {
thread.get_lock().AcquireUnconditionally();
WaitQueue* wq = thread.wait_queue_state().blocking_wait_queue_;
OwnedWaitQueue* owq = DowncastToOwq(wq);
// Lock the rest of the PI chain, restarting if we need to.
if (LockPiChain(thread) == ChainLock::LockResult::kBackoff) {
thread.get_lock().Release();
continue; // Drop all locks and try again.
}
// Finished obtaining locks. We can now propagate.
clt.Finalize();
// If our thread is blocked in an owned wait queue, we need observe the
// thread's transmitted IPVs before and after the base profile change in order
// to properly handle propagation.
SchedulerState::InheritedProfileValues old_ipvs;
if (owq != nullptr) {
old_ipvs = SnapshotThreadIpv(thread);
}
// Regardless of the state of the thread whose base profile has changed, we
// need to update the base profile and let the scheduler know. The scheduler
// code will handle:
// 1) Updating our effective profile
// 2) Repositioning us in our wait queue (if we are blocked)
// 3) Updating our dynamic scheduling parameters (if we are either runnable or
// a blocked deadline thread)
// 4) Updating the scheduler's state (if we happen to be a runnable thread).
SchedulerState& state = thread.scheduler_state();
state.base_profile_ = profile;
state.effective_profile_.MarkBaseProfileChanged();
Scheduler::ThreadBaseProfileChanged(thread);
// Now, if we are blocked in an owned wait queue, propagate the consequences
// of the base profile change downstream.
if (owq != nullptr) {
owq->get_lock().AssertHeld();
SchedulerState::InheritedProfileValues new_ipvs = SnapshotThreadIpv(thread);
FinishPropagate(thread, *owq, &new_ipvs, &old_ipvs, BaseProfileChangedOp);
}
// Finished, drop the locks we are holding and make sure we break out of the
// retry loop.
UnlockPiChainCommon(thread);
break;
}
}
zx_status_t OwnedWaitQueue::AssignOwner(Thread* new_owner) {
using LockResult = ChainLock::LockResult;
DEBUG_ASSERT(magic() == kOwnedMagic);
ChainLockTransactionEagerReschedDisableAndIrqSave clt{CLT_TAG("OwnedWaitQueue::AssignOwner")};
for (;; clt.Relax()) {
UnconditionalChainLockGuard guard{get_lock()};
// If the owner is not changing, we are already done.
if (owner_ == new_owner) {
return ZX_OK;
}
// Obtain the locks needed to change owner. If, while obtaining locks, we
// detect that the new owner proposal would generate a cycle, we nak the
// entire operation with a BAD_STATE error and change nothing.
const auto variant_res = LockForOwnerReplacement(new_owner, nullptr, true);
if (ktl::holds_alternative<LockResult>(variant_res)) {
const LockResult lock_res = ktl::get<LockResult>(variant_res);
if (lock_res == LockResult::kCycleDetected) {
return ZX_ERR_BAD_STATE;
}
// The only other option here is that we are supposed to back off and try
// again. If we had succeeded, we would have gotten back locking details
// instead.
DEBUG_ASSERT(lock_res == LockResult::kBackoff);
continue;
}
// Go ahead and perform the assignment. Start by removing any current
// owner we have, dropping the locks along that path immediately after we
// are done.
const auto& details = ktl::get<ReplaceOwnerLockingDetails>(variant_res);
if (Thread* const old_owner = owner_; old_owner != nullptr) {
old_owner->get_lock().AssertHeld();
BeginPropagate(*this, *old_owner, RemoveSingleEdgeOp);
UnlockPiChainCommon(*old_owner, details.stop_point);
}
// Now, if we have a new owner to assign, perform the assignment, then
// drop the locks along the new owner path.
DEBUG_ASSERT(details.new_owner == new_owner);
if (new_owner != nullptr) {
new_owner->get_lock().AssertHeld();
BeginPropagate(*this, *new_owner, AddSingleEdgeOp);
UnlockPiChainCommon(*new_owner);
}
// We are finished. We will drop our queue lock and re-enable preemption as
// we unwind.
return ZX_OK;
}
}
void OwnedWaitQueue::ResetOwnerIfNoWaiters() {
using LockResult = ChainLock::LockResult;
DEBUG_ASSERT(magic() == kOwnedMagic);
ChainLockTransactionEagerReschedDisableAndIrqSave clt{
CLT_TAG("OwnedWaitQueue::ResetOwnerIfNoWaiters")};
for (;; clt.Relax()) {
UnconditionalChainLockGuard guard{get_lock()};
// If we have no owner, or we have waiters, we are finished.
if (!IsEmpty() || (owner_ == nullptr)) {
return;
}
// We have an owner, but no waiters behind us. We need to lock our owner in
// order to clear out its bookkeeping, but there is nothing to propagate
// down the PI chain since we are not inheriting anything.
if (owner_->get_lock().Acquire() == LockResult::kBackoff) {
// try again
continue;
}
clt.Finalize();
owner_->get_lock().AssertAcquired();
owner_->wait_queue_state_.owned_wait_queues_.erase(*this);
owner_->get_lock().Release();
owner_ = nullptr;
return;
}
}
void OwnedWaitQueue::AssignOwnerInternal(Thread* new_owner) {
// If there is no change, then we are done already.
if (owner_ == new_owner) {
return;
}
// Start by releasing the old owner (if any) and propagating the PI effects.
if (owner_ != nullptr) {
owner_->get_lock().AssertHeld();
BeginPropagate(*this, *owner_, RemoveSingleEdgeOp);
}
// If there is a new owner to assign, do so now and propagate the PI effects.
if (new_owner != nullptr) {
new_owner->get_lock().AssertHeld();
DEBUG_ASSERT(new_owner->can_own_wait_queues_);
BeginPropagate(*this, *new_owner, AddSingleEdgeOp);
}
ValidateSchedStateStorage();
}
zx_status_t OwnedWaitQueue::BlockAndAssignOwner(const Deadline& deadline, Thread* new_owner,
ResourceOwnership resource_ownership,
Interruptible interruptible) {
Thread* const current_thread = Thread::Current::Get();
ChainLockTransactionIrqSave clt{CLT_TAG("OwnedWaitQueue::BlockAndAssignOwner")};
const BAAOLockingDetails details = LockForBAAOOperation(current_thread, new_owner);
clt.Finalize();
const zx_status_t ret = BlockAndAssignOwnerLocked(current_thread, deadline, details,
resource_ownership, interruptible);
// After the block operation, our thread is going to be locked. Make sure to
// drop the lock before exiting.
current_thread->get_lock().Release();
return ret;
}
zx_status_t OwnedWaitQueue::BlockAndAssignOwnerLocked(Thread* const current_thread,
const Deadline& deadline,
const BAAOLockingDetails& details,
ResourceOwnership resource_ownership,
Interruptible interruptible) {
DEBUG_ASSERT(current_thread == Thread::Current::Get());
DEBUG_ASSERT(magic() == kOwnedMagic);
DEBUG_ASSERT(current_thread->state() == THREAD_RUNNING);
const ChainLockTransaction& active_clt = ChainLockTransaction::ActiveRef();
// TODO(johngro):
//
// Locking for this is more tricky than it seems. If we have an original
// owner (A), and a new owner (B), and both of them are blocked, and both of
// them share the same PI target, simply attempting to lock both PI chains
// would result in deadlock detection.
//
// What really needs to happen here is that, as we lock for a BAAO operation
// which involves changing owners, we need to lock the current owner chain
// first (before the new owner chain) and remove the current existing owner.
// Once that is finished, we can attempt to lock the new-owner chain (again,
// if any). If this fails, we need to be prepared to back-off and try again,
// including the possibility that we might need to drop the queue lock, and
// displace yet another new owner in the process.
//
// We should not need to check for any cycles at this point, locking should
// have taken care of that for us. If the there is a change of owner
// happening, start by releasing the current owner. After this, the PI chain
// starting from our previous owner (if any) should be released.
const bool owner_changed = (owner_ != details.new_owner);
if (owner_changed && (owner_ != nullptr)) {
// Remove our old owner, then release the locks which had been held on the
// old owner path, stopping early if we need to do to (see
// LockForBAAOOperation for details). Note, if the old owner being removed
// is the same as the thread which is currently blocking, there is nothing
// to unlock. We need to continue to hold the current thread's lock until
// it blocks, and there cannot be anything downstream of the current thread
// since it is currently running.
Thread* old_owner = owner_;
AssignOwnerInternal(nullptr);
if (current_thread != old_owner) {
// Do not unlock the old owner chain if the old owner is the same as the
// stop point. This can happen if the new owner chain intersects the old
// owner chain start at the old owner itself. In this case, the new owner
// chain is a superset of the old owner chain, and all of the locks will
// be properly released as we finish propagation down the new owner chain.
if (old_owner != details.stop_point) {
old_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*old_owner, details.stop_point);
} else {
old_owner->get_lock().AssertHeld();
}
} else {
DEBUG_ASSERT(details.stop_point == current_thread);
}
}
// Perform the first half of the BlockEtc operation. This will attempt to add
// an edge between the thread which is blocking, and the OWQ it is blocking in
// (this). We know that this cannot produce a cycle in the graph because we
// know that this OWQ does not currently have an owner.
//
// If the block preamble fails, then the state of the actual wait queue is
// unchanged and we can just get out now.
zx_status_t res =
BlockEtcPreamble(current_thread, deadline, 0u, resource_ownership, interruptible);
if (res != ZX_OK) {
// There are only three reasons why the pre-wait operation should ever fail.
//
// 1) ZX_ERR_TIMED_OUT : The timeout has already expired.
// 2) ZX_ERR_INTERNAL_INTR_KILLED : The thread has been signaled for death.
// 3) ZX_ERR_INTERNAL_INTR_RETRY : The thread has been signaled for suspend.
//
// No matter what, we are not actually going to block in the wait queue.
// Even so, however, we still need to assign the owner to what was
// requested by the thread. Just because we didn't manage to block does
// not mean that ownership assignment gets skipped.
ZX_DEBUG_ASSERT((res == ZX_ERR_TIMED_OUT) || (res == ZX_ERR_INTERNAL_INTR_KILLED) ||
(res == ZX_ERR_INTERNAL_INTR_RETRY));
// If the owner was changing, the existing owner should already be nullptr
// at this point. If we have a new owner to assign, do so now, and unlock
// the rest of the PI chain.
//
// Otherwise, if the owner has not changed, and we still have an old owner,
// don't forget to unlock the old owner chain before propagating our error
// out.
if (owner_changed) {
DEBUG_ASSERT(owner_ == nullptr);
if (details.new_owner != nullptr) {
details.new_owner->get_lock().AssertAcquired();
AssignOwnerInternal(details.new_owner);
UnlockPiChainCommon(*details.new_owner);
}
} else if (owner_ != nullptr) {
owner_->get_lock().AssertAcquired();
UnlockPiChainCommon(*owner_);
}
// Now drop our lock and get out. We should be holding only the current
// thread's lock as we exit.
get_lock().Release();
active_clt.AssertNumLocksHeld(1);
return res;
}
// We succeeded in placing our thread into our wait collection. Make sure we
// update the scheduler state storage location if needed, then propagate the
// effects down the chain.
UpdateSchedStateStorageThreadAdded(*current_thread);
BeginPropagate(*current_thread, *this, AddSingleEdgeOp);
// If we have an new_owner, we need to do one of two things.
//
// 1) If the owner has not changed, then the PI consequences were propagated
// when we added the edge from the blocking thread to this OWQ. We just need
// to unlock the PI chain starting from the already existing owner and we
// should be finished.
// 2) If the owner changed, then we should have unassigned the original owner
// at the start of this routine. We need to assign the new owner now, and
// then unlock the chain starting from the new owner.
//
if (owner_changed) {
DEBUG_ASSERT(owner_ == nullptr);
if (details.new_owner != nullptr) {
details.new_owner->get_lock().AssertAcquired();
AssignOwnerInternal(details.new_owner);
UnlockPiChainCommon(*details.new_owner);
}
} else {
if (owner_ != nullptr) {
owner_->get_lock().AssertAcquired();
UnlockPiChainCommon(*owner_);
}
}
// OK, we are almost done at this point in time. We have dealt with all of
// the ownership and PI related bookkeeping, and we have added the blocking
// thread to this owned wait queue. We can now drop the OWQ lock and finish
// the BAAO operation by calling BlockEtcPostable. When we return from the
// block operation, we should have re-acquired the current thread's lock
// (however, it may be with a different lock token).
get_lock().Release();
active_clt.AssertNumLocksHeld(1);
// Finally, go ahead and run the second half of the BlockEtc operation.
// This will actually block our thread and handle setting any timeout timers
// in the process.
// DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!!
//
// It is very important that no attempts to access |this| are made after
// either of the calls to BlockEtcPostamble (below). When someone eventually
// comes along and unblocks us from the queue, they have already taken care of
// removing us from the this wait queue. It it totally possible that the wait
// queue we were blocking in has been destroyed by the time we make it out of
// BlockEtcPostable, making |this| no longer a valid pointer.
//
// DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!! DANGER!!
//
res = BlockEtcPostamble(current_thread, deadline);
DEBUG_ASSERT(&active_clt == ChainLockTransaction::Active());
active_clt.AssertNumLocksHeld(1);
return res;
}
void OwnedWaitQueue::CancelBAAOOperationLocked(Thread* const current_thread,
const BAAOLockingDetails& details) {
// If there is a current owner, handle unlocking the current owner chain.
// There are a few special cases to consider here.
//
// 1) We don't need to unlock starting from here if the owner wasn't going to
// change. If the existing owner == the proposed new owner, we are going
// to unconditionally unlock the new owner chain anyway.
// 2) If the new owner is the same as the current thread (trying to block)
// thread, then the owner must have changed (the blocking thread cannot be the
// owning thread), but we don't want to drop the locks yet. There cannot
// be anything downstream of the current thread (it is currently running),
// and we are going to unconditionally drop the current thread's lock on
// our way out of this method.
// 3) If we are going to unlock starting from the existing owner's chain, we
// need to remember to stop at the stop-point provided by our details. It
// is possible that our old owner, and a proposed new owner, have chains
// that intersect down-the-line, and we don't want to try to double release
// any of the locks held on the shared path between the two.
//
const bool owner_changed = (owner_ != details.new_owner);
if (owner_changed && (owner_ != nullptr) && (current_thread != owner_)) {
owner_->get_lock().AssertAcquired();
UnlockPiChainCommon(*owner_, details.stop_point);
}
// If we had a newly proposed owner, unlock its path. This will also handle
// unlocking the current owner path in the case that the current owner was not
// changing.
if (details.new_owner != nullptr) {
details.new_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*details.new_owner);
}
// We should be holding exactly 2 locks at this point in time. The queue's
// lock and the current thread's lock. Drop these and we should be done.
ChainLockTransaction::Active()->AssertNumLocksHeld(2);
get_lock().Release();
current_thread->get_lock().Release();
}
OwnedWaitQueue::WakeThreadsResult OwnedWaitQueue::WakeThreads(uint32_t wake_count,
IWakeRequeueHook& wake_hooks) {
ChainLockTransactionPreemptDisableAndIrqSave clt{CLT_TAG("OwnedWaitQueue::WakeThreadsResult")};
Thread::UnblockList threads = LockForWakeOperation(wake_count, wake_hooks);
clt.Finalize();
WakeThreadsResult result = WakeThreadsLocked(ktl::move(threads), wake_hooks);
get_lock().Release();
return result;
}
OwnedWaitQueue::WakeThreadsResult OwnedWaitQueue::WakeThreadAndAssignOwner(
IWakeRequeueHook& wake_hooks) {
ChainLockTransactionPreemptDisableAndIrqSave clt{CLT_TAG("OwnedWaitQueue::WakeThreadsResult")};
Thread::UnblockList threads = LockForWakeOperation(1, wake_hooks);
clt.Finalize();
WakeThreadsResult result =
WakeThreadsLocked(ktl::move(threads), wake_hooks, WakeOption::AssignOwner);
get_lock().Release();
return result;
}
OwnedWaitQueue::WakeThreadsResult OwnedWaitQueue::WakeAndRequeue(
OwnedWaitQueue& requeue_target, Thread* new_requeue_owner, uint32_t wake_count,
uint32_t requeue_count, IWakeRequeueHook& wake_hooks, IWakeRequeueHook& requeue_hooks,
WakeOption wake_option) {
ChainLockTransactionPreemptDisableAndIrqSave clt{CLT_TAG("OwnedWaitQueue::WakeAndRequeue")};
RequeueLockingDetails details = LockForRequeueOperation(
requeue_target, new_requeue_owner, wake_count, requeue_count, wake_hooks, requeue_hooks);
clt.Finalize();
return WakeAndRequeueInternal(details, requeue_target, wake_hooks, requeue_hooks, wake_option);
}
OwnedWaitQueue::WakeThreadsResult OwnedWaitQueue::WakeAndRequeueInternal(
RequeueLockingDetails& details, OwnedWaitQueue& requeue_target, IWakeRequeueHook& wake_hooks,
IWakeRequeueHook& requeue_hooks, WakeOption wake_option) {
DEBUG_ASSERT(magic() == kOwnedMagic);
DEBUG_ASSERT(requeue_target.magic() == kOwnedMagic);
// Step 1, remove the target of the wait-queue (this) and unlock its chain,
// assuming that it has any unique nodes in it.
if (owner_ != nullptr) {
Thread* const old_owner = owner_;
AssignOwnerInternal(nullptr);
// Don't attempt to unlock this chain if the old owner of the wake-queue is
// the same as the old owner stop-point. The implication here is that the
// old-wake-queue-owner locking chain overlaps 100% with one of the other
// chains currently held, but which has yet to be dealt with. For example,
// the old owner of the wake-queue could be the same as the old owner of the
// requeue-target, or the proposed new owner of the requeue-target. We need
// to keep holding those chains until later on in the process (after we have
// dealt with their effects).
if (old_owner != details.old_wake_queue_owner_stop_point) {
old_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*old_owner, details.old_wake_queue_owner_stop_point);
}
}
// Step 2: If the requeue target has an owner, and that owner is changing,
// remove the existing owner and unlock the unique nodes in its chain.
const bool requeue_owner_changed = requeue_target.owner_ != details.new_requeue_target_owner;
if (requeue_owner_changed && requeue_target.owner_) {
Thread* const old_requeue_owner = requeue_target.owner_;
requeue_target.AssignOwnerInternal(nullptr);
if (old_requeue_owner != details.old_requeue_target_owner_stop_point) {
old_requeue_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*old_requeue_owner, details.old_requeue_target_owner_stop_point);
}
}
// Step #3, take any of the threads we need to requeue and move them over into
// the requeue target, transferring the bookkeeping from the wake queue over
// to the requeue_target, and dropping each of the threads' locks, in the
// process.
while (!details.threads.requeue_threads.is_empty()) {
Thread* const t = details.threads.requeue_threads.pop_front();
t->get_lock().AssertAcquired();
// Call the user's hook, allowing them to maintain any internal bookkeeping
// they need to maintain.
requeue_hooks.OnWakeOrRequeue(*t);
// Actually move the thread from this to the requeue_target.
this->collection_.Remove(t);
t->wait_queue_state().blocking_wait_queue_ = nullptr;
this->UpdateSchedStateStorageThreadRemoved(*t);
BeginPropagate(*t, *this, RemoveSingleEdgeOp);
requeue_target.collection_.Insert(t);
t->wait_queue_state().blocking_wait_queue_ = &requeue_target;
requeue_target.UpdateSchedStateStorageThreadAdded(*t);
BeginPropagate(*t, requeue_target, AddSingleEdgeOp);
t->get_lock().Release();
}
// Step #4, if we have a new owner for the requeue target, assign it now and
// drop the locking chain starting from the new owner. Otherwise, if our
// owner didn't change, we still need to drop the locking chain starting from
// the old owner (we had been holding it in order to propagate the PI
// consequences of adding the newly requeued threads).
if (requeue_owner_changed) {
DEBUG_ASSERT(requeue_target.owner_ == nullptr);
if (details.new_requeue_target_owner != nullptr) {
// If there is no one waiting in the requeue target, then it is not
// allowed to have an owner.
if (!requeue_target.IsEmpty()) {
details.new_requeue_target_owner->get_lock().AssertHeld();
requeue_target.AssignOwnerInternal(details.new_requeue_target_owner);
}
// Only unlock the new requeue-target owner chain if the new owner != the
// chain's unlock stopping point. This can happen when the new requeue
// target owner specified is a thread which already exists in the
// wake-queue. These threads (and the wake queue itself) are already
// locked, and the locks will be dropped during the final wake operation
// (below).
if (details.new_requeue_target_owner != details.new_requeue_target_owner_stop_point) {
details.new_requeue_target_owner->get_lock().AssertAcquired();
UnlockPiChainCommon(*details.new_requeue_target_owner,
details.new_requeue_target_owner_stop_point);
}
}
} else if (requeue_target.owner_ != nullptr) {
DEBUG_ASSERT(requeue_target.owner_ == details.new_requeue_target_owner);
requeue_target.owner_->get_lock().AssertAcquired();
UnlockPiChainCommon(*requeue_target.owner_, details.new_requeue_target_owner_stop_point);
}
// We are finished with the requeue target and can drop its lock now.
requeue_target.ValidateSchedStateStorage();
requeue_target.get_lock().Release();
// Finally, step #5. Wake the threads we identified as needing to be woken
// during the locking operation, dealing with ownership assignment in the
// process.
WakeThreadsResult wtr =
WakeThreadsLocked(ktl::move(details.threads.wake_threads), wake_hooks, wake_option);
get_lock().Release();
return wtr;
}
// Explicit instantiation of a variant of the generic BeginPropagate method used in
// wait.cc during thread unblock operations.
template void OwnedWaitQueue::BeginPropagate(Thread&, OwnedWaitQueue&, RemoveSingleEdgeTag);
// Explicit instantiation of the common lock/unlock routines.
template ktl::variant<ChainLock::LockResult, const void*>
OwnedWaitQueue::LockPiChainCommon<OwnedWaitQueue::LockingBehavior::RefuseCycle>(Thread& start);
template ktl::variant<ChainLock::LockResult, const void*>
OwnedWaitQueue::LockPiChainCommon<OwnedWaitQueue::LockingBehavior::RefuseCycle>(WaitQueue& start);
template void OwnedWaitQueue::UnlockPiChainCommon(Thread& start, const void* stop_point);
template void OwnedWaitQueue::UnlockPiChainCommon(WaitQueue& start, const void* stop_point);