src: implement v8::Platform::CallDelayedOnWorkerThread
This method is crucial for Runtime.evaluate protocol command with timeout flag. At least Chrome DevTools frontend uses this method for every execution in console. PR-URL: https://github.com/nodejs/node/pull/22383 Fixes: https://github.com/nodejs/node/issues/22157 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
f1d3f97c3b
commit
b1e26128f3
@ -2,6 +2,7 @@
|
|||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
|
|
||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
|
#include "debug_utils.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -29,7 +30,127 @@ static void PlatformWorkerThread(void* data) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
class WorkerThreadsTaskRunner::DelayedTaskScheduler {
|
||||||
|
public:
|
||||||
|
explicit DelayedTaskScheduler(TaskQueue<Task>* tasks)
|
||||||
|
: pending_worker_tasks_(tasks) {}
|
||||||
|
|
||||||
|
std::unique_ptr<uv_thread_t> Start() {
|
||||||
|
auto start_thread = [](void* data) {
|
||||||
|
static_cast<DelayedTaskScheduler*>(data)->Run();
|
||||||
|
};
|
||||||
|
std::unique_ptr<uv_thread_t> t { new uv_thread_t() };
|
||||||
|
uv_sem_init(&ready_, 0);
|
||||||
|
CHECK_EQ(0, uv_thread_create(t.get(), start_thread, this));
|
||||||
|
uv_sem_wait(&ready_);
|
||||||
|
uv_sem_destroy(&ready_);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostDelayedTask(std::unique_ptr<Task> task, double delay_in_seconds) {
|
||||||
|
tasks_.Push(std::unique_ptr<Task>(new ScheduleTask(this, std::move(task),
|
||||||
|
delay_in_seconds)));
|
||||||
|
uv_async_send(&flush_tasks_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
tasks_.Push(std::unique_ptr<Task>(new StopTask(this)));
|
||||||
|
uv_async_send(&flush_tasks_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Run() {
|
||||||
|
TRACE_EVENT_METADATA1("__metadata", "thread_name", "name",
|
||||||
|
"WorkerThreadsTaskRunner::DelayedTaskScheduler");
|
||||||
|
loop_.data = this;
|
||||||
|
CHECK_EQ(0, uv_loop_init(&loop_));
|
||||||
|
flush_tasks_.data = this;
|
||||||
|
CHECK_EQ(0, uv_async_init(&loop_, &flush_tasks_, FlushTasks));
|
||||||
|
uv_sem_post(&ready_);
|
||||||
|
|
||||||
|
uv_run(&loop_, UV_RUN_DEFAULT);
|
||||||
|
CheckedUvLoopClose(&loop_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FlushTasks(uv_async_t* flush_tasks) {
|
||||||
|
DelayedTaskScheduler* scheduler =
|
||||||
|
ContainerOf(&DelayedTaskScheduler::loop_, flush_tasks->loop);
|
||||||
|
while (std::unique_ptr<Task> task = scheduler->tasks_.Pop())
|
||||||
|
task->Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StopTask : public Task {
|
||||||
|
public:
|
||||||
|
explicit StopTask(DelayedTaskScheduler* scheduler): scheduler_(scheduler) {}
|
||||||
|
|
||||||
|
void Run() override {
|
||||||
|
std::vector<uv_timer_t*> timers;
|
||||||
|
for (uv_timer_t* timer : scheduler_->timers_)
|
||||||
|
timers.push_back(timer);
|
||||||
|
for (uv_timer_t* timer : timers)
|
||||||
|
scheduler_->TakeTimerTask(timer);
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(&scheduler_->flush_tasks_),
|
||||||
|
[](uv_handle_t* handle) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DelayedTaskScheduler* scheduler_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScheduleTask : public Task {
|
||||||
|
public:
|
||||||
|
ScheduleTask(DelayedTaskScheduler* scheduler,
|
||||||
|
std::unique_ptr<Task> task,
|
||||||
|
double delay_in_seconds)
|
||||||
|
: scheduler_(scheduler),
|
||||||
|
task_(std::move(task)),
|
||||||
|
delay_in_seconds_(delay_in_seconds) {}
|
||||||
|
|
||||||
|
void Run() override {
|
||||||
|
uint64_t delay_millis =
|
||||||
|
static_cast<uint64_t>(delay_in_seconds_ + 0.5) * 1000;
|
||||||
|
std::unique_ptr<uv_timer_t> timer(new uv_timer_t());
|
||||||
|
CHECK_EQ(0, uv_timer_init(&scheduler_->loop_, timer.get()));
|
||||||
|
timer->data = task_.release();
|
||||||
|
CHECK_EQ(0, uv_timer_start(timer.get(), RunTask, delay_millis, 0));
|
||||||
|
scheduler_->timers_.insert(timer.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DelayedTaskScheduler* scheduler_;
|
||||||
|
std::unique_ptr<Task> task_;
|
||||||
|
double delay_in_seconds_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void RunTask(uv_timer_t* timer) {
|
||||||
|
DelayedTaskScheduler* scheduler =
|
||||||
|
ContainerOf(&DelayedTaskScheduler::loop_, timer->loop);
|
||||||
|
scheduler->pending_worker_tasks_->Push(scheduler->TakeTimerTask(timer));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Task> TakeTimerTask(uv_timer_t* timer) {
|
||||||
|
std::unique_ptr<Task> task(static_cast<Task*>(timer->data));
|
||||||
|
uv_timer_stop(timer);
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(timer), [](uv_handle_t* handle) {
|
||||||
|
delete reinterpret_cast<uv_timer_t*>(handle);
|
||||||
|
});
|
||||||
|
timers_.erase(timer);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
uv_sem_t ready_;
|
||||||
|
TaskQueue<v8::Task>* pending_worker_tasks_;
|
||||||
|
|
||||||
|
TaskQueue<v8::Task> tasks_;
|
||||||
|
uv_loop_t loop_;
|
||||||
|
uv_async_t flush_tasks_;
|
||||||
|
std::unordered_set<uv_timer_t*> timers_;
|
||||||
|
};
|
||||||
|
|
||||||
WorkerThreadsTaskRunner::WorkerThreadsTaskRunner(int thread_pool_size) {
|
WorkerThreadsTaskRunner::WorkerThreadsTaskRunner(int thread_pool_size) {
|
||||||
|
delayed_task_scheduler_.reset(
|
||||||
|
new DelayedTaskScheduler(&pending_worker_tasks_));
|
||||||
|
threads_.push_back(delayed_task_scheduler_->Start());
|
||||||
for (int i = 0; i < thread_pool_size; i++) {
|
for (int i = 0; i < thread_pool_size; i++) {
|
||||||
std::unique_ptr<uv_thread_t> t { new uv_thread_t() };
|
std::unique_ptr<uv_thread_t> t { new uv_thread_t() };
|
||||||
if (uv_thread_create(t.get(), PlatformWorkerThread,
|
if (uv_thread_create(t.get(), PlatformWorkerThread,
|
||||||
@ -46,7 +167,7 @@ void WorkerThreadsTaskRunner::PostTask(std::unique_ptr<Task> task) {
|
|||||||
|
|
||||||
void WorkerThreadsTaskRunner::PostDelayedTask(std::unique_ptr<v8::Task> task,
|
void WorkerThreadsTaskRunner::PostDelayedTask(std::unique_ptr<v8::Task> task,
|
||||||
double delay_in_seconds) {
|
double delay_in_seconds) {
|
||||||
UNREACHABLE();
|
delayed_task_scheduler_->PostDelayedTask(std::move(task), delay_in_seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorkerThreadsTaskRunner::BlockingDrain() {
|
void WorkerThreadsTaskRunner::BlockingDrain() {
|
||||||
@ -55,6 +176,7 @@ void WorkerThreadsTaskRunner::BlockingDrain() {
|
|||||||
|
|
||||||
void WorkerThreadsTaskRunner::Shutdown() {
|
void WorkerThreadsTaskRunner::Shutdown() {
|
||||||
pending_worker_tasks_.Stop();
|
pending_worker_tasks_.Stop();
|
||||||
|
delayed_task_scheduler_->Stop();
|
||||||
for (size_t i = 0; i < threads_.size(); i++) {
|
for (size_t i = 0; i < threads_.size(); i++) {
|
||||||
CHECK_EQ(0, uv_thread_join(threads_[i].get()));
|
CHECK_EQ(0, uv_thread_join(threads_[i].get()));
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,10 @@ class WorkerThreadsTaskRunner {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
TaskQueue<v8::Task> pending_worker_tasks_;
|
TaskQueue<v8::Task> pending_worker_tasks_;
|
||||||
|
|
||||||
|
class DelayedTaskScheduler;
|
||||||
|
std::unique_ptr<DelayedTaskScheduler> delayed_task_scheduler_;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<uv_thread_t>> threads_;
|
std::vector<std::unique_ptr<uv_thread_t>> threads_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
// Flags: --expose-internals
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
common.skipIfInspectorDisabled();
|
||||||
|
|
||||||
|
(async function test() {
|
||||||
|
const { strictEqual } = require('assert');
|
||||||
|
const { Session } = require('inspector');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
|
||||||
|
const session = new Session();
|
||||||
|
session.connect();
|
||||||
|
session.post = promisify(session.post);
|
||||||
|
const result = await session.post('Runtime.evaluate', {
|
||||||
|
expression: 'for(;;);',
|
||||||
|
timeout: 0
|
||||||
|
}).catch((e) => e);
|
||||||
|
strictEqual(result.message, 'Execution was terminated');
|
||||||
|
session.disconnect();
|
||||||
|
})();
|
Loading…
x
Reference in New Issue
Block a user