Commit 1dc3ac96 authored by Shu-yu Guo's avatar Shu-yu Guo Committed by Commit Bot

[platform] Support nestable and non-nestable tasks in DefaultForegroundTaskRunner

Bug: v8:8179
Change-Id: I3a41243b971d499d50e35c4782bff5b8b012f434
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2013695
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65965}
parent dc3893e5
......@@ -47,9 +47,11 @@ V8_PLATFORM_EXPORT std::unique_ptr<v8::Platform> NewDefaultPlatform(
* Pumps the message loop for the given isolate.
*
* The caller has to make sure that this is called from the right thread.
* Returns true if a task was executed, and false otherwise. Unless requested
* through the |behavior| parameter, this call does not block if no task is
* pending. The |platform| has to be created using |NewDefaultPlatform|.
* Returns true if a task was executed, and false otherwise. If the call to
* PumpMessageLoop is nested within another call to PumpMessageLoop, only
* nestable tasks may run. Otherwise, any task may run. Unless requested through
* the |behavior| parameter, this call does not block if no task is pending. The
* |platform| has to be created using |NewDefaultPlatform|.
*/
V8_PLATFORM_EXPORT bool PumpMessageLoop(
v8::Platform* platform, v8::Isolate* isolate,
......
......@@ -10,6 +10,18 @@
namespace v8 {
namespace platform {
DefaultForegroundTaskRunner::RunTaskScope::RunTaskScope(
std::shared_ptr<DefaultForegroundTaskRunner> task_runner)
: task_runner_(task_runner) {
DCHECK_GE(task_runner->nesting_depth_, 0);
task_runner->nesting_depth_++;
}
DefaultForegroundTaskRunner::RunTaskScope::~RunTaskScope() {
DCHECK_GT(task_runner_->nesting_depth_, 0);
task_runner_->nesting_depth_--;
}
DefaultForegroundTaskRunner::DefaultForegroundTaskRunner(
IdleTaskSupport idle_task_support, TimeFunction time_function)
: idle_task_support_(idle_task_support), time_function_(time_function) {}
......@@ -19,21 +31,22 @@ void DefaultForegroundTaskRunner::Terminate() {
terminated_ = true;
// Drain the task queues.
while (!task_queue_.empty()) task_queue_.pop();
while (!task_queue_.empty()) task_queue_.pop_front();
while (!delayed_task_queue_.empty()) delayed_task_queue_.pop();
while (!idle_task_queue_.empty()) idle_task_queue_.pop();
}
void DefaultForegroundTaskRunner::PostTaskLocked(std::unique_ptr<Task> task,
Nestability nestability,
const base::MutexGuard&) {
if (terminated_) return;
task_queue_.push(std::move(task));
task_queue_.push_back(std::make_pair(nestability, std::move(task)));
event_loop_control_.NotifyOne();
}
void DefaultForegroundTaskRunner::PostTask(std::unique_ptr<Task> task) {
base::MutexGuard guard(&lock_);
PostTaskLocked(std::move(task), guard);
PostTaskLocked(std::move(task), kNestable, guard);
}
double DefaultForegroundTaskRunner::MonotonicallyIncreasingTime() {
......@@ -62,31 +75,46 @@ bool DefaultForegroundTaskRunner::IdleTasksEnabled() {
void DefaultForegroundTaskRunner::PostNonNestableTask(
std::unique_ptr<Task> task) {
// Default platform does not nest tasks.
PostTask(std::move(task));
base::MutexGuard guard(&lock_);
PostTaskLocked(std::move(task), kNonNestable, guard);
}
bool DefaultForegroundTaskRunner::NonNestableTasksEnabled() const {
return true;
}
bool DefaultForegroundTaskRunner::HasPoppableTaskInQueue() const {
if (nesting_depth_ == 0) return !task_queue_.empty();
for (auto it = task_queue_.cbegin(); it != task_queue_.cend(); it++) {
if (it->first == kNestable) return true;
}
return false;
}
std::unique_ptr<Task> DefaultForegroundTaskRunner::PopTaskFromQueue(
MessageLoopBehavior wait_for_work) {
base::MutexGuard guard(&lock_);
// Move delayed tasks that hit their deadline to the main queue.
std::unique_ptr<Task> task = PopTaskFromDelayedQueueLocked(guard);
while (task) {
PostTaskLocked(std::move(task), guard);
PostTaskLocked(std::move(task), kNestable, guard);
task = PopTaskFromDelayedQueueLocked(guard);
}
while (task_queue_.empty()) {
while (!HasPoppableTaskInQueue()) {
if (wait_for_work == MessageLoopBehavior::kDoNotWait) return {};
WaitForTaskLocked(guard);
}
task = std::move(task_queue_.front());
task_queue_.pop();
auto it = task_queue_.begin();
for (; it != task_queue_.end(); it++) {
// When the task queue is nested (i.e. popping a task from the queue from
// within a task), only nestable tasks may run. Otherwise, any task may run.
if (nesting_depth_ == 0 || it->first == kNestable) break;
}
DCHECK(it != task_queue_.end());
task = std::move(it->second);
task_queue_.erase(it);
return task;
}
......
......@@ -20,6 +20,18 @@ class V8_PLATFORM_EXPORT DefaultForegroundTaskRunner
: public NON_EXPORTED_BASE(TaskRunner) {
public:
using TimeFunction = double (*)();
class RunTaskScope {
public:
explicit RunTaskScope(
std::shared_ptr<DefaultForegroundTaskRunner> task_runner);
~RunTaskScope();
private:
RunTaskScope(const RunTaskScope&) = delete;
RunTaskScope& operator=(const RunTaskScope&) = delete;
std::shared_ptr<DefaultForegroundTaskRunner> task_runner_;
};
DefaultForegroundTaskRunner(IdleTaskSupport idle_task_support,
TimeFunction time_function);
......@@ -46,18 +58,31 @@ class V8_PLATFORM_EXPORT DefaultForegroundTaskRunner
bool NonNestableTasksEnabled() const override;
private:
// The same as PostTask, but the lock is already held by the caller. The
// {guard} parameter should make sure that the caller is holding the lock.
void PostTaskLocked(std::unique_ptr<Task> task, const base::MutexGuard&);
enum Nestability { kNestable, kNonNestable };
// The same as PostTask or PostNonNestableTask, but the lock is already held
// by the caller. The {guard} parameter should make sure that the caller is
// holding the lock.
void PostTaskLocked(std::unique_ptr<Task> task, Nestability nestability,
const base::MutexGuard&);
// A caller of this function has to hold {lock_}. The {guard} parameter should
// make sure that the caller is holding the lock.
std::unique_ptr<Task> PopTaskFromDelayedQueueLocked(const base::MutexGuard&);
// A non-nestable task is poppable only if the task runner is not nested,
// i.e. if a task is not being run from within a task. A nestable task is
// always poppable.
bool HasPoppableTaskInQueue() const;
bool terminated_ = false;
base::Mutex lock_;
base::ConditionVariable event_loop_control_;
std::queue<std::unique_ptr<Task>> task_queue_;
int nesting_depth_ = 0;
using TaskQueueEntry = std::pair<Nestability, std::unique_ptr<Task>>;
std::deque<TaskQueueEntry> task_queue_;
IdleTaskSupport idle_task_support_;
std::queue<std::unique_ptr<IdleTask>> idle_task_queue_;
......
......@@ -141,6 +141,7 @@ bool DefaultPlatform::PumpMessageLoop(v8::Isolate* isolate,
std::unique_ptr<Task> task = task_runner->PopTaskFromQueue(wait_for_work);
if (!task) return failed_result;
DefaultForegroundTaskRunner::RunTaskScope scope(task_runner);
task->Run();
return true;
}
......@@ -163,6 +164,7 @@ void DefaultPlatform::RunIdleTasks(v8::Isolate* isolate,
while (deadline_in_seconds > MonotonicallyIncreasingTime()) {
std::unique_ptr<IdleTask> task = task_runner->PopTaskFromIdleQueue();
if (!task) return;
DefaultForegroundTaskRunner::RunTaskScope scope(task_runner);
task->Run(deadline_in_seconds);
}
}
......
......@@ -65,6 +65,9 @@ class PlatformTest : public ::testing::Test {
void CallOnForegroundThread(Task* task) {
task_runner()->PostTask(std::unique_ptr<Task>(task));
}
void CallNonNestableOnForegroundThread(Task* task) {
task_runner()->PostNonNestableTask(std::unique_ptr<Task>(task));
}
void CallDelayedOnForegroundThread(Task* task, double delay_in_seconds) {
task_runner()->PostDelayedTask(std::unique_ptr<Task>(task),
delay_in_seconds);
......@@ -113,6 +116,37 @@ TEST_F(DefaultPlatformTest, PumpMessageLoopWithTaskRunner) {
EXPECT_FALSE(PumpMessageLoop());
}
TEST_F(DefaultPlatformTest, PumpMessageLoopNested) {
EXPECT_FALSE(PumpMessageLoop());
StrictMock<MockTask>* nestable_task1 = new StrictMock<MockTask>;
StrictMock<MockTask>* non_nestable_task2 = new StrictMock<MockTask>;
StrictMock<MockTask>* nestable_task3 = new StrictMock<MockTask>;
StrictMock<MockTask>* non_nestable_task4 = new StrictMock<MockTask>;
CallOnForegroundThread(nestable_task1);
CallNonNestableOnForegroundThread(non_nestable_task2);
CallOnForegroundThread(nestable_task3);
CallNonNestableOnForegroundThread(non_nestable_task4);
// Nestable tasks are FIFO; non-nestable tasks are FIFO. A task being
// non-nestable may cause it to be executed later, but not earlier.
EXPECT_CALL(*nestable_task1, Run).WillOnce([this]() {
EXPECT_TRUE(PumpMessageLoop());
});
EXPECT_CALL(*nestable_task3, Run());
EXPECT_CALL(*nestable_task3, Die());
EXPECT_CALL(*nestable_task1, Die());
EXPECT_TRUE(PumpMessageLoop());
EXPECT_CALL(*non_nestable_task2, Run());
EXPECT_CALL(*non_nestable_task2, Die());
EXPECT_TRUE(PumpMessageLoop());
EXPECT_CALL(*non_nestable_task4, Run());
EXPECT_CALL(*non_nestable_task4, Die());
EXPECT_TRUE(PumpMessageLoop());
EXPECT_FALSE(PumpMessageLoop());
}
TEST_F(DefaultPlatformTestWithMockTime, PumpMessageLoopDelayed) {
EXPECT_FALSE(PumpMessageLoop());
......
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