// 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