Skip to content

Commit 80d2173

Browse files
src: avoid use-after-close of delayed task scheduler async handle
Fixes: #64322 Signed-off-by: StefanStojanovic <stefan.stojanovic@janeasystems.com> Co-authored-by: Kirill Saied <sayed.kirill@gmail.com>
1 parent 4140d4c commit 80d2173

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

src/node_platform.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
116116
std::unique_ptr<Task> task,
117117
double delay_in_seconds) {
118118
auto locked = tasks_.Lock();
119+
// Once Stop() has begun tearing down the scheduler, the uv_async_send()
120+
// below would hit a closing handle and abort. Drop tasks that arrive
121+
// during shutdown.
122+
if (stopped_) return;
119123

120124
auto entry = std::make_unique<TaskQueueEntry>(std::move(task), priority);
121125
auto delayed = std::make_unique<ScheduleTask>(
@@ -132,6 +136,9 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
132136

133137
void Stop() {
134138
auto locked = tasks_.Lock();
139+
// Flip stopped_ so no new tasks get scheduled while we shut down.
140+
if (stopped_) return;
141+
stopped_ = true;
135142
locked.Push(std::make_unique<StopTask>(this));
136143
uv_async_send(&flush_tasks_);
137144
}
@@ -238,6 +245,7 @@ class WorkerThreadsTaskRunner::DelayedTaskScheduler {
238245
// Locally scheduled tasks to be poped into the worker task runner queue.
239246
// It is flushed whenever the next closest timer expires.
240247
TaskQueue<Task> tasks_;
248+
bool stopped_ = false;
241249
uv_loop_t loop_;
242250
uv_async_t flush_tasks_;
243251
std::unordered_set<uv_timer_t*> timers_;

test/cctest/test_platform.cc

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,40 @@ class RepostingTask : public v8::Task {
3838
node::NodePlatform* platform_;
3939
};
4040

41+
class NoopTask : public v8::Task {
42+
public:
43+
void Run() final {}
44+
};
45+
46+
class PostDelayedTaskAfterShutdownStartsTask : public v8::Task {
47+
public:
48+
PostDelayedTaskAfterShutdownStartsTask(node::NodePlatform* platform,
49+
uv_sem_t* task_started,
50+
uv_sem_t* post_task)
51+
: platform_(platform),
52+
task_started_(task_started),
53+
post_task_(post_task) {}
54+
55+
void Run() final {
56+
uv_sem_post(task_started_);
57+
uv_sem_wait(post_task_);
58+
uv_sleep(50);
59+
platform_->PostDelayedTaskOnWorkerThreadImpl(v8::TaskPriority::kUserVisible,
60+
std::make_unique<NoopTask>(),
61+
1.0,
62+
v8::SourceLocation());
63+
}
64+
65+
private:
66+
node::NodePlatform* platform_;
67+
uv_sem_t* task_started_;
68+
uv_sem_t* post_task_;
69+
};
70+
71+
static void ShutdownPlatform(void* arg) {
72+
static_cast<node::NodePlatform*>(arg)->Shutdown();
73+
}
74+
4175
class PlatformTest : public EnvironmentTestFixture {};
4276

4377
TEST_F(PlatformTest, SkipNewTasksInFlushForegroundTasks) {
@@ -60,6 +94,40 @@ TEST_F(PlatformTest, SkipNewTasksInFlushForegroundTasks) {
6094
EXPECT_FALSE(platform->FlushForegroundTasks(isolate_));
6195
}
6296

97+
// Regression test: a worker thread posting a delayed task concurrently with
98+
// platform shutdown must not call uv_async_send() on the scheduler's
99+
// flush_tasks_ handle after Stop() has begun closing it. The two semaphores
100+
// and the sleeps pin the ordering so the delayed task is posted only after
101+
// Shutdown() has run, which is the window that used to abort with
102+
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". The test passes
103+
// when shutdown completes without crashing.
104+
TEST_F(NodeZeroIsolateTestFixture, DelayedWorkerTaskDuringPlatformShutdown) {
105+
node::NodePlatform test_platform(1, tracing_agent->GetTracingController());
106+
107+
uv_sem_t task_started;
108+
uv_sem_t post_task;
109+
ASSERT_EQ(0, uv_sem_init(&task_started, 0));
110+
ASSERT_EQ(0, uv_sem_init(&post_task, 0));
111+
112+
test_platform.PostTaskOnWorkerThreadImpl(
113+
v8::TaskPriority::kUserBlocking,
114+
std::make_unique<PostDelayedTaskAfterShutdownStartsTask>(
115+
&test_platform, &task_started, &post_task),
116+
v8::SourceLocation());
117+
118+
uv_sem_wait(&task_started);
119+
120+
uv_thread_t shutdown_thread;
121+
ASSERT_EQ(
122+
0, uv_thread_create(&shutdown_thread, ShutdownPlatform, &test_platform));
123+
uv_sleep(50);
124+
uv_sem_post(&post_task);
125+
ASSERT_EQ(0, uv_thread_join(&shutdown_thread));
126+
127+
uv_sem_destroy(&post_task);
128+
uv_sem_destroy(&task_started);
129+
}
130+
63131
// Tests the registration of an abstract `IsolatePlatformDelegate` instance as
64132
// opposed to the more common `uv_loop_s*` version of `RegisterIsolate`.
65133
TEST_F(NodeZeroIsolateTestFixture, IsolatePlatformDelegateTest) {

0 commit comments

Comments
 (0)