Reland "Use background tasks for the compiler dispatcher
Original issue's description:
> Use background tasks for the compiler dispatcher
>
> BUG=v8:5215
> R=marja@chromium.org,vogelheim@chromium.org
>
> Review-Url: https://codereview.chromium.org/2606263002
> Cr-Commit-Position: refs/heads/master@{#42035}
> Committed: 7a1b3a7beb
BUG=v8:5215
TBR=marja@chromium.org,vogelheim@chromium.org,rmcilroy@chromium.org
Review-Url: https://codereview.chromium.org/2613483002
Cr-Commit-Position: refs/heads/master@{#42040}
This commit is contained in:
parent
dc4586ce0c
commit
efb329a8ab
@ -93,13 +93,18 @@ void CancelableTaskManager::CancelAndWait() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CancelableTask::CancelableTask(Isolate* isolate)
|
||||
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
|
||||
: CancelableTask(isolate, isolate->cancelable_task_manager()) {}
|
||||
|
||||
CancelableTask::CancelableTask(Isolate* isolate, CancelableTaskManager* manager)
|
||||
: Cancelable(manager), isolate_(isolate) {}
|
||||
|
||||
CancelableIdleTask::CancelableIdleTask(Isolate* isolate)
|
||||
: Cancelable(isolate->cancelable_task_manager()), isolate_(isolate) {}
|
||||
: CancelableIdleTask(isolate, isolate->cancelable_task_manager()) {}
|
||||
|
||||
CancelableIdleTask::CancelableIdleTask(Isolate* isolate,
|
||||
CancelableTaskManager* manager)
|
||||
: Cancelable(manager), isolate_(isolate) {}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -126,6 +126,7 @@ class V8_EXPORT_PRIVATE Cancelable {
|
||||
class CancelableTask : public Cancelable, public Task {
|
||||
public:
|
||||
explicit CancelableTask(Isolate* isolate);
|
||||
CancelableTask(Isolate* isolate, CancelableTaskManager* manager);
|
||||
|
||||
// Task overrides.
|
||||
void Run() final {
|
||||
@ -148,6 +149,7 @@ class CancelableTask : public Cancelable, public Task {
|
||||
class CancelableIdleTask : public Cancelable, public IdleTask {
|
||||
public:
|
||||
explicit CancelableIdleTask(Isolate* isolate);
|
||||
CancelableIdleTask(Isolate* isolate, CancelableTaskManager* manager);
|
||||
|
||||
// IdleTask overrides.
|
||||
void Run(double deadline_in_seconds) final {
|
||||
|
@ -66,15 +66,65 @@ bool IsFinished(CompilerDispatcherJob* job) {
|
||||
job->status() == CompileJobStatus::kFailed;
|
||||
}
|
||||
|
||||
bool CanRunOnAnyThread(CompilerDispatcherJob* job) {
|
||||
return (job->status() == CompileJobStatus::kReadyToParse &&
|
||||
job->can_parse_on_background_thread()) ||
|
||||
(job->status() == CompileJobStatus::kReadyToCompile &&
|
||||
job->can_compile_on_background_thread());
|
||||
}
|
||||
|
||||
void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
|
||||
DCHECK(CanRunOnAnyThread(job));
|
||||
switch (job->status()) {
|
||||
case CompileJobStatus::kReadyToParse:
|
||||
job->Parse();
|
||||
break;
|
||||
|
||||
case CompileJobStatus::kReadyToCompile:
|
||||
job->Compile();
|
||||
break;
|
||||
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
// Theoretically we get 50ms of idle time max, however it's unlikely that
|
||||
// we'll get all of it so try to be a conservative.
|
||||
const double kMaxIdleTimeToExpectInMs = 40;
|
||||
|
||||
} // namespace
|
||||
|
||||
class CompilerDispatcher::BackgroundTask : public CancelableTask {
|
||||
public:
|
||||
BackgroundTask(Isolate* isolate, CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher);
|
||||
~BackgroundTask() override;
|
||||
|
||||
// CancelableTask implementation.
|
||||
void RunInternal() override;
|
||||
|
||||
private:
|
||||
CompilerDispatcher* dispatcher_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BackgroundTask);
|
||||
};
|
||||
|
||||
CompilerDispatcher::BackgroundTask::BackgroundTask(
|
||||
Isolate* isolate, CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher)
|
||||
: CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}
|
||||
|
||||
CompilerDispatcher::BackgroundTask::~BackgroundTask() {}
|
||||
|
||||
void CompilerDispatcher::BackgroundTask::RunInternal() {
|
||||
dispatcher_->DoBackgroundWork();
|
||||
}
|
||||
|
||||
class CompilerDispatcher::IdleTask : public CancelableIdleTask {
|
||||
public:
|
||||
IdleTask(Isolate* isolate, CompilerDispatcher* dispatcher);
|
||||
IdleTask(Isolate* isolate, CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher);
|
||||
~IdleTask() override;
|
||||
|
||||
// CancelableIdleTask implementation.
|
||||
@ -87,8 +137,9 @@ class CompilerDispatcher::IdleTask : public CancelableIdleTask {
|
||||
};
|
||||
|
||||
CompilerDispatcher::IdleTask::IdleTask(Isolate* isolate,
|
||||
CancelableTaskManager* task_manager,
|
||||
CompilerDispatcher* dispatcher)
|
||||
: CancelableIdleTask(isolate), dispatcher_(dispatcher) {}
|
||||
: CancelableIdleTask(isolate, task_manager), dispatcher_(dispatcher) {}
|
||||
|
||||
CompilerDispatcher::IdleTask::~IdleTask() {}
|
||||
|
||||
@ -102,11 +153,15 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
|
||||
platform_(platform),
|
||||
max_stack_size_(max_stack_size),
|
||||
tracer_(new CompilerDispatcherTracer(isolate_)),
|
||||
idle_task_scheduled_(false) {}
|
||||
task_manager_(new CancelableTaskManager()),
|
||||
idle_task_scheduled_(false),
|
||||
num_scheduled_background_tasks_(0),
|
||||
main_thread_blocking_on_job_(nullptr) {}
|
||||
|
||||
CompilerDispatcher::~CompilerDispatcher() {
|
||||
// To avoid crashing in unit tests due to unfished jobs.
|
||||
AbortAll(BlockingBehavior::kBlock);
|
||||
task_manager_->CancelAndWait();
|
||||
}
|
||||
|
||||
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
|
||||
@ -138,12 +193,27 @@ bool CompilerDispatcher::IsEnqueued(Handle<SharedFunctionInfo> function) const {
|
||||
return GetJobFor(function) != jobs_.end();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::WaitForJobIfRunningOnBackground(
|
||||
CompilerDispatcherJob* job) {
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
if (running_background_jobs_.find(job) == running_background_jobs_.end()) {
|
||||
pending_background_jobs_.erase(job);
|
||||
return;
|
||||
}
|
||||
DCHECK_NULL(main_thread_blocking_on_job_);
|
||||
main_thread_blocking_on_job_ = job;
|
||||
while (main_thread_blocking_on_job_ != nullptr) {
|
||||
main_thread_blocking_signal_.Wait(&mutex_);
|
||||
}
|
||||
DCHECK(pending_background_jobs_.find(job) == pending_background_jobs_.end());
|
||||
DCHECK(running_background_jobs_.find(job) == running_background_jobs_.end());
|
||||
}
|
||||
|
||||
bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
|
||||
JobMap::const_iterator job = GetJobFor(function);
|
||||
CHECK(job != jobs_.end());
|
||||
|
||||
// TODO(jochen): Check if there's an in-flight background task working on this
|
||||
// job.
|
||||
WaitForJobIfRunningOnBackground(job->second.get());
|
||||
while (!IsFinished(job->second.get())) {
|
||||
DoNextStepOnMainThread(isolate_, job->second.get(),
|
||||
ExceptionHandling::kThrow);
|
||||
@ -154,23 +224,11 @@ bool CompilerDispatcher::FinishNow(Handle<SharedFunctionInfo> function) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void CompilerDispatcher::Abort(Handle<SharedFunctionInfo> function,
|
||||
BlockingBehavior blocking) {
|
||||
USE(blocking);
|
||||
JobMap::const_iterator job = GetJobFor(function);
|
||||
CHECK(job != jobs_.end());
|
||||
|
||||
// TODO(jochen): Check if there's an in-flight background task working on this
|
||||
// job.
|
||||
job->second->ResetOnMainThread();
|
||||
jobs_.erase(job);
|
||||
}
|
||||
|
||||
void CompilerDispatcher::AbortAll(BlockingBehavior blocking) {
|
||||
USE(blocking);
|
||||
// TODO(jochen): Check if there's an in-flight background task working on this
|
||||
// job.
|
||||
// TODO(jochen): Implement support for non-blocking abort.
|
||||
DCHECK(blocking == BlockingBehavior::kBlock);
|
||||
for (auto& kv : jobs_) {
|
||||
WaitForJobIfRunningOnBackground(kv.second.get());
|
||||
kv.second->ResetOnMainThread();
|
||||
}
|
||||
jobs_.clear();
|
||||
@ -188,18 +246,87 @@ CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
|
||||
return jobs_.end();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
|
||||
void CompilerDispatcher::ScheduleIdleTaskFromAnyThread() {
|
||||
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
|
||||
DCHECK(platform_->IdleTasksEnabled(v8_isolate));
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
if (idle_task_scheduled_) return;
|
||||
if (jobs_.empty()) return;
|
||||
idle_task_scheduled_ = true;
|
||||
platform_->CallIdleOnForegroundThread(v8_isolate,
|
||||
new IdleTask(isolate_, this));
|
||||
}
|
||||
platform_->CallIdleOnForegroundThread(
|
||||
v8_isolate, new IdleTask(isolate_, task_manager_.get(), this));
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ScheduleIdleTaskIfNeeded() {
|
||||
if (jobs_.empty()) return;
|
||||
ScheduleIdleTaskFromAnyThread();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ConsiderJobForBackgroundProcessing(
|
||||
CompilerDispatcherJob* job) {
|
||||
if (!CanRunOnAnyThread(job)) return;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
pending_background_jobs_.insert(job);
|
||||
}
|
||||
ScheduleMoreBackgroundTasksIfNeeded();
|
||||
}
|
||||
|
||||
void CompilerDispatcher::ScheduleMoreBackgroundTasksIfNeeded() {
|
||||
if (FLAG_single_threaded) return;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
if (pending_background_jobs_.empty()) return;
|
||||
if (platform_->NumberOfAvailableBackgroundThreads() <=
|
||||
num_scheduled_background_tasks_) {
|
||||
return;
|
||||
}
|
||||
++num_scheduled_background_tasks_;
|
||||
}
|
||||
platform_->CallOnBackgroundThread(
|
||||
new BackgroundTask(isolate_, task_manager_.get(), this),
|
||||
v8::Platform::kShortRunningTask);
|
||||
}
|
||||
|
||||
void CompilerDispatcher::DoBackgroundWork() {
|
||||
CompilerDispatcherJob* job = nullptr;
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
--num_scheduled_background_tasks_;
|
||||
if (!pending_background_jobs_.empty()) {
|
||||
auto it = pending_background_jobs_.begin();
|
||||
job = *it;
|
||||
pending_background_jobs_.erase(it);
|
||||
running_background_jobs_.insert(job);
|
||||
}
|
||||
}
|
||||
if (job == nullptr) return;
|
||||
DoNextStepOnBackgroundThread(job);
|
||||
|
||||
ScheduleMoreBackgroundTasksIfNeeded();
|
||||
// Unconditionally schedule an idle task, as all background steps have to be
|
||||
// followed by a main thread step.
|
||||
ScheduleIdleTaskFromAnyThread();
|
||||
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
running_background_jobs_.erase(job);
|
||||
|
||||
if (main_thread_blocking_on_job_ == job) {
|
||||
main_thread_blocking_on_job_ = nullptr;
|
||||
main_thread_blocking_signal_.NotifyOne();
|
||||
}
|
||||
}
|
||||
// Don't touch |this| anymore after this point, as it might have been
|
||||
// deleted.
|
||||
}
|
||||
|
||||
void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
|
||||
{
|
||||
base::LockGuard<base::Mutex> lock(&mutex_);
|
||||
idle_task_scheduled_ = false;
|
||||
}
|
||||
|
||||
// Number of jobs that are unlikely to make progress during any idle callback
|
||||
// due to their estimated duration.
|
||||
@ -214,6 +341,17 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
|
||||
job != jobs_.end() && idle_time_in_seconds > 0.0;
|
||||
idle_time_in_seconds =
|
||||
deadline_in_seconds - platform_->MonotonicallyIncreasingTime()) {
|
||||
// Don't work on jobs that are being worked on by background tasks.
|
||||
// Similarly, remove jobs we work on from the set of available background
|
||||
// jobs.
|
||||
std::unique_ptr<base::LockGuard<base::Mutex>> lock(
|
||||
new base::LockGuard<base::Mutex>(&mutex_));
|
||||
if (running_background_jobs_.find(job->second.get()) !=
|
||||
running_background_jobs_.end()) {
|
||||
++job;
|
||||
continue;
|
||||
}
|
||||
auto it = pending_background_jobs_.find(job->second.get());
|
||||
double estimate_in_ms = job->second->EstimateRuntimeOfNextStepInMs();
|
||||
if (idle_time_in_seconds <
|
||||
(estimate_in_ms /
|
||||
@ -222,14 +360,23 @@ void CompilerDispatcher::DoIdleWork(double deadline_in_seconds) {
|
||||
// have managed to finish the job in a large idle task to assess
|
||||
// whether we should ask for another idle callback.
|
||||
if (estimate_in_ms > kMaxIdleTimeToExpectInMs) ++too_long_jobs;
|
||||
if (it == pending_background_jobs_.end()) {
|
||||
lock.reset();
|
||||
ConsiderJobForBackgroundProcessing(job->second.get());
|
||||
}
|
||||
++job;
|
||||
} else if (IsFinished(job->second.get())) {
|
||||
DCHECK(it == pending_background_jobs_.end());
|
||||
job->second->ResetOnMainThread();
|
||||
job = jobs_.erase(job);
|
||||
break;
|
||||
continue;
|
||||
} else {
|
||||
// Do one step, and keep processing the job (as we don't advance the
|
||||
// iterator).
|
||||
if (it != pending_background_jobs_.end()) {
|
||||
pending_background_jobs_.erase(it);
|
||||
}
|
||||
lock.reset();
|
||||
DoNextStepOnMainThread(isolate_, job->second.get(),
|
||||
ExceptionHandling::kSwallow);
|
||||
}
|
||||
|
@ -7,9 +7,12 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "src/base/macros.h"
|
||||
#include "src/base/platform/condition-variable.h"
|
||||
#include "src/base/platform/mutex.h"
|
||||
#include "src/globals.h"
|
||||
#include "testing/gtest/include/gtest/gtest_prod.h"
|
||||
|
||||
@ -19,6 +22,7 @@ class Platform;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class CancelableTaskManager;
|
||||
class CompilerDispatcherJob;
|
||||
class CompilerDispatcherTracer;
|
||||
class Isolate;
|
||||
@ -29,6 +33,30 @@ class Handle;
|
||||
|
||||
// The CompilerDispatcher uses a combination of idle tasks and background tasks
|
||||
// to parse and compile lazily parsed functions.
|
||||
//
|
||||
// As both parsing and compilation currently requires a preparation and
|
||||
// finalization step that happens on the main thread, every task has to be
|
||||
// advanced during idle time first. Depending on the properties of the task, it
|
||||
// can then be parsed or compiled on either background threads, or during idle
|
||||
// time. Last, it has to be finalized during idle time again.
|
||||
//
|
||||
// CompilerDispatcher::jobs_ maintains the list of all CompilerDispatcherJobs
|
||||
// the CompilerDispatcher knows about.
|
||||
//
|
||||
// CompilerDispatcher::pending_background_jobs_ contains the set of
|
||||
// CompilerDispatcherJobs that can be processed on a background thread.
|
||||
//
|
||||
// CompilerDispatcher::running_background_jobs_ contains the set of
|
||||
// CompilerDispatcherJobs that are currently being processed on a background
|
||||
// thread.
|
||||
//
|
||||
// CompilerDispatcher::DoIdleWork tries to advance as many jobs out of jobs_ as
|
||||
// possible during idle time. If a job can't be advanced, but is suitable for
|
||||
// background processing, it fires off background threads.
|
||||
//
|
||||
// CompilerDispatcher::DoBackgroundWork advances one of the pending jobs, and
|
||||
// then spins of another idle task to potentially do the final step on the main
|
||||
// thread.
|
||||
class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
public:
|
||||
enum class BlockingBehavior { kBlock, kDontBlock };
|
||||
@ -55,15 +83,23 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
|
||||
private:
|
||||
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
|
||||
FRIEND_TEST(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask);
|
||||
|
||||
typedef std::multimap<std::pair<int, int>,
|
||||
std::unique_ptr<CompilerDispatcherJob>>
|
||||
JobMap;
|
||||
class BackgroundTask;
|
||||
class IdleTask;
|
||||
|
||||
void WaitForJobIfRunningOnBackground(CompilerDispatcherJob* job);
|
||||
bool IsEnabled() const;
|
||||
JobMap::const_iterator GetJobFor(Handle<SharedFunctionInfo> shared) const;
|
||||
void ConsiderJobForBackgroundProcessing(CompilerDispatcherJob* job);
|
||||
void ScheduleMoreBackgroundTasksIfNeeded();
|
||||
void ScheduleIdleTaskFromAnyThread();
|
||||
void ScheduleIdleTaskIfNeeded();
|
||||
void DoBackgroundWork();
|
||||
void DoIdleWork(double deadline_in_seconds);
|
||||
|
||||
Isolate* isolate_;
|
||||
@ -71,12 +107,33 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
|
||||
size_t max_stack_size_;
|
||||
std::unique_ptr<CompilerDispatcherTracer> tracer_;
|
||||
|
||||
bool idle_task_scheduled_;
|
||||
std::unique_ptr<CancelableTaskManager> task_manager_;
|
||||
|
||||
// Mapping from (script id, function literal id) to job. We use a multimap,
|
||||
// as script id is not necessarily unique.
|
||||
JobMap jobs_;
|
||||
|
||||
// The following members can be accessed from any thread. Methods need to hold
|
||||
// the mutex |mutex_| while accessing them.
|
||||
base::Mutex mutex_;
|
||||
|
||||
bool idle_task_scheduled_;
|
||||
|
||||
// Number of currently scheduled BackgroundTask objects.
|
||||
size_t num_scheduled_background_tasks_;
|
||||
|
||||
// The set of CompilerDispatcherJobs that can be advanced on any thread.
|
||||
std::unordered_set<CompilerDispatcherJob*> pending_background_jobs_;
|
||||
|
||||
// The set of CompilerDispatcherJobs currently processed on background
|
||||
// threads.
|
||||
std::unordered_set<CompilerDispatcherJob*> running_background_jobs_;
|
||||
|
||||
// If not nullptr, then the main thread waits for the task processing
|
||||
// this job, and blocks on the ConditionVariable main_thread_blocking_signal_.
|
||||
CompilerDispatcherJob* main_thread_blocking_on_job_;
|
||||
base::ConditionVariable main_thread_blocking_signal_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CompilerDispatcher);
|
||||
};
|
||||
|
||||
|
@ -5,10 +5,13 @@
|
||||
#include "src/compiler-dispatcher/compiler-dispatcher.h"
|
||||
|
||||
#include "include/v8-platform.h"
|
||||
#include "src/base/platform/semaphore.h"
|
||||
#include "src/compiler-dispatcher/compiler-dispatcher-job.h"
|
||||
#include "src/compiler-dispatcher/compiler-dispatcher-tracer.h"
|
||||
#include "src/flags.h"
|
||||
#include "src/handles.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/v8.h"
|
||||
#include "test/unittests/compiler-dispatcher/compiler-dispatcher-helper.h"
|
||||
#include "test/unittests/test-utils.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
@ -40,16 +43,44 @@ class CompilerDispatcherTest : public TestWithContext {
|
||||
|
||||
bool CompilerDispatcherTest::old_flag_;
|
||||
|
||||
class IgnitionCompilerDispatcherTest : public CompilerDispatcherTest {
|
||||
public:
|
||||
IgnitionCompilerDispatcherTest() = default;
|
||||
~IgnitionCompilerDispatcherTest() override = default;
|
||||
|
||||
static void SetUpTestCase() {
|
||||
old_flag_ = i::FLAG_ignition;
|
||||
i::FLAG_ignition = true;
|
||||
CompilerDispatcherTest::SetUpTestCase();
|
||||
}
|
||||
|
||||
static void TearDownTestCase() {
|
||||
CompilerDispatcherTest::TearDownTestCase();
|
||||
i::FLAG_ignition = old_flag_;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool old_flag_;
|
||||
DISALLOW_COPY_AND_ASSIGN(IgnitionCompilerDispatcherTest);
|
||||
};
|
||||
|
||||
bool IgnitionCompilerDispatcherTest::old_flag_;
|
||||
|
||||
namespace {
|
||||
|
||||
class MockPlatform : public v8::Platform {
|
||||
public:
|
||||
MockPlatform() : task_(nullptr), time_(0.0), time_step_(0.0) {}
|
||||
~MockPlatform() override = default;
|
||||
MockPlatform() : idle_task_(nullptr), time_(0.0), time_step_(0.0), sem_(0) {}
|
||||
~MockPlatform() override {
|
||||
EXPECT_TRUE(tasks_.empty());
|
||||
EXPECT_TRUE(idle_task_ == nullptr);
|
||||
}
|
||||
|
||||
size_t NumberOfAvailableBackgroundThreads() override { return 1; }
|
||||
|
||||
void CallOnBackgroundThread(Task* task,
|
||||
ExpectedRuntime expected_runtime) override {
|
||||
UNREACHABLE();
|
||||
tasks_.push_back(task);
|
||||
}
|
||||
|
||||
void CallOnForegroundThread(v8::Isolate* isolate, Task* task) override {
|
||||
@ -63,7 +94,8 @@ class MockPlatform : public v8::Platform {
|
||||
|
||||
void CallIdleOnForegroundThread(v8::Isolate* isolate,
|
||||
IdleTask* task) override {
|
||||
task_ = task;
|
||||
ASSERT_TRUE(idle_task_ == nullptr);
|
||||
idle_task_ = task;
|
||||
}
|
||||
|
||||
bool IdleTasksEnabled(v8::Isolate* isolate) override { return true; }
|
||||
@ -74,21 +106,78 @@ class MockPlatform : public v8::Platform {
|
||||
}
|
||||
|
||||
void RunIdleTask(double deadline_in_seconds, double time_step) {
|
||||
ASSERT_TRUE(task_ != nullptr);
|
||||
ASSERT_TRUE(idle_task_ != nullptr);
|
||||
time_step_ = time_step;
|
||||
IdleTask* task = task_;
|
||||
task_ = nullptr;
|
||||
IdleTask* task = idle_task_;
|
||||
idle_task_ = nullptr;
|
||||
task->Run(deadline_in_seconds);
|
||||
delete task;
|
||||
}
|
||||
|
||||
bool IdleTaskPending() const { return !!task_; }
|
||||
bool IdleTaskPending() const { return idle_task_; }
|
||||
|
||||
bool BackgroundTasksPending() const { return !tasks_.empty(); }
|
||||
|
||||
void RunBackgroundTasksAndBlock(Platform* platform) {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, true),
|
||||
kShortRunningTask);
|
||||
sem_.Wait();
|
||||
}
|
||||
|
||||
void RunBackgroundTasks(Platform* platform) {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
platform->CallOnBackgroundThread(new TaskWrapper(this, tasks, false),
|
||||
kShortRunningTask);
|
||||
}
|
||||
|
||||
void ClearBackgroundTasks() {
|
||||
std::vector<Task*> tasks;
|
||||
tasks.swap(tasks_);
|
||||
for (auto& task : tasks) {
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
|
||||
void ClearIdleTask() {
|
||||
ASSERT_TRUE(idle_task_ != nullptr);
|
||||
delete idle_task_;
|
||||
idle_task_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
IdleTask* task_;
|
||||
class TaskWrapper : public Task {
|
||||
public:
|
||||
TaskWrapper(MockPlatform* platform, const std::vector<Task*>& tasks,
|
||||
bool signal)
|
||||
: platform_(platform), tasks_(tasks), signal_(signal) {}
|
||||
~TaskWrapper() = default;
|
||||
|
||||
void Run() override {
|
||||
for (auto& task : tasks_) {
|
||||
task->Run();
|
||||
delete task;
|
||||
}
|
||||
if (signal_) platform_->sem_.Signal();
|
||||
}
|
||||
|
||||
private:
|
||||
MockPlatform* platform_;
|
||||
std::vector<Task*> tasks_;
|
||||
bool signal_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TaskWrapper);
|
||||
};
|
||||
|
||||
IdleTask* idle_task_;
|
||||
double time_;
|
||||
double time_step_;
|
||||
|
||||
std::vector<Task*> tasks_;
|
||||
base::Semaphore sem_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MockPlatform);
|
||||
};
|
||||
|
||||
@ -112,8 +201,10 @@ TEST_F(CompilerDispatcherTest, IsEnqueued) {
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_TRUE(dispatcher.Enqueue(shared));
|
||||
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
|
||||
dispatcher.Abort(shared, CompilerDispatcher::BlockingBehavior::kBlock);
|
||||
dispatcher.AbortAll(CompilerDispatcher::BlockingBehavior::kBlock);
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
platform.ClearIdleTask();
|
||||
}
|
||||
|
||||
TEST_F(CompilerDispatcherTest, FinishNow) {
|
||||
@ -132,6 +223,8 @@ TEST_F(CompilerDispatcherTest, FinishNow) {
|
||||
// Finishing removes the SFI from the queue.
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_TRUE(shared->is_compiled());
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
platform.ClearIdleTask();
|
||||
}
|
||||
|
||||
TEST_F(CompilerDispatcherTest, IdleTask) {
|
||||
@ -188,7 +281,7 @@ TEST_F(CompilerDispatcherTest, IdleTaskSmallIdleTime) {
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kReadyToParse);
|
||||
|
||||
// Only grant a lot of idle time and freeze time.
|
||||
// Now grant a lot of idle time and freeze time.
|
||||
platform.RunIdleTask(1000.0, 0.0);
|
||||
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
@ -225,5 +318,91 @@ TEST_F(CompilerDispatcherTest, IdleTaskException) {
|
||||
ASSERT_FALSE(try_catch.HasCaught());
|
||||
}
|
||||
|
||||
TEST_F(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread) {
|
||||
MockPlatform platform;
|
||||
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
|
||||
|
||||
const char script[] =
|
||||
"function g() { var y = 1; function f6(x) { return x * y }; return f6; } "
|
||||
"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());
|
||||
|
||||
platform.RunBackgroundTasksAndBlock(V8::GetCurrentPlatform());
|
||||
|
||||
ASSERT_TRUE(platform.IdleTaskPending());
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
ASSERT_TRUE(dispatcher.jobs_.begin()->second->status() ==
|
||||
CompileJobStatus::kCompiled);
|
||||
|
||||
// Now grant a lot of idle time and freeze time.
|
||||
platform.RunIdleTask(1000.0, 0.0);
|
||||
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_TRUE(shared->is_compiled());
|
||||
ASSERT_FALSE(platform.IdleTaskPending());
|
||||
}
|
||||
|
||||
TEST_F(IgnitionCompilerDispatcherTest, FinishNowWithBackgroundTask) {
|
||||
MockPlatform platform;
|
||||
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
|
||||
|
||||
const char script[] =
|
||||
"function g() { var y = 1; function f7(x) { return x * y }; return f7; } "
|
||||
"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());
|
||||
|
||||
// This does not block, but races with the FinishNow() call below.
|
||||
platform.RunBackgroundTasks(V8::GetCurrentPlatform());
|
||||
|
||||
ASSERT_TRUE(dispatcher.FinishNow(shared));
|
||||
// Finishing removes the SFI from the queue.
|
||||
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
|
||||
ASSERT_TRUE(shared->is_compiled());
|
||||
if (platform.IdleTaskPending()) platform.ClearIdleTask();
|
||||
ASSERT_FALSE(platform.BackgroundTasksPending());
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
Loading…
Reference in New Issue
Block a user