v8/src/cancelable-task.cc
Andreas Haas 62fa048749 [wasm] Reschedule compilation tasks
At the moment, the whole WebAssembly compilation may run in a single
background task. On a low-end device, this can mean that the background
thread is busy for seconds and thereby blocks other tasks, see e.g.
https://crbug.com/914757.

With this CL we re-schedule compilation tasks after every 50ms. These
50ms are an arbitrary number. I don't want to introduce too much
overhead, but since this is in the background we also don't have to
make tasks super short.

Tasks which are going to compile with TurboFan will be posted with
lower priority.

This change requires changes in the CancelableTaskManager. At the
moment it is not possible that a background task posts a new task
which is managed by the same task manager as itself. The problem is
about how to deal with another thread which calls CancelAndWait
concurrently. At the moment, if a new task gets posted after the call
to CancelAndWait, then `CHECK(!canceled_)` in
CancelableTaskManager::Register will fail. If we used a lock to
synchronize the calls to CancelAndWait and Register, then there would
be a deadlock, where the thread which calls CancelAndWait waits for
the task which wants to call Register, but at the same time blocks that
task by holding the lock.

With the change here, posting a task after the call to CancelAndWait
will just immediately cancel the new task. This matches the behavior
you would get if CancelAndWait is called right after calling Register.

Bug: chromium:914757
Change-Id: I6d57aba161db8a915ec0d745658e0c28d25219a8
Reviewed-on: https://chromium-review.googlesource.com/c/1411884
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58898}
2019-01-17 18:27:08 +00:00

136 lines
4.4 KiB
C++

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/cancelable-task.h"
#include "src/base/platform/platform.h"
#include "src/isolate.h"
namespace v8 {
namespace internal {
Cancelable::~Cancelable() {
// The following check is needed to avoid calling an already terminated
// manager object. This happens when the manager cancels all pending tasks
// in {CancelAndWait} only before destroying the manager object.
Status previous;
if (TryRun(&previous) || previous == kRunning) {
parent_->RemoveFinishedTask(id_);
}
}
CancelableTaskManager::CancelableTaskManager()
: task_id_counter_(kInvalidTaskId), canceled_(false) {}
CancelableTaskManager::~CancelableTaskManager() {
// It is required that {CancelAndWait} is called before the manager object is
// destroyed. This guarantees that all tasks managed by this
// {CancelableTaskManager} are either canceled or finished their execution
// when the {CancelableTaskManager} dies.
CHECK(canceled_);
}
CancelableTaskManager::Id CancelableTaskManager::Register(Cancelable* task) {
base::MutexGuard guard(&mutex_);
if (canceled_) {
// The CancelableTaskManager has already been canceled. Therefore we mark
// the new task immediately as canceled so that it does not get executed.
task->Cancel();
return kInvalidTaskId;
}
CancelableTaskManager::Id id = ++task_id_counter_;
// Id overflows are not supported.
CHECK_NE(kInvalidTaskId, id);
CHECK(!canceled_);
cancelable_tasks_[id] = task;
return id;
}
void CancelableTaskManager::RemoveFinishedTask(CancelableTaskManager::Id id) {
CHECK_NE(kInvalidTaskId, id);
base::MutexGuard guard(&mutex_);
size_t removed = cancelable_tasks_.erase(id);
USE(removed);
DCHECK_NE(0u, removed);
cancelable_tasks_barrier_.NotifyOne();
}
TryAbortResult CancelableTaskManager::TryAbort(CancelableTaskManager::Id id) {
CHECK_NE(kInvalidTaskId, id);
base::MutexGuard guard(&mutex_);
auto entry = cancelable_tasks_.find(id);
if (entry != cancelable_tasks_.end()) {
Cancelable* value = entry->second;
if (value->Cancel()) {
// Cannot call RemoveFinishedTask here because of recursive locking.
cancelable_tasks_.erase(entry);
cancelable_tasks_barrier_.NotifyOne();
return TryAbortResult::kTaskAborted;
} else {
return TryAbortResult::kTaskRunning;
}
}
return TryAbortResult::kTaskRemoved;
}
void CancelableTaskManager::CancelAndWait() {
// Clean up all cancelable fore- and background tasks. Tasks are canceled on
// the way if possible, i.e., if they have not started yet. After each round
// of canceling we wait for the background tasks that have already been
// started.
base::MutexGuard guard(&mutex_);
canceled_ = true;
// Cancelable tasks could be running or could potentially register new
// tasks, requiring a loop here.
while (!cancelable_tasks_.empty()) {
for (auto it = cancelable_tasks_.begin(); it != cancelable_tasks_.end();) {
auto current = it;
// We need to get to the next element before erasing the current.
++it;
if (current->second->Cancel()) {
cancelable_tasks_.erase(current);
}
}
// Wait for already running background tasks.
if (!cancelable_tasks_.empty()) {
cancelable_tasks_barrier_.Wait(&mutex_);
}
}
}
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::MutexGuard guard(&mutex_);
if (cancelable_tasks_.empty()) return TryAbortResult::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() ? TryAbortResult::kTaskAborted
: TryAbortResult::kTaskRunning;
}
CancelableTask::CancelableTask(Isolate* isolate)
: CancelableTask(isolate->cancelable_task_manager()) {}
CancelableTask::CancelableTask(CancelableTaskManager* manager)
: Cancelable(manager) {}
CancelableIdleTask::CancelableIdleTask(Isolate* isolate)
: CancelableIdleTask(isolate->cancelable_task_manager()) {}
CancelableIdleTask::CancelableIdleTask(CancelableTaskManager* manager)
: Cancelable(manager) {}
} // namespace internal
} // namespace v8