 Chromium Code Reviews
 Chromium Code Reviews Issue 2615603002:
  Implement async AbortAll for the compiler dispatcher  (Closed)
    
  
    Issue 2615603002:
  Implement async AbortAll for the compiler dispatcher  (Closed) 
  | Index: test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc | 
| diff --git a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc | 
| index bef225efaf299b0b2afc2e27399b684b7e9a49c3..28efacf8769db38753626d13eea23d2a477d6f28 100644 | 
| --- a/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc | 
| +++ b/test/unittests/compiler-dispatcher/compiler-dispatcher-unittest.cc | 
| @@ -70,9 +70,11 @@ namespace { | 
| class MockPlatform : public v8::Platform { | 
| public: | 
| - MockPlatform() : idle_task_(nullptr), time_(0.0), time_step_(0.0), sem_(0) {} | 
| + MockPlatform() : time_(0.0), time_step_(0.0), idle_task_(nullptr), sem_(0) {} | 
| ~MockPlatform() override { | 
| - EXPECT_TRUE(tasks_.empty()); | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + EXPECT_TRUE(foreground_tasks_.empty()); | 
| + EXPECT_TRUE(background_tasks_.empty()); | 
| EXPECT_TRUE(idle_task_ == nullptr); | 
| } | 
| @@ -80,11 +82,13 @@ class MockPlatform : public v8::Platform { | 
| void CallOnBackgroundThread(Task* task, | 
| ExpectedRuntime expected_runtime) override { | 
| - tasks_.push_back(task); | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + background_tasks_.push_back(task); | 
| } | 
| void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override { | 
| - UNREACHABLE(); | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + foreground_tasks_.push_back(task); | 
| } | 
| void CallDelayedOnForegroundThread(v8::Isolate* isolate, Task* task, | 
| @@ -94,6 +98,7 @@ class MockPlatform : public v8::Platform { | 
| void CallIdleOnForegroundThread(v8::Isolate* isolate, | 
| IdleTask* task) override { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| ASSERT_TRUE(idle_task_ == nullptr); | 
| idle_task_ = task; | 
| } | 
| @@ -106,21 +111,39 @@ class MockPlatform : public v8::Platform { | 
| } | 
| void RunIdleTask(double deadline_in_seconds, double time_step) { | 
| - ASSERT_TRUE(idle_task_ != nullptr); | 
| time_step_ = time_step; | 
| - IdleTask* task = idle_task_; | 
| - idle_task_ = nullptr; | 
| + IdleTask* task; | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + task = idle_task_; | 
| + ASSERT_TRUE(idle_task_ != nullptr); | 
| + idle_task_ = nullptr; | 
| + } | 
| task->Run(deadline_in_seconds); | 
| delete task; | 
| } | 
| - bool IdleTaskPending() const { return idle_task_; } | 
| + bool IdleTaskPending() { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + return idle_task_; | 
| + } | 
| + | 
| + bool BackgroundTasksPending() { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + return !background_tasks_.empty(); | 
| + } | 
| - bool BackgroundTasksPending() const { return !tasks_.empty(); } | 
| + bool ForegroundTasksPending() { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + return !foreground_tasks_.empty(); | 
| + } | 
| void RunBackgroundTasksAndBlock(Platform* platform) { | 
| std::vector<Task*> tasks; | 
| - tasks.swap(tasks_); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + tasks.swap(background_tasks_); | 
| + } | 
| platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true), | 
| kShortRunningTask); | 
| sem_.Wait(); | 
| @@ -128,20 +151,50 @@ class MockPlatform : public v8::Platform { | 
| void RunBackgroundTasks(Platform* platform) { | 
| std::vector<Task*> tasks; | 
| - tasks.swap(tasks_); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + tasks.swap(background_tasks_); | 
| + } | 
| platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false), | 
| kShortRunningTask); | 
| } | 
| + void RunForegroundTasks() { | 
| + std::vector<Task*> tasks; | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + tasks.swap(foreground_tasks_); | 
| + } | 
| + for (auto& task : tasks) { | 
| + task->Run(); | 
| + delete task; | 
| + } | 
| + } | 
| + | 
| void ClearBackgroundTasks() { | 
| std::vector<Task*> tasks; | 
| - tasks.swap(tasks_); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + tasks.swap(background_tasks_); | 
| + } | 
| + for (auto& task : tasks) { | 
| + delete task; | 
| + } | 
| + } | 
| + | 
| + void ClearForegroundTasks() { | 
| + std::vector<Task*> tasks; | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| + tasks.swap(foreground_tasks_); | 
| + } | 
| for (auto& task : tasks) { | 
| delete task; | 
| } | 
| } | 
| void ClearIdleTask() { | 
| + base::LockGuard<base::Mutex> lock(&mutex_); | 
| ASSERT_TRUE(idle_task_ != nullptr); | 
| delete idle_task_; | 
| idle_task_ = nullptr; | 
| @@ -171,11 +224,16 @@ class MockPlatform : public v8::Platform { | 
| DISALLOW_COPY_AND_ASSIGN(TaskWrapper); | 
| }; | 
| - IdleTask* idle_task_; | 
| double time_; | 
| double time_step_; | 
| - std::vector<Task*> tasks_; | 
| + // Protects all *_tasks_. | 
| + base::Mutex mutex_; | 
| + | 
| + IdleTask* idle_task_; | 
| + std::vector<Task*> background_tasks_; | 
| + std::vector<Task*> foreground_tasks_; | 
| + | 
| base::Semaphore sem_; | 
| DISALLOW_COPY_AND_ASSIGN(MockPlatform); | 
| @@ -459,5 +517,202 @@ TEST_F(CompilerDispatcherTest, FinishNowException) { | 
| platform.ClearIdleTask(); | 
| } | 
| +TEST_F(IgnitionCompilerDispatcherTest, AsyncAbortAllPendingBackgroundTask) { | 
| + MockPlatform platform; | 
| + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); | 
| + | 
| + const char script[] = | 
| + "function g() { var y = 1; function f11(x) { return x * y }; return f11; " | 
| + "} g();"; | 
| + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); | 
| + Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); | 
| + | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(dispatcher.Enqueue(shared)); | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + | 
| + ASSERT_EQ(dispatcher.jobs_.size(), 1u); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kInitial); | 
| + | 
| + // Make compiling super expensive, and advance job as much as possible on the | 
| + // foreground thread. | 
| + dispatcher.tracer_->RecordCompile(50000.0, 1); | 
| + platform.RunIdleTask(10.0, 0.0); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kReadyToCompile); | 
| + | 
| + ASSERT_TRUE(dispatcher.IsEnqueued(shared)); | 
| + ASSERT_FALSE(shared->is_compiled()); | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(platform.BackgroundTasksPending()); | 
| + | 
| + // The background task hasn't yet started, so we can just cancel it. | 
| + dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock); | 
| + ASSERT_FALSE(platform.ForegroundTasksPending()); | 
| + | 
| + ASSERT_FALSE(dispatcher.IsEnqueued(shared)); | 
| + ASSERT_FALSE(shared->is_compiled()); | 
| + | 
| + platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform()); | 
| + | 
| + if (platform.IdleTaskPending()) platform.ClearIdleTask(); | 
| + ASSERT_FALSE(platform.BackgroundTasksPending()); | 
| + ASSERT_FALSE(platform.ForegroundTasksPending()); | 
| +} | 
| + | 
| +TEST_F(IgnitionCompilerDispatcherTest, AsyncAbortAllRunningBackgroundTask) { | 
| + MockPlatform platform; | 
| + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); | 
| + | 
| + const char script1[] = | 
| + "function g() { var y = 1; function f11(x) { return x * y }; return f11; " | 
| + "} g();"; | 
| + Handle<JSFunction> f1 = Handle<JSFunction>::cast(RunJS(isolate(), script1)); | 
| + Handle<SharedFunctionInfo> shared1(f1->shared(), i_isolate()); | 
| + | 
| + const char script2[] = | 
| + "function g() { var y = 1; function f12(x) { return x * y }; return f12; " | 
| + "} g();"; | 
| + Handle<JSFunction> f2 = Handle<JSFunction>::cast(RunJS(isolate(), script2)); | 
| + Handle<SharedFunctionInfo> shared2(f2->shared(), i_isolate()); | 
| + | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(dispatcher.Enqueue(shared1)); | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + | 
| + ASSERT_EQ(dispatcher.jobs_.size(), 1u); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kInitial); | 
| + | 
| + // Make compiling super expensive, and advance job as much as possible on the | 
| + // foreground thread. | 
| + dispatcher.tracer_->RecordCompile(50000.0, 1); | 
| + platform.RunIdleTask(10.0, 0.0); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kReadyToCompile); | 
| + | 
| + ASSERT_TRUE(dispatcher.IsEnqueued(shared1)); | 
| + ASSERT_FALSE(shared1->is_compiled()); | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(platform.BackgroundTasksPending()); | 
| + | 
| + // Kick off background tasks, racing with the AbortAll() call. | 
| 
marja
2017/01/04 12:54:43
Instead of making your tests racy, you could use c
 
jochen (gone - plz use gerrit)
2017/01/04 13:18:31
ah, the comment is just wrong, it's no longer a ra
 | 
| + dispatcher.block_for_testing_.SetValue(true); | 
| + platform.RunBackgroundTasks(V8::GetCurrentPlatform()); | 
| + | 
| + // Busy loop until the background task started running. | 
| + while (dispatcher.block_for_testing_.Value()) { | 
| 
marja
2017/01/04 12:54:43
Here too you can wait on a condition variable.
 
jochen (gone - plz use gerrit)
2017/01/04 13:18:31
then I'd need even more members just for testing..
 
marja
2017/01/04 13:28:36
Ah, right, because you can't mock the task and jus
 | 
| + } | 
| + dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock); | 
| + ASSERT_TRUE(platform.ForegroundTasksPending()); | 
| + | 
| + // We can't schedule new tasks while we're aborting. | 
| + ASSERT_FALSE(dispatcher.Enqueue(shared2)); | 
| + | 
| + // Run the first AbortTask. Since the background job is still pending, it | 
| + // can't do anything. | 
| + platform.RunForegroundTasks(); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); | 
| + ASSERT_TRUE(dispatcher.abort_); | 
| + } | 
| + | 
| + // Release background task. | 
| + dispatcher.semaphore_for_testing_.Signal(); | 
| + | 
| + // Busy loop until the background task scheduled another AbortTask task. | 
| 
marja
2017/01/04 12:54:43
Ditto
 | 
| + while (!platform.ForegroundTasksPending()) { | 
| + } | 
| + | 
| + platform.RunForegroundTasks(); | 
| + ASSERT_TRUE(dispatcher.jobs_.empty()); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); | 
| + ASSERT_FALSE(dispatcher.abort_); | 
| + } | 
| + | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + platform.RunIdleTask(5.0, 1.0); | 
| + ASSERT_FALSE(platform.BackgroundTasksPending()); | 
| + ASSERT_FALSE(platform.ForegroundTasksPending()); | 
| + | 
| + // Now it's possible to enqueue new functions again. | 
| + ASSERT_TRUE(dispatcher.Enqueue(shared2)); | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + ASSERT_FALSE(platform.BackgroundTasksPending()); | 
| + ASSERT_FALSE(platform.ForegroundTasksPending()); | 
| + platform.ClearIdleTask(); | 
| +} | 
| + | 
| +TEST_F(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll) { | 
| + MockPlatform platform; | 
| + CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size); | 
| + | 
| + const char script[] = | 
| + "function g() { var y = 1; function f13(x) { return x * y }; return f13; " | 
| + "} g();"; | 
| + Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script)); | 
| + Handle<SharedFunctionInfo> shared(f->shared(), i_isolate()); | 
| + | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(dispatcher.Enqueue(shared)); | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + | 
| + ASSERT_EQ(dispatcher.jobs_.size(), 1u); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kInitial); | 
| + | 
| + // Make compiling super expensive, and advance job as much as possible on the | 
| + // foreground thread. | 
| + dispatcher.tracer_->RecordCompile(50000.0, 1); | 
| + platform.RunIdleTask(10.0, 0.0); | 
| + ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() == | 
| + CompileJobStatus::kReadyToCompile); | 
| + | 
| + ASSERT_TRUE(dispatcher.IsEnqueued(shared)); | 
| + ASSERT_FALSE(shared->is_compiled()); | 
| + ASSERT_FALSE(platform.IdleTaskPending()); | 
| + ASSERT_TRUE(platform.BackgroundTasksPending()); | 
| + | 
| + // Kick off background tasks, racing with the AbortAll() call. | 
| 
marja
2017/01/04 12:54:43
Ditto
 | 
| + dispatcher.block_for_testing_.SetValue(true); | 
| + platform.RunBackgroundTasks(V8::GetCurrentPlatform()); | 
| + | 
| + // Busy loop until the background task started running. | 
| + while (dispatcher.block_for_testing_.Value()) { | 
| + } | 
| + dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock); | 
| + ASSERT_TRUE(platform.ForegroundTasksPending()); | 
| + | 
| + // Run the first AbortTask. Since the background job is still pending, it | 
| + // can't do anything. | 
| + platform.RunForegroundTasks(); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); | 
| + ASSERT_TRUE(dispatcher.abort_); | 
| + } | 
| + | 
| + // While the background thread holds on to a job, it is still enqueud. | 
| + ASSERT_TRUE(dispatcher.IsEnqueued(shared)); | 
| + | 
| + // Release background task. | 
| + dispatcher.semaphore_for_testing_.Signal(); | 
| + | 
| + // Force the compilation to finish, even while aborting. | 
| + ASSERT_TRUE(dispatcher.FinishNow(shared)); | 
| + ASSERT_TRUE(dispatcher.jobs_.empty()); | 
| + { | 
| + base::LockGuard<base::Mutex> lock(&dispatcher.mutex_); | 
| + ASSERT_FALSE(dispatcher.abort_); | 
| + } | 
| + | 
| + ASSERT_TRUE(platform.ForegroundTasksPending()); | 
| + ASSERT_TRUE(platform.IdleTaskPending()); | 
| + ASSERT_FALSE(platform.BackgroundTasksPending()); | 
| + platform.ClearForegroundTasks(); | 
| + platform.ClearIdleTask(); | 
| +} | 
| + | 
| } // namespace internal | 
| } // namespace v8 |