Commit e426fdd5 authored by jochen's avatar jochen Committed by Commit bot

Implement async AbortAll for the compiler dispatcher

BUG=v8:5215
R=marja@chromium.org,vogelheim@chromium.org

Review-Url: https://codereview.chromium.org/2615603002
Cr-Commit-Position: refs/heads/master@{#42068}
parent 20defd29
......@@ -93,6 +93,24 @@ void CancelableTaskManager::CancelAndWait() {
}
}
CancelableTaskManager::TryAbortResult CancelableTaskManager::TryAbortAll() {
// Clean up all cancelable fore- and background tasks. Tasks are canceled on
// the way if possible, i.e., if they have not started yet.
base::LockGuard<base::Mutex> guard(&mutex_);
if (cancelable_tasks_.empty()) return kTaskRemoved;
for (auto it = cancelable_tasks_.begin(); it != cancelable_tasks_.end();) {
if (it->second->Cancel()) {
it = cancelable_tasks_.erase(it);
} else {
++it;
}
}
return cancelable_tasks_.empty() ? kTaskAborted : kTaskRunning;
}
CancelableTask::CancelableTask(Isolate* isolate)
: CancelableTask(isolate, isolate->cancelable_task_manager()) {}
......
......@@ -45,6 +45,17 @@ class V8_EXPORT_PRIVATE CancelableTaskManager {
// already running. This disallows subsequent Register calls.
void CancelAndWait();
// Tries to cancel all remaining registered tasks. The return value indicates
// whether
//
// 1) No tasks were registered (kTaskRemoved), or
//
// 2) There is at least one remaining task that couldn't be cancelled
// (kTaskRunning), or
//
// 3) All registered tasks were cancelled (kTaskAborted).
TryAbortResult TryAbortAll();
private:
// Only called by {Cancelable} destructor. The task is done with executing,
// but needs to be removed.
......
......@@ -52,13 +52,11 @@ bool DoNextStepOnMainThread(Isolate* isolate, CompilerDispatcherJob* job,
break;
}
if (job->status() == CompileJobStatus::kFailed) {
DCHECK(isolate->has_pending_exception());
if (exception_handling == ExceptionHandling::kSwallow) {
isolate->clear_pending_exception();
}
} else {
DCHECK(!isolate->has_pending_exception());
DCHECK_EQ(job->status() == CompileJobStatus::kFailed,
isolate->has_pending_exception());
if (job->status() == CompileJobStatus::kFailed &&
exception_handling == ExceptionHandling::kSwallow) {
isolate->clear_pending_exception();
}
return job->status() != CompileJobStatus::kFailed;
}
......@@ -97,6 +95,32 @@ const double kMaxIdleTimeToExpectInMs = 40;
} // namespace
class CompilerDispatcher::AbortTask : public CancelableTask {
public:
AbortTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~AbortTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(AbortTask);
};
CompilerDispatcher::AbortTask::AbortTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}
CompilerDispatcher::AbortTask::~AbortTask() {}
void CompilerDispatcher::AbortTask::RunInternal() {
dispatcher_->AbortInactiveJobs();
}
class CompilerDispatcher::BackgroundTask : public CancelableTask {
public:
BackgroundTask(Isolate* isolate, CancelableTaskManager* task_manager,
......@@ -156,9 +180,12 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
max_stack_size_(max_stack_size),
tracer_(new CompilerDispatcherTracer(isolate_)),
task_manager_(new CancelableTaskManager()),
abort_(false),
idle_task_scheduled_(false),
num_scheduled_background_tasks_(0),
main_thread_blocking_on_job_(nullptr) {}
main_thread_blocking_on_job_(nullptr),
block_for_testing_(false),
semaphore_for_testing_(0) {}
CompilerDispatcher::~CompilerDispatcher() {
// To avoid crashing in unit tests due to unfished jobs.
......@@ -169,6 +196,11 @@ CompilerDispatcher::~CompilerDispatcher() {
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
if (!IsEnabled()) return false;
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (abort_) return false;
}
// We only handle functions (no eval / top-level code / wasm) that are
// attached to a script.
if (!function->script()->IsScript() || !function->is_function() ||
......@@ -223,17 +255,68 @@ bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
bool result = job->second->status() != CompileJobStatus::kFailed;
job->second->ResetOnMainThread();
jobs_.erase(job);
if (jobs_.empty()) {
base::LockGuard<base::Mutex> lock(&mutex_);
abort_ = false;
}
return result;
}
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
// TODO(jochen): Implement support for non-blocking abort.
DCHECK(blocking == BlockingBehavior::kBlock);
for (auto& kv : jobs_) {
WaitForJobIfRunningOnBackground(kv.second.get());
kv.second->ResetOnMainThread();
bool background_tasks_running =
task_manager_->TryAbortAll() == CancelableTaskManager::kTaskRunning;
if (!background_tasks_running || blocking == BlockingBehavior::kBlock) {
for (auto& it : jobs_) {
WaitForJobIfRunningOnBackground(it.second.get());
it.second->ResetOnMainThread();
}
jobs_.clear();
{
base::LockGuard<base::Mutex> lock(&mutex_);
DCHECK(pending_background_jobs_.empty());
DCHECK(running_background_jobs_.empty());
abort_ = false;
}
return;
}
{
base::LockGuard<base::Mutex> lock(&mutex_);
abort_ = true;
pending_background_jobs_.clear();
}
AbortInactiveJobs();
// All running background jobs might already have scheduled idle tasks instead
// of abort tasks. Schedule a single abort task here to make sure they get
// processed as soon as possible (and not first when we have idle time).
ScheduleAbortTask();
}
void CompilerDispatcher::AbortInactiveJobs() {
{
base::LockGuard<base::Mutex> lock(&mutex_);
// Since we schedule two abort tasks per async abort, we might end up
// here with nothing left to do.
if (!abort_) return;
}
for (auto it = jobs_.begin(); it != jobs_.end();) {
auto job = it;
++it;
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (running_background_jobs_.find(job->second.get()) !=
running_background_jobs_.end()) {
continue;
}
}
job->second->ResetOnMainThread();
jobs_.erase(job);
}
if (jobs_.empty()) {
base::LockGuard<base::Mutex> lock(&mutex_);
abort_ = false;
}
jobs_.clear();
}
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
......@@ -265,6 +348,12 @@ void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
ScheduleIdleTaskFromAnyThread();
}
void CompilerDispatcher::ScheduleAbortTask() {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
platform_->CallOnForegroundThread(
v8_isolate, new AbortTask(isolate_, task_manager_.get(), this));
}
void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
CompilerDispatcherJob* job) {
if (!CanRunOnAnyThread(job)) return;
......@@ -304,6 +393,12 @@ void CompilerDispatcher::DoBackgroundWork() {
}
}
if (job == nullptr) return;
if (V8_UNLIKELY(block_for_testing_.Value())) {
block_for_testing_.SetValue(false);
semaphore_for_testing_.Wait();
}
DoNextStepOnBackgroundThread(job);
ScheduleMoreBackgroundTasksIfNeeded();
......@@ -315,6 +410,13 @@ void CompilerDispatcher::DoBackgroundWork() {
base::LockGuard<base::Mutex> lock(&mutex_);
running_background_jobs_.erase(job);
if (running_background_jobs_.empty() && abort_) {
// This is the last background job that finished. The abort task
// scheduled by AbortAll might already have ran, so schedule another
// one to be on the safe side.
ScheduleAbortTask();
}
if (main_thread_blocking_on_job_ == job) {
main_thread_blocking_on_job_ = nullptr;
main_thread_blocking_signal_.NotifyOne();
......@@ -325,9 +427,16 @@ void CompilerDispatcher::DoBackgroundWork() {
}
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
bool aborted = false;
{
base::LockGuard<base::Mutex> lock(&mutex_);
idle_task_scheduled_ = false;
aborted = abort_;
}
if (aborted) {
AbortInactiveJobs();
return;
}
// Number of jobs that are unlikely to make progress during any idle callback
......
......@@ -10,9 +10,11 @@
#include <unordered_set>
#include <utility>
#include "src/base/atomic-utils.h"
#include "src/base/macros.h"
#include "src/base/platform/condition-variable.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/semaphore.h"
#include "src/globals.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
......@@ -85,20 +87,28 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask);
FRIEND_TEST(IgnitionCompilerDispatcherTest,
AsyncAbortAllPendingBackgroundTask);
FRIEND_TEST(IgnitionCompilerDispatcherTest,
AsyncAbortAllRunningBackgroundTask);
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll);
typedef std::multimap<std::pair<int, int>,
std::unique_ptr<CompilerDispatcherJob>>
JobMap;
class AbortTask;
class BackgroundTask;
class IdleTask;
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
bool IsEnabled() const;
void AbortInactiveJobs();
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
void ScheduleMoreBackgroundTasksIfNeeded();
void ScheduleIdleTaskFromAnyThread();
void ScheduleIdleTaskIfNeeded();
void ScheduleAbortTask();
void DoBackgroundWork();
void DoIdleWork(double deadline_in_seconds);
......@@ -117,6 +127,9 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
// the mutex |mutex_| while accessing them.
base::Mutex mutex_;
// True if the dispatcher is in the process of aborting running tasks.
bool abort_;
bool idle_task_scheduled_;
// Number of currently scheduled BackgroundTask objects.
......@@ -134,6 +147,10 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
CompilerDispatcherJob* main_thread_blocking_on_job_;
base::ConditionVariable main_thread_blocking_signal_;
// Test support.
base::AtomicValue<bool> block_for_testing_;
base::Semaphore semaphore_for_testing_;
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher);
};
......
......@@ -214,5 +214,50 @@ TEST(CancelableTask, RemoveUnmanagedId) {
EXPECT_FALSE(manager.TryAbort(3));
}
TEST(CancelableTask, EmptyTryAbortAll) {
CancelableTaskManager manager;
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskRemoved);
}
TEST(CancelableTask, ThreadedMultipleTasksNotRunTryAbortAll) {
CancelableTaskManager manager;
ResultType result1 = 0;
ResultType result2 = 0;
TestTask* task1 = new TestTask(&manager, &result1, TestTask::kCheckNotRun);
TestTask* task2 = new TestTask(&manager, &result2, TestTask::kCheckNotRun);
ThreadedRunner runner1(task1);
ThreadedRunner runner2(task2);
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskAborted);
// Tasks are canceled, hence the runner will bail out and not update result.
runner1.Start();
runner2.Start();
runner1.Join();
runner2.Join();
EXPECT_EQ(GetValue(&result1), 0);
EXPECT_EQ(GetValue(&result2), 0);
}
TEST(CancelableTask, ThreadedMultipleTasksStartedTryAbortAll) {
CancelableTaskManager manager;
ResultType result1 = 0;
ResultType result2 = 0;
TestTask* task1 =
new TestTask(&manager, &result1, TestTask::kWaitTillCanceledAgain);
TestTask* task2 =
new TestTask(&manager, &result2, TestTask::kWaitTillCanceledAgain);
ThreadedRunner runner1(task1);
ThreadedRunner runner2(task2);
runner1.Start();
// Busy wait on result to make sure task1 is done.
while (GetValue(&result1) == 0) {
}
EXPECT_EQ(manager.TryAbortAll(), CancelableTaskManager::kTaskRunning);
runner2.Start();
runner1.Join();
runner2.Join();
EXPECT_EQ(GetValue(&result1), 1);
EXPECT_EQ(GetValue(&result2), 0);
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment