Commit 10453e85 authored by jochen's avatar jochen Committed by Commit bot

Abort running compiler dispatcher tasks under memory pressure

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

Review-Url: https://codereview.chromium.org/2608163006
Cr-Commit-Position: refs/heads/master@{#42084}
parent b371b0b7
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include "src/bootstrapper.h" #include "src/bootstrapper.h"
#include "src/char-predicates-inl.h" #include "src/char-predicates-inl.h"
#include "src/code-stubs.h" #include "src/code-stubs.h"
#include "src/compiler-dispatcher/compiler-dispatcher.h"
#include "src/compiler.h" #include "src/compiler.h"
#include "src/context-measure.h" #include "src/context-measure.h"
#include "src/contexts.h" #include "src/contexts.h"
...@@ -8469,6 +8470,8 @@ void Isolate::MemoryPressureNotification(MemoryPressureLevel level) { ...@@ -8469,6 +8470,8 @@ void Isolate::MemoryPressureNotification(MemoryPressureLevel level) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this); i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->heap()->MemoryPressureNotification(level, Locker::IsLocked(this)); isolate->heap()->MemoryPressureNotification(level, Locker::IsLocked(this));
isolate->allocator()->MemoryPressureNotification(level); isolate->allocator()->MemoryPressureNotification(level);
isolate->compiler_dispatcher()->MemoryPressureNotification(
level, Locker::IsLocked(this));
} }
void Isolate::SetRAILMode(RAILMode rail_mode) { void Isolate::SetRAILMode(RAILMode rail_mode) {
......
...@@ -134,7 +134,8 @@ class V8_EXPORT_PRIVATE Cancelable { ...@@ -134,7 +134,8 @@ class V8_EXPORT_PRIVATE Cancelable {
// Multiple inheritance can be used because Task is a pure interface. // Multiple inheritance can be used because Task is a pure interface.
class CancelableTask : public Cancelable, public Task { class V8_EXPORT_PRIVATE CancelableTask : public Cancelable,
NON_EXPORTED_BASE(public Task) {
public: public:
explicit CancelableTask(Isolate* isolate); explicit CancelableTask(Isolate* isolate);
CancelableTask(Isolate* isolate, CancelableTaskManager* manager); CancelableTask(Isolate* isolate, CancelableTaskManager* manager);
......
...@@ -93,6 +93,32 @@ void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) { ...@@ -93,6 +93,32 @@ void DoNextStepOnBackgroundThread(CompilerDispatcherJob* job) {
// we'll get all of it so try to be a conservative. // we'll get all of it so try to be a conservative.
const double kMaxIdleTimeToExpectInMs = 40; const double kMaxIdleTimeToExpectInMs = 40;
class MemoryPressureTask : public CancelableTask {
public:
MemoryPressureTask(Isolate* isolate, CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher);
~MemoryPressureTask() override;
// CancelableTask implementation.
void RunInternal() override;
private:
CompilerDispatcher* dispatcher_;
DISALLOW_COPY_AND_ASSIGN(MemoryPressureTask);
};
MemoryPressureTask::MemoryPressureTask(Isolate* isolate,
CancelableTaskManager* task_manager,
CompilerDispatcher* dispatcher)
: CancelableTask(isolate, task_manager), dispatcher_(dispatcher) {}
MemoryPressureTask::~MemoryPressureTask() {}
void MemoryPressureTask::RunInternal() {
dispatcher_->AbortAll(CompilerDispatcher::BlockingBehavior::kDontBlock);
}
} // namespace } // namespace
class CompilerDispatcher::AbortTask : public CancelableTask { class CompilerDispatcher::AbortTask : public CancelableTask {
...@@ -180,6 +206,7 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform, ...@@ -180,6 +206,7 @@ CompilerDispatcher::CompilerDispatcher(Isolate* isolate, Platform* platform,
max_stack_size_(max_stack_size), max_stack_size_(max_stack_size),
tracer_(new CompilerDispatcherTracer(isolate_)), tracer_(new CompilerDispatcherTracer(isolate_)),
task_manager_(new CancelableTaskManager()), task_manager_(new CancelableTaskManager()),
memory_pressure_level_(MemoryPressureLevel::kNone),
abort_(false), abort_(false),
idle_task_scheduled_(false), idle_task_scheduled_(false),
num_scheduled_background_tasks_(0), num_scheduled_background_tasks_(0),
...@@ -196,6 +223,10 @@ CompilerDispatcher::~CompilerDispatcher() { ...@@ -196,6 +223,10 @@ CompilerDispatcher::~CompilerDispatcher() {
bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) { bool CompilerDispatcher::Enqueue(Handle<SharedFunctionInfo> function) {
if (!IsEnabled()) return false; if (!IsEnabled()) return false;
if (memory_pressure_level_.Value() != MemoryPressureLevel::kNone) {
return false;
}
{ {
base::LockGuard<base::Mutex> lock(&mutex_); base::LockGuard<base::Mutex> lock(&mutex_);
if (abort_) return false; if (abort_) return false;
...@@ -319,6 +350,34 @@ void CompilerDispatcher::AbortInactiveJobs() { ...@@ -319,6 +350,34 @@ void CompilerDispatcher::AbortInactiveJobs() {
} }
} }
void CompilerDispatcher::MemoryPressureNotification(
v8::MemoryPressureLevel level, bool is_isolate_locked) {
MemoryPressureLevel previous = memory_pressure_level_.Value();
memory_pressure_level_.SetValue(level);
// If we're already under pressure, we haven't accepted new tasks meanwhile
// and can just return. If we're no longer under pressure, we're also done.
if (previous != MemoryPressureLevel::kNone ||
level == MemoryPressureLevel::kNone) {
return;
}
if (is_isolate_locked) {
AbortAll(BlockingBehavior::kDontBlock);
} else {
{
base::LockGuard<base::Mutex> lock(&mutex_);
if (abort_) return;
// By going into abort mode here, and clearing the
// pending_background_jobs_, we at keep existing background jobs from
// picking up more work before the MemoryPressureTask gets executed.
abort_ = true;
pending_background_jobs_.clear();
}
platform_->CallOnForegroundThread(
reinterpret_cast<v8::Isolate*>(isolate_),
new MemoryPressureTask(isolate_, task_manager_.get(), this));
}
}
CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor( CompilerDispatcher::JobMap::const_iterator CompilerDispatcher::GetJobFor(
Handle<SharedFunctionInfo> shared) const { Handle<SharedFunctionInfo> shared) const {
if (!shared->script()->IsScript()) return jobs_.end(); if (!shared->script()->IsScript()) return jobs_.end();
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
namespace v8 { namespace v8 {
class Platform; class Platform;
enum class MemoryPressureLevel;
namespace internal { namespace internal {
...@@ -83,6 +84,10 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -83,6 +84,10 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
// Aborts all jobs. Blocks if requested. // Aborts all jobs. Blocks if requested.
void AbortAll(BlockingBehavior blocking); void AbortAll(BlockingBehavior blocking);
// Memory pressure notifications from the embedder.
void MemoryPressureNotification(v8::MemoryPressureLevel level,
bool is_isolate_locked);
private: private:
FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime); FRIEND_TEST(CompilerDispatcherTest, IdleTaskSmallIdleTime);
FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread); FRIEND_TEST(IgnitionCompilerDispatcherTest, CompileOnBackgroundThread);
...@@ -123,6 +128,8 @@ class V8_EXPORT_PRIVATE CompilerDispatcher { ...@@ -123,6 +128,8 @@ class V8_EXPORT_PRIVATE CompilerDispatcher {
// as script id is not necessarily unique. // as script id is not necessarily unique.
JobMap jobs_; JobMap jobs_;
base::AtomicValue<v8::MemoryPressureLevel> memory_pressure_level_;
// The following members can be accessed from any thread. Methods need to hold // The following members can be accessed from any thread. Methods need to hold
// the mutex |mutex_| while accessing them. // the mutex |mutex_| while accessing them.
base::Mutex mutex_; base::Mutex mutex_;
......
...@@ -714,5 +714,88 @@ TEST_F(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll) { ...@@ -714,5 +714,88 @@ TEST_F(IgnitionCompilerDispatcherTest, FinishNowDuringAbortAll) {
platform.ClearIdleTask(); platform.ClearIdleTask();
} }
TEST_F(CompilerDispatcherTest, MemoryPressure) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f14(x) { return x * y }; return f14; "
"} g();";
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
// Can't enqueue tasks under memory pressure.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true);
ASSERT_FALSE(dispatcher.Enqueue(shared));
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kNone, true);
ASSERT_TRUE(dispatcher.Enqueue(shared));
// Memory pressure cancels current jobs.
dispatcher.MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
true);
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
platform.ClearIdleTask();
}
namespace {
class PressureNotificationTask : public CancelableTask {
public:
PressureNotificationTask(Isolate* isolate, CompilerDispatcher* dispatcher,
base::Semaphore* sem)
: CancelableTask(isolate), dispatcher_(dispatcher), sem_(sem) {}
~PressureNotificationTask() override {}
void RunInternal() override {
dispatcher_->MemoryPressureNotification(v8::MemoryPressureLevel::kCritical,
false);
sem_->Signal();
}
private:
CompilerDispatcher* dispatcher_;
base::Semaphore* sem_;
DISALLOW_COPY_AND_ASSIGN(PressureNotificationTask);
};
} // namespace
TEST_F(CompilerDispatcherTest, MemoryPressureFromBackground) {
MockPlatform platform;
CompilerDispatcher dispatcher(i_isolate(), &platform, FLAG_stack_size);
const char script[] =
"function g() { var y = 1; function f15(x) { return x * y }; return f15; "
"} g();";
Handle<JSFunction> f = Handle<JSFunction>::cast(RunJS(isolate(), script));
Handle<SharedFunctionInfo> shared(f->shared(), i_isolate());
ASSERT_TRUE(dispatcher.Enqueue(shared));
base::Semaphore sem(0);
V8::GetCurrentPlatform()->CallOnBackgroundThread(
new PressureNotificationTask(i_isolate(), &dispatcher, &sem),
v8::Platform::kShortRunningTask);
sem.Wait();
// A memory pressure task is pending, and running it will cancel the job.
ASSERT_TRUE(platform.ForegroundTasksPending());
ASSERT_TRUE(dispatcher.IsEnqueued(shared));
platform.RunForegroundTasks();
ASSERT_FALSE(dispatcher.IsEnqueued(shared));
ASSERT_FALSE(shared->is_compiled());
// Since the AbortAll() call is made from a task, AbortAll thinks that there
// is at least one task running, and fires of an AbortTask to be safe.
ASSERT_TRUE(platform.ForegroundTasksPending());
platform.RunForegroundTasks();
ASSERT_FALSE(platform.ForegroundTasksPending());
platform.ClearIdleTask();
}
} // namespace internal } // namespace internal
} // namespace v8 } // 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