Skip to content

Commit 0192c4f

Browse files
committed
src: improve thread safety of TaskQueue
1 parent 5077ea4 commit 0192c4f

File tree

2 files changed

+123
-102
lines changed

2 files changed

+123
-102
lines changed

src/node_platform.cc

Lines changed: 99 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ static void PlatformWorkerThread(void* data) {
4040
worker_data->platform_workers_ready->Signal(lock);
4141
}
4242

43-
while (std::unique_ptr<Task> task = pending_worker_tasks->BlockingPop()) {
43+
while (std::unique_ptr<Task> task =
44+
pending_worker_tasks->Lock().BlockingPop()) {
4445
task->Run();
45-
pending_worker_tasks->NotifyOfCompletion();
46+
pending_worker_tasks->Lock().NotifyOfCompletion();
4647
}
4748
}
4849

@@ -73,13 +74,15 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
7374
}
7475

7576
void PostDelayedTask(std::unique_ptr<Task> task, double delay_in_seconds) {
76-
tasks_.Push(std::make_unique<ScheduleTask>(this, std::move(task),
77-
delay_in_seconds));
77+
auto locked = tasks_.Lock();
78+
locked.Push(std::make_unique<ScheduleTask>(
79+
this, std::move(task), delay_in_seconds));
7880
uv_async_send(&flush_tasks_);
7981
}
8082

8183
void Stop() {
82-
tasks_.Push(std::make_unique<StopTask>(this));
84+
auto locked = tasks_.Lock();
85+
locked.Push(std::make_unique<StopTask>(this));
8386
uv_async_send(&flush_tasks_);
8487
}
8588

@@ -100,8 +103,13 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
100103
static void FlushTasks(uv_async_t* flush_tasks) {
101104
DelayedTaskScheduler* scheduler =
102105
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
103-
while (std::unique_ptr<Task> task = scheduler->tasks_.Pop())
106+
107+
std::queue<std::unique_ptr<Task>> tasks_to_run = scheduler->tasks_.Lock().PopAll();
108+
while (!tasks_to_run.empty()) {
109+
std::unique_ptr<Task> task = std::move(tasks_to_run.front());
110+
tasks_to_run.pop();
104111
task->Run();
112+
}
105113
}
106114

107115
class StopTask : public Task {
@@ -149,7 +157,8 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
149157
static void RunTask(uv_timer_t* timer) {
150158
DelayedTaskScheduler* scheduler =
151159
ContainerOf(&DelayedTaskScheduler::loop_, timer->loop);
152-
scheduler->pending_worker_tasks_->Push(scheduler->TakeTimerTask(timer));
160+
scheduler->pending_worker_tasks_->Lock().Push(
161+
scheduler->TakeTimerTask(timer));
153162
}
154163

155164
std::unique_ptr<Task> TakeTimerTask(uv_timer_t* timer) {
@@ -203,7 +212,7 @@ WorkerThreadsTaskRunner::WorkerThreadsTaskRunner(int thread_pool_size) {
203212
}
204213

205214
void WorkerThreadsTaskRunner::PostTask(std::unique_ptr<Task> task) {
206-
pending_worker_tasks_.Push(std::move(task));
215+
pending_worker_tasks_.Lock().Push(std::move(task));
207216
}
208217

209218
void WorkerThreadsTaskRunner::PostDelayedTask(std::unique_ptr<Task> task,
@@ -212,11 +221,11 @@ void WorkerThreadsTaskRunner::PostDelayedTask(std::unique_ptr<Task> task,
212221
}
213222

214223
void WorkerThreadsTaskRunner::BlockingDrain() {
215-
pending_worker_tasks_.BlockingDrain();
224+
pending_worker_tasks_.Lock().BlockingDrain();
216225
}
217226

218227
void WorkerThreadsTaskRunner::Shutdown() {
219-
pending_worker_tasks_.Stop();
228+
pending_worker_tasks_.Lock().Stop();
220229
delayed_task_scheduler_->Stop();
221230
for (size_t i = 0; i < threads_.size(); i++) {
222231
CHECK_EQ(0, uv_thread_join(threads_[i].get()));
@@ -253,29 +262,23 @@ void PerIsolatePlatformData::PostIdleTaskImpl(
253262

254263
void PerIsolatePlatformData::PostTaskImpl(std::unique_ptr<Task> task,
255264
const v8::SourceLocation& location) {
256-
if (flush_tasks_ == nullptr) {
257-
// V8 may post tasks during Isolate disposal. In that case, the only
258-
// sensible path forward is to discard the task.
259-
return;
260-
}
261-
foreground_tasks_.Push(std::move(task));
265+
auto locked = foreground_tasks_.Lock();
266+
if (flush_tasks_ == nullptr) return;
267+
locked.Push(std::move(task));
262268
uv_async_send(flush_tasks_);
263269
}
264270

265271
void PerIsolatePlatformData::PostDelayedTaskImpl(
266272
std::unique_ptr<Task> task,
267273
double delay_in_seconds,
268274
const v8::SourceLocation& location) {
269-
if (flush_tasks_ == nullptr) {
270-
// V8 may post tasks during Isolate disposal. In that case, the only
271-
// sensible path forward is to discard the task.
272-
return;
273-
}
275+
auto locked = foreground_delayed_tasks_.Lock();
276+
if (flush_tasks_ == nullptr) return;
274277
std::unique_ptr<DelayedTask> delayed(new DelayedTask());
275278
delayed->task = std::move(task);
276279
delayed->platform_data = shared_from_this();
277280
delayed->timeout = delay_in_seconds;
278-
foreground_delayed_tasks_.Push(std::move(delayed));
281+
locked.Push(std::move(delayed));
279282
uv_async_send(flush_tasks_);
280283
}
281284

@@ -301,32 +304,30 @@ void PerIsolatePlatformData::AddShutdownCallback(void (*callback)(void*),
301304
}
302305

303306
void PerIsolatePlatformData::Shutdown() {
304-
if (flush_tasks_ == nullptr)
305-
return;
307+
auto foreground_tasks_locked = foreground_tasks_.Lock();
308+
auto foreground_delayed_tasks_locked = foreground_delayed_tasks_.Lock();
306309

307-
// While there should be no V8 tasks in the queues at this point, it is
308-
// possible that Node.js-internal tasks from e.g. the inspector are still
309-
// lying around. We clear these queues and ignore the return value,
310-
// effectively deleting the tasks instead of running them.
311-
foreground_delayed_tasks_.PopAll();
312-
foreground_tasks_.PopAll();
310+
foreground_delayed_tasks_locked.PopAll();
311+
foreground_tasks_locked.PopAll();
313312
scheduled_delayed_tasks_.clear();
314313

315-
// Both destroying the scheduled_delayed_tasks_ lists and closing
316-
// flush_tasks_ handle add tasks to the event loop. We keep a count of all
317-
// non-closed handles, and when that reaches zero, we inform any shutdown
318-
// callbacks that the platform is done as far as this Isolate is concerned.
319-
self_reference_ = shared_from_this();
320-
uv_close(reinterpret_cast<uv_handle_t*>(flush_tasks_),
321-
[](uv_handle_t* handle) {
322-
std::unique_ptr<uv_async_t> flush_tasks {
323-
reinterpret_cast<uv_async_t*>(handle) };
324-
PerIsolatePlatformData* platform_data =
325-
static_cast<PerIsolatePlatformData*>(flush_tasks->data);
326-
platform_data->DecreaseHandleCount();
327-
platform_data->self_reference_.reset();
328-
});
329-
flush_tasks_ = nullptr;
314+
if (flush_tasks_ != nullptr) {
315+
// Both destroying the scheduled_delayed_tasks_ lists and closing
316+
// flush_tasks_ handle add tasks to the event loop. We keep a count of all
317+
// non-closed handles, and when that reaches zero, we inform any shutdown
318+
// callbacks that the platform is done as far as this Isolate is concerned.
319+
self_reference_ = shared_from_this();
320+
uv_close(reinterpret_cast<uv_handle_t*>(flush_tasks_),
321+
[](uv_handle_t* handle) {
322+
std::unique_ptr<uv_async_t> flush_tasks{
323+
reinterpret_cast<uv_async_t*>(handle)};
324+
PerIsolatePlatformData* platform_data =
325+
static_cast<PerIsolatePlatformData*>(flush_tasks->data);
326+
platform_data->DecreaseHandleCount();
327+
platform_data->self_reference_.reset();
328+
});
329+
flush_tasks_ = nullptr;
330+
}
330331
}
331332

332333
void PerIsolatePlatformData::DecreaseHandleCount() {
@@ -472,39 +473,46 @@ void NodePlatform::DrainTasks(Isolate* isolate) {
472473
bool PerIsolatePlatformData::FlushForegroundTasksInternal() {
473474
bool did_work = false;
474475

475-
while (std::unique_ptr<DelayedTask> delayed =
476-
foreground_delayed_tasks_.Pop()) {
476+
std::queue<std::unique_ptr<DelayedTask>> delayed_tasks_to_schedule = foreground_delayed_tasks_.Lock().PopAll();
477+
while (!delayed_tasks_to_schedule.empty()) {
478+
std::unique_ptr<DelayedTask> delayed = std::move(delayed_tasks_to_schedule.front());
479+
delayed_tasks_to_schedule.pop();
480+
477481
did_work = true;
478482
uint64_t delay_millis = llround(delayed->timeout * 1000);
479483

480484
delayed->timer.data = static_cast<void*>(delayed.get());
481485
uv_timer_init(loop_, &delayed->timer);
482-
// Timers may not guarantee queue ordering of events with the same delay if
483-
// the delay is non-zero. This should not be a problem in practice.
486+
// Timers may not guarantee queue ordering of events with the same delay
487+
// if the delay is non-zero. This should not be a problem in practice.
484488
uv_timer_start(&delayed->timer, RunForegroundTask, delay_millis, 0);
485489
uv_unref(reinterpret_cast<uv_handle_t*>(&delayed->timer));
486490
uv_handle_count_++;
487491

488-
scheduled_delayed_tasks_.emplace_back(delayed.release(),
489-
[](DelayedTask* delayed) {
490-
uv_close(reinterpret_cast<uv_handle_t*>(&delayed->timer),
491-
[](uv_handle_t* handle) {
492-
std::unique_ptr<DelayedTask> task {
493-
static_cast<DelayedTask*>(handle->data) };
494-
task->platform_data->DecreaseHandleCount();
495-
});
496-
});
492+
scheduled_delayed_tasks_.emplace_back(
493+
delayed.release(), [](DelayedTask* delayed) {
494+
uv_close(reinterpret_cast<uv_handle_t*>(&delayed->timer),
495+
[](uv_handle_t* handle) {
496+
std::unique_ptr<DelayedTask> task{
497+
static_cast<DelayedTask*>(handle->data)};
498+
task->platform_data->DecreaseHandleCount();
499+
});
500+
});
501+
}
502+
503+
std::queue<std::unique_ptr<Task>> tasks;
504+
{
505+
auto locked = foreground_tasks_.Lock();
506+
tasks = locked.PopAll();
497507
}
498-
// Move all foreground tasks into a separate queue and flush that queue.
499-
// This way tasks that are posted while flushing the queue will be run on the
500-
// next call of FlushForegroundTasksInternal.
501-
std::queue<std::unique_ptr<Task>> tasks = foreground_tasks_.PopAll();
508+
502509
while (!tasks.empty()) {
503510
std::unique_ptr<Task> task = std::move(tasks.front());
504511
tasks.pop();
505512
did_work = true;
506513
RunForegroundTask(std::move(task));
507514
}
515+
508516
return did_work;
509517
}
510518

@@ -594,66 +602,63 @@ TaskQueue<T>::TaskQueue()
594602
outstanding_tasks_(0), stopped_(false), task_queue_() { }
595603

596604
template <class T>
597-
void TaskQueue<T>::Push(std::unique_ptr<T> task) {
598-
Mutex::ScopedLock scoped_lock(lock_);
599-
outstanding_tasks_++;
600-
task_queue_.push(std::move(task));
601-
tasks_available_.Signal(scoped_lock);
605+
TaskQueue<T>::Locked::Locked(TaskQueue* queue)
606+
: queue_(queue), lock_(queue->lock_) {}
607+
608+
template <class T>
609+
void TaskQueue<T>::Locked::Push(std::unique_ptr<T> task) {
610+
queue_->outstanding_tasks_++;
611+
queue_->task_queue_.push(std::move(task));
612+
queue_->tasks_available_.Signal(lock_);
602613
}
603614

604615
template <class T>
605-
std::unique_ptr<T> TaskQueue<T>::Pop() {
606-
Mutex::ScopedLock scoped_lock(lock_);
607-
if (task_queue_.empty()) {
616+
std::unique_ptr<T> TaskQueue<T>::Locked::Pop() {
617+
if (queue_->task_queue_.empty()) {
608618
return std::unique_ptr<T>(nullptr);
609619
}
610-
std::unique_ptr<T> result = std::move(task_queue_.front());
611-
task_queue_.pop();
620+
std::unique_ptr<T> result = std::move(queue_->task_queue_.front());
621+
queue_->task_queue_.pop();
612622
return result;
613623
}
614624

615625
template <class T>
616-
std::unique_ptr<T> TaskQueue<T>::BlockingPop() {
617-
Mutex::ScopedLock scoped_lock(lock_);
618-
while (task_queue_.empty() && !stopped_) {
619-
tasks_available_.Wait(scoped_lock);
626+
std::unique_ptr<T> TaskQueue<T>::Locked::BlockingPop() {
627+
while (queue_->task_queue_.empty() && !queue_->stopped_) {
628+
queue_->tasks_available_.Wait(lock_);
620629
}
621-
if (stopped_) {
630+
if (queue_->stopped_) {
622631
return std::unique_ptr<T>(nullptr);
623632
}
624-
std::unique_ptr<T> result = std::move(task_queue_.front());
625-
task_queue_.pop();
633+
std::unique_ptr<T> result = std::move(queue_->task_queue_.front());
634+
queue_->task_queue_.pop();
626635
return result;
627636
}
628637

629638
template <class T>
630-
void TaskQueue<T>::NotifyOfCompletion() {
631-
Mutex::ScopedLock scoped_lock(lock_);
632-
if (--outstanding_tasks_ == 0) {
633-
tasks_drained_.Broadcast(scoped_lock);
639+
void TaskQueue<T>::Locked::NotifyOfCompletion() {
640+
if (--queue_->outstanding_tasks_ == 0) {
641+
queue_->tasks_drained_.Broadcast(lock_);
634642
}
635643
}
636644

637645
template <class T>
638-
void TaskQueue<T>::BlockingDrain() {
639-
Mutex::ScopedLock scoped_lock(lock_);
640-
while (outstanding_tasks_ > 0) {
641-
tasks_drained_.Wait(scoped_lock);
646+
void TaskQueue<T>::Locked::BlockingDrain() {
647+
while (queue_->outstanding_tasks_ > 0) {
648+
queue_->tasks_drained_.Wait(lock_);
642649
}
643650
}
644651

645652
template <class T>
646-
void TaskQueue<T>::Stop() {
647-
Mutex::ScopedLock scoped_lock(lock_);
648-
stopped_ = true;
649-
tasks_available_.Broadcast(scoped_lock);
653+
void TaskQueue<T>::Locked::Stop() {
654+
queue_->stopped_ = true;
655+
queue_->tasks_available_.Broadcast(lock_);
650656
}
651657

652658
template <class T>
653-
std::queue<std::unique_ptr<T>> TaskQueue<T>::PopAll() {
654-
Mutex::ScopedLock scoped_lock(lock_);
659+
std::queue<std::unique_ptr<T>> TaskQueue<T>::Locked::PopAll() {
655660
std::queue<std::unique_ptr<T>> result;
656-
result.swap(task_queue_);
661+
result.swap(queue_->task_queue_);
657662
return result;
658663
}
659664

src/node_platform.h

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,28 @@ class PerIsolatePlatformData;
2222
template <class T>
2323
class TaskQueue {
2424
public:
25+
class Locked {
26+
public:
27+
void Push(std::unique_ptr<T> task);
28+
std::unique_ptr<T> Pop();
29+
std::unique_ptr<T> BlockingPop();
30+
void NotifyOfCompletion();
31+
void BlockingDrain();
32+
void Stop();
33+
std::queue<std::unique_ptr<T>> PopAll();
34+
35+
private:
36+
friend class TaskQueue;
37+
explicit Locked(TaskQueue* queue);
38+
39+
TaskQueue* queue_;
40+
Mutex::ScopedLock lock_;
41+
};
42+
2543
TaskQueue();
2644
~TaskQueue() = default;
2745

28-
void Push(std::unique_ptr<T> task);
29-
std::unique_ptr<T> Pop();
30-
std::unique_ptr<T> BlockingPop();
31-
std::queue<std::unique_ptr<T>> PopAll();
32-
void NotifyOfCompletion();
33-
void BlockingDrain();
34-
void Stop();
46+
Locked Lock() { return Locked(this); }
3547

3648
private:
3749
Mutex lock_;
@@ -98,6 +110,8 @@ class PerIsolatePlatformData
98110
void RunForegroundTask(std::unique_ptr<v8::Task> task);
99111
static void RunForegroundTask(uv_timer_t* timer);
100112

113+
uv_async_t* flush_tasks_ = nullptr;
114+
101115
struct ShutdownCallback {
102116
void (*cb)(void*);
103117
void* data;
@@ -110,7 +124,9 @@ class PerIsolatePlatformData
110124

111125
v8::Isolate* const isolate_;
112126
uv_loop_t* const loop_;
113-
uv_async_t* flush_tasks_ = nullptr;
127+
128+
// When acquiring locks for both task queues, lock foreground_tasks_
129+
// first then foreground_delayed_tasks_ to avoid deadlocks.
114130
TaskQueue<v8::Task> foreground_tasks_;
115131
TaskQueue<DelayedTask> foreground_delayed_tasks_;
116132

0 commit comments

Comments
 (0)