Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 340b983

Browse files
codebytereaduh95
authored andcommitted
src: run same-priority platform tasks in posting order
TaskQueue became a std::priority_queue when worker tasks started to honor v8::TaskPriority. Its comparator returns false for entry types without a priority member, and for entries of equal priority, on the assumption that the heap then keeps insertion order. It does not: three tasks pushed A, B, C pop as A, C, B, and larger batches come out in heap order. That affects the per-isolate foreground task queue (tasks of one priority no longer run in the order they were posted), the foreground delayed task queue, and the delayed task scheduler of the worker thread task runner, whose local queue is drained in one batch: when v8 posts a delayed worker task shortly before the platform shuts down, the StopTask pushed by Stop() can run before a ScheduleTask that was pushed earlier, that ScheduleTask then starts a timer on the scheduler's loop after all timers were supposed to be stopped, and Shutdown() blocks in uv_thread_join() until the delay (e.g. the 8 s of the memory reducer) expires. Give every queued item a sequence number and use it as the tie breaker, so that tasks of equal priority, and tasks without one, come out in FIFO order again; higher priorities still come first. PopAll() now returns the tasks in that order instead of handing out the heap. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65353 Refs: #58047 Refs: #61999 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 2de845b commit 340b983

3 files changed

Lines changed: 103 additions & 60 deletions

File tree

β€Žsrc/node_platform.ccβ€Ž

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
157157
DelayedTaskScheduler* scheduler =
158158
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
159159

160-
auto tasks_to_run = scheduler->tasks_.Lock().PopAll();
161-
while (!tasks_to_run.empty()) {
162-
// We have to use const_cast because std::priority_queue::top() does not
163-
// return a movable item.
164-
std::unique_ptr<Task> task =
165-
std::move(const_cast<std::unique_ptr<Task>&>(tasks_to_run.top()));
166-
tasks_to_run.pop();
167-
// This runs either the ScheduleTasks that scheduels the timers to
168-
// pop the tasks back into the worker task runner queue, or the
169-
// or the StopTasks to stop the timers and drop all the pending tasks.
160+
// ScheduleTasks (start a timer that pops the task into the worker queue)
161+
// in posting order, then, once Stop() was called, the StopTask.
162+
for (std::unique_ptr<Task>& task : scheduler->tasks_.Lock().PopAll()) {
170163
task->Run();
171164
}
172165
}
@@ -611,15 +604,8 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
611604
boolPerIsolatePlatformData::FlushForegroundTasksInternal() {
612605
bool did_work = false;
613606

614-
auto delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
615-
while (!delayed_tasks_to_schedule.empty()) {
616-
// We have to use const_cast because std::priority_queue::top() does not
617-
// return a movable item.
618-
std::unique_ptr<DelayedTask> delayed =
619-
std::move(const_cast<std::unique_ptr<DelayedTask>&>(
620-
delayed_tasks_to_schedule.top()));
621-
delayed_tasks_to_schedule.pop();
622-
607+
for (std::unique_ptr<DelayedTask>& delayed :
608+
foreground_delayed_tasks_.Lock().PopAll()) {
623609
did_work = true;
624610
uint64_t delay_millis = llround(delayed->timeout * 1000);
625611

@@ -642,18 +628,8 @@ bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
642628
});
643629
}
644630

645-
TaskQueue<TaskQueueEntry>::PriorityQueue tasks;
646-
{
647-
auto locked = foreground_tasks_.Lock();
648-
tasks = locked.PopAll();
649-
}
650-
651-
while (!tasks.empty()) {
652-
// We have to use const_cast because std::priority_queue::top() does not
653-
// return a movable item.
654-
std::unique_ptr<TaskQueueEntry> entry =
655-
std::move(const_cast<std::unique_ptr<TaskQueueEntry>&>(tasks.top()));
656-
tasks.pop();
631+
for (std::unique_ptr<TaskQueueEntry>& entry :
632+
foreground_tasks_.Lock().PopAll()) {
657633
did_work = true;
658634
RunForegroundTask(std::move(entry->task));
659635
}
@@ -788,12 +764,21 @@ template <class T>
788764
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
789765
: queue_(queue), lock_(queue->lock_) {}
790766

767+
template <classT>
768+
std::unique_ptr<T> TaskQueue<T>::PopTask() {
769+
// std::priority_queue::top() only hands out a const reference.
770+
Item& top = const_cast<Item&>(task_queue_.top());
771+
std::unique_ptr<T> task = std::move(top.task);
772+
task_queue_.pop();
773+
return task;
774+
}
775+
791776
template <classT>
792777
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task, bool outstanding) {
793778
if (outstanding) {
794779
queue_->outstanding_tasks_++;
795780
}
796-
queue_->task_queue_.push(std::move(task));
781+
queue_->task_queue_.push({std::move(task), queue_->next_sequence_++});
797782
queue_->tasks_available_.Signal(lock_);
798783
}
799784

@@ -802,10 +787,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
802787
if (queue_->task_queue_.empty()) {
803788
return std::unique_ptr<T>(nullptr);
804789
}
805-
std::unique_ptr<T> result = std::move(
806-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
807-
queue_->task_queue_.pop();
808-
return result;
790+
return queue_->PopTask();
809791
}
810792

811793
template <classT>
@@ -816,10 +798,7 @@ std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
816798
if (queue_->stopped_) {
817799
return std::unique_ptr<T>(nullptr);
818800
}
819-
std::unique_ptr<T> result = std::move(
820-
std::move(const_cast<std::unique_ptr<T>&>(queue_->task_queue_.top())));
821-
queue_->task_queue_.pop();
822-
return result;
801+
return queue_->PopTask();
823802
}
824803

825804
template <classT>
@@ -843,12 +822,19 @@ void TaskQueue<T>::Locked::Stop() {
843822
}
844823

845824
template <classT>
846-
TaskQueue<T>::PriorityQueue TaskQueue<T>::Locked::PopAll() {
847-
TaskQueue<T>::PriorityQueue result;
848-
result.swap(queue_->task_queue_);
825+
std::vector<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
826+
std::vector<std::unique_ptr<T>> result;
827+
result.reserve(queue_->task_queue_.size());
828+
while (!queue_->task_queue_.empty()) {
829+
result.push_back(queue_->PopTask());
830+
}
849831
return result;
850832
}
851833

834+
template classTaskQueue<Task>;
835+
template classTaskQueue<TaskQueueEntry>;
836+
template classTaskQueue<DelayedTask>;
837+
852838
voidMultiIsolatePlatform::DisposeIsolate(Isolate* isolate) {
853839
// The order of these calls is important. When the Isolate is disposed,
854840
// it may still post tasks to the platform, so it must still be registered

β€Žsrc/node_platform.hβ€Ž

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,6 @@ concept has_priority = requires(T t) { t.priority; };
2727
template <classT>
2828
classTaskQueue {
2929
public:
30-
// If the entry type has a priority member, order the priority queue by
31-
// that - higher priority first. Otherwise, maintain insertion order.
32-
structEntryCompare {
33-
booloperator()(const std::unique_ptr<T>& a,
34-
const std::unique_ptr<T>& b) const {
35-
ifconstexpr (has_priority<T>) {
36-
return a->priority < b->priority;
37-
} else {
38-
returnfalse;
39-
}
40-
}
41-
};
42-
43-
using PriorityQueue = std::priority_queue<std::unique_ptr<T>,
44-
std::vector<std::unique_ptr<T>>,
45-
EntryCompare>;
4630
classLocked {
4731
public:
4832
voidPush(std::unique_ptr<T> task, bool outstanding = false);
@@ -51,7 +35,8 @@ class TaskQueue {
5135
voidNotifyOfOutstandingCompletion();
5236
voidBlockingDrain();
5337
voidStop();
54-
PriorityQueue PopAll();
38+
// All queued tasks, in the order Pop() would have returned them.
39+
std::vector<std::unique_ptr<T>> PopAll();
5540

5641
private:
5742
friendclassTaskQueue;
@@ -67,11 +52,33 @@ class TaskQueue {
6752
Locked Lock() { returnLocked(this); }
6853

6954
private:
55+
structItem {
56+
std::unique_ptr<T> task;
57+
uint64_t sequence;
58+
};
59+
// Higher priority first if the entry type has one; posting order otherwise
60+
// and among equal priorities (a sequence number breaks the tie).
61+
structItemCompare {
62+
booloperator()(const Item& a, const Item& b) const {
63+
ifconstexpr (has_priority<T>) {
64+
if (a.task->priority != b.task->priority) {
65+
return a.task->priority < b.task->priority;
66+
}
67+
}
68+
return a.sequence > b.sequence;
69+
}
70+
};
71+
using PriorityQueue =
72+
std::priority_queue<Item, std::vector<Item>, ItemCompare>;
73+
74+
std::unique_ptr<T> PopTask();
75+
7076
Mutex lock_;
7177
ConditionVariable tasks_available_;
7278
ConditionVariable outstanding_tasks_drained_;
7379
int outstanding_tasks_;
7480
bool stopped_;
81+
uint64_t next_sequence_ = 0;
7582
PriorityQueue task_queue_;
7683
};
7784

β€Žtest/cctest/test_platform.ccβ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,53 @@ TEST_F(PlatformTest, TracingControllerNullptr) {
128128
node::SetTracingController(orig_controller);
129129
EXPECT_EQ(node::GetTracingController(), orig_controller);
130130
}
131+
132+
classRecordingTask : publicv8::Task {
133+
public:
134+
RecordingTask(std::vector<int>* log, int id) : log_(log), id_(id) {}
135+
voidRun() override { log_->push_back(id_); }
136+
137+
private:
138+
std::vector<int>* log_;
139+
int id_;
140+
};
141+
142+
TEST(TaskQueueTest, HigherPriorityFirstThenPostingOrder) {
143+
std::vector<int> log;
144+
{
145+
node::TaskQueue<v8::Task> queue;
146+
for (int i = 0; i < 64; i++) {
147+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
148+
}
149+
for (std::unique_ptr<v8::Task>& task : queue.Lock().PopAll()) task->Run();
150+
for (int i = 64; i < 96; i++) {
151+
queue.Lock().Push(std::make_unique<RecordingTask>(&log, i));
152+
}
153+
while (std::unique_ptr<v8::Task> task = queue.Lock().Pop()) task->Run();
154+
}
155+
ASSERT_EQ(log.size(), 96u);
156+
for (int i = 0; i < 96; i++) EXPECT_EQ(log[i], i);
157+
158+
log.clear();
159+
{
160+
using v8::TaskPriority;
161+
node::TaskQueue<node::TaskQueueEntry> queue;
162+
const TaskPriority priorities[] = {TaskPriority::kUserVisible,
163+
TaskPriority::kBestEffort,
164+
TaskPriority::kUserBlocking,
165+
TaskPriority::kUserVisible,
166+
TaskPriority::kUserBlocking,
167+
TaskPriority::kBestEffort,
168+
TaskPriority::kUserVisible};
169+
int id = 0;
170+
for (TaskPriority priority : priorities) {
171+
queue.Lock().Push(std::make_unique<node::TaskQueueEntry>(
172+
std::make_unique<RecordingTask>(&log, id++), priority));
173+
}
174+
for (std::unique_ptr<node::TaskQueueEntry>& entry : queue.Lock().PopAll()) {
175+
entry->task->Run();
176+
}
177+
}
178+
const std::vector<int> expected = {2, 4, 0, 3, 6, 1, 5};
179+
EXPECT_EQ(log, expected);
180+
}

0 commit comments

Comments
Β (0)