blob: 92dbbcd8ef2494b1666100cc2a8c46abc1763294 [file] [edit]
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/objects/waiter-queue-node.h"
#include "src/base/macros.h"
#include "src/base/platform/time.h"
#include "src/heap/local-heap-inl.h"
namespace v8 {
namespace internal {
namespace detail {
WaiterQueueNode::WaiterQueueNode(Isolate* requester)
: requester_(requester), should_wait_(true) {}
WaiterQueueNode::~WaiterQueueNode() {
// Since waiter queue nodes are allocated on the stack, they must be removed
// from the intrusive linked list once they go out of scope, otherwise there
// will be dangling pointers.
SbxCheckNotInList();
}
// static
void WaiterQueueNode::Enqueue(WaiterQueueNode** head,
WaiterQueueNode* new_tail) {
DCHECK_NOT_NULL(head);
new_tail->SbxCheckNotInList();
WaiterQueueNode* current_head = *head;
if (current_head == nullptr) {
new_tail->next_ = new_tail;
new_tail->prev_ = new_tail;
*head = new_tail;
} else {
WaiterQueueNode* current_tail = current_head->prev_;
current_tail->next_ = new_tail;
current_head->prev_ = new_tail;
new_tail->next_ = current_head;
new_tail->prev_ = current_tail;
}
}
WaiterQueueNode* WaiterQueueNode::DequeueMatching(
WaiterQueueNode** head, const DequeueMatcher& matcher) {
DCHECK_NOT_NULL(head);
DCHECK_NOT_NULL(*head);
WaiterQueueNode* const original_head = *head;
WaiterQueueNode* cur = *head;
do {
if (matcher(cur)) {
WaiterQueueNode* const next = cur->next_;
if (next == cur) {
// The queue contains exactly 1 node.
DCHECK_EQ(cur, original_head);
*head = nullptr;
} else {
// The queue contains >1 nodes.
if (cur == original_head) {
// The matched node is the original head, so next is the new head.
WaiterQueueNode* tail = original_head->prev_;
next->prev_ = tail;
tail->next_ = next;
*head = next;
} else {
// The matched node is in the middle of the queue, so the head does
// not need to be updated.
cur->prev_->next_ = next;
next->prev_ = cur->prev_;
}
}
cur->SetNotInListForVerification();
return cur;
}
cur = cur->next_;
} while (cur != original_head);
return nullptr;
}
// static
WaiterQueueNode* WaiterQueueNode::Dequeue(WaiterQueueNode** head) {
return DequeueMatching(head, [](WaiterQueueNode* node) { return true; });
}
// static
WaiterQueueNode* WaiterQueueNode::Split(WaiterQueueNode** head,
uint32_t count) {
DCHECK_GT(count, 0);
DCHECK_NOT_NULL(head);
DCHECK_NOT_NULL(*head);
WaiterQueueNode* front_head = *head;
WaiterQueueNode* back_head = front_head;
uint32_t actual_count = 0;
while (actual_count < count) {
back_head = back_head->next_;
// The queue is shorter than the requested count, return the whole queue.
if (back_head == front_head) {
*head = nullptr;
return front_head;
}
actual_count++;
}
WaiterQueueNode* front_tail = back_head->prev_;
WaiterQueueNode* back_tail = front_head->prev_;
// Fix up the back list (i.e. remainder of the list).
back_head->prev_ = back_tail;
back_tail->next_ = back_head;
*head = back_head;
// Fix up and return the front list (i.e. the dequeued list).
front_head->prev_ = front_tail;
front_tail->next_ = front_head;
return front_head;
}
// static
int WaiterQueueNode::LengthFromHead(WaiterQueueNode* head) {
WaiterQueueNode* cur = head;
int len = 0;
do {
len++;
cur = cur->next_;
} while (cur != head);
return len;
}
void WaiterQueueNode::Wait() {
AllowGarbageCollection allow_before_parking;
requester_->main_thread_local_heap()->ExecuteWhileParked([this]() {
base::MutexGuard guard(&wait_lock_);
while (should_wait_) {
wait_cond_var_.Wait(&wait_lock_);
}
});
}
// Returns false if timed out, true otherwise.
bool WaiterQueueNode::WaitFor(const base::TimeDelta& rel_time) {
bool result;
AllowGarbageCollection allow_before_parking;
requester_->main_thread_local_heap()->ExecuteWhileParked([this, rel_time,
&result]() {
base::MutexGuard guard(&wait_lock_);
base::TimeTicks current_time = base::TimeTicks::Now();
base::TimeTicks timeout_time = current_time + rel_time;
for (;;) {
if (!should_wait_) {
result = true;
return;
}
current_time = base::TimeTicks::Now();
if (current_time >= timeout_time) {
result = false;
return;
}
base::TimeDelta time_until_timeout = timeout_time - current_time;
bool wait_res = wait_cond_var_.WaitFor(&wait_lock_, time_until_timeout);
USE(wait_res);
// The wake up may have been spurious, so loop again.
}
});
return result;
}
void WaiterQueueNode::Notify() {
base::MutexGuard guard(&wait_lock_);
should_wait_ = false;
wait_cond_var_.NotifyOne();
SetNotInListForVerification();
}
uint32_t WaiterQueueNode::NotifyAllInList() {
WaiterQueueNode* cur = this;
uint32_t count = 0;
do {
WaiterQueueNode* next = cur->next_;
cur->Notify();
cur = next;
count++;
} while (cur != this);
return count;
}
void WaiterQueueNode::SbxCheckNotInList() const {
SBXCHECK_EQ(nullptr, next_);
SBXCHECK_EQ(nullptr, prev_);
}
void WaiterQueueNode::SetNotInListForVerification() {
next_ = prev_ = nullptr;
}
} // namespace detail
} // namespace internal
} // namespace v8