#include "base/task/execution_fence.h"
#include <map>
#include <optional>
#include <ostream>
#include <string>
#include "base/barrier_closure.h"
#include "base/containers/enum_set.h"
#include "base/features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
using ::testing::_;
enum class TaskType {
kThreadPoolDefault,
kThreadPoolBestEffort,
kTaskQueueDefault,
kTaskQueueBestEffort,
};
using TaskTypeSet = EnumSet<TaskType,
TaskType::kThreadPoolDefault,
TaskType::kTaskQueueBestEffort>;
std::ostream& operator<<(std::ostream& os, TaskType task_type) {
switch (task_type) {
case TaskType::kThreadPoolDefault:
return os << "kThreadPoolDefault";
case TaskType::kThreadPoolBestEffort:
return os << "kThreadPoolBestEffort";
case TaskType::kTaskQueueDefault:
return os << "kTaskQueueDefault";
case TaskType::kTaskQueueBestEffort:
return os << "kTaskQueueBestEffort";
}
}
std::ostream& operator<<(std::ostream& os, TaskTypeSet task_types) {
std::string sep = "";
os << "[";
for (TaskType task_type : task_types) {
os << sep << task_type;
sep = ",";
}
return os << "]";
}
}
struct TestParams {
bool block_best_effort_task_queue = false;
TaskTypeSet task_queue_types_during_best_effort_fence;
TaskTypeSet task_queue_types_after_best_effort_fence;
};
class ExecutionFenceTest : public ::testing::TestWithParam<TestParams> {
public:
ExecutionFenceTest() {
scoped_feature_list_.InitWithFeatureState(
features::kScopedBestEffortExecutionFenceForTaskQueue,
GetParam().block_best_effort_task_queue);
}
~ExecutionFenceTest() override {
RepeatingClosure barrier_closure =
BarrierClosure(TaskTypeSet::All().size(), task_env_.QuitClosure());
for (TaskType task_type : TaskTypeSet::All()) {
task_runners_.at(task_type)->PostTask(FROM_HERE, barrier_closure);
}
task_env_.RunUntilQuit();
}
void TinyWait() {
task_env_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE, task_env_.QuitClosure(), TestTimeouts::tiny_timeout());
task_env_.RunUntilQuit();
}
void RunPostedTasksAndExpect(TaskTypeSet expected_tasks,
const Location& location = Location::Current()) {
SCOPED_TRACE(location.ToString());
{
AutoLock lock(tasks_that_ran_lock_);
while (!tasks_that_ran_.HasAll(expected_tasks)) {
AutoUnlock unlock(tasks_that_ran_lock_);
TinyWait();
}
}
if (expected_tasks != TaskTypeSet::All()) {
TinyWait();
}
AutoLock lock(tasks_that_ran_lock_);
EXPECT_EQ(tasks_that_ran_, expected_tasks);
tasks_that_ran_.Clear();
}
void PostTestTasks() {
{
AutoLock lock(tasks_that_ran_lock_);
ASSERT_TRUE(tasks_that_ran_.empty());
}
for (TaskType task_type : TaskTypeSet::All()) {
task_runners_.at(task_type)->PostTask(
FROM_HERE, BindLambdaForTesting([this, task_type] {
AutoLock lock(tasks_that_ran_lock_);
tasks_that_ran_.Put(task_type);
}));
}
}
protected:
test::ScopedFeatureList scoped_feature_list_;
test::TaskEnvironmentWithMainThreadPriorities task_env_{
test::TaskEnvironment::ScopedExecutionFenceBehaviour::
MAIN_THREAD_AND_THREAD_POOL};
std::map<TaskType, scoped_refptr<SequencedTaskRunner>> task_runners_{
{TaskType::kThreadPoolDefault, ThreadPool::CreateSequencedTaskRunner({})},
{TaskType::kThreadPoolBestEffort,
ThreadPool::CreateSequencedTaskRunner({TaskPriority::BEST_EFFORT})},
{TaskType::kTaskQueueDefault, task_env_.GetMainThreadTaskRunner()},
{TaskType::kTaskQueueBestEffort,
task_env_.GetMainThreadTaskRunnerWithPriority(
TaskPriority::BEST_EFFORT)},
};
Lock tasks_that_ran_lock_;
TaskTypeSet tasks_that_ran_ GUARDED_BY(tasks_that_ran_lock_);
};
INSTANTIATE_TEST_SUITE_P(All,
ExecutionFenceTest,
::testing::Values(
TestParams{
.block_best_effort_task_queue = false,
.task_queue_types_during_best_effort_fence =
{TaskType::kTaskQueueDefault,
TaskType::kTaskQueueBestEffort},
.task_queue_types_after_best_effort_fence = {},
},
TestParams{
.block_best_effort_task_queue = true,
.task_queue_types_during_best_effort_fence =
{TaskType::kTaskQueueDefault},
.task_queue_types_after_best_effort_fence =
{TaskType::kTaskQueueBestEffort},
}));
TEST_P(ExecutionFenceTest, BestEffortFence) {
{
ScopedBestEffortExecutionFence best_effort_fence;
PostTestTasks();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolDefault},
GetParam().task_queue_types_during_best_effort_fence));
}
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolBestEffort},
GetParam().task_queue_types_after_best_effort_fence));
PostTestTasks();
RunPostedTasksAndExpect(TaskTypeSet::All());
}
TEST_P(ExecutionFenceTest, ThreadPoolFence) {
{
ScopedThreadPoolExecutionFence thread_pool_fence;
PostTestTasks();
RunPostedTasksAndExpect(
{TaskType::kTaskQueueDefault, TaskType::kTaskQueueBestEffort});
}
RunPostedTasksAndExpect(
{TaskType::kThreadPoolDefault, TaskType::kThreadPoolBestEffort});
PostTestTasks();
RunPostedTasksAndExpect(TaskTypeSet::All());
}
TEST_P(ExecutionFenceTest, NestedFences) {
auto best_effort_fence1 =
std::make_optional<ScopedBestEffortExecutionFence>();
auto best_effort_fence2 =
std::make_optional<ScopedBestEffortExecutionFence>();
PostTestTasks();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolDefault},
GetParam().task_queue_types_during_best_effort_fence));
auto thread_pool_fence1 =
std::make_optional<ScopedThreadPoolExecutionFence>();
auto thread_pool_fence2 =
std::make_optional<ScopedThreadPoolExecutionFence>();
PostTestTasks();
RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);
thread_pool_fence2.reset();
RunPostedTasksAndExpect({});
PostTestTasks();
RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);
thread_pool_fence1.reset();
RunPostedTasksAndExpect({TaskType::kThreadPoolDefault});
PostTestTasks();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolDefault},
GetParam().task_queue_types_during_best_effort_fence));
best_effort_fence2.reset();
RunPostedTasksAndExpect({});
PostTestTasks();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolDefault},
GetParam().task_queue_types_during_best_effort_fence));
best_effort_fence1.reset();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolBestEffort},
GetParam().task_queue_types_after_best_effort_fence));
PostTestTasks();
RunPostedTasksAndExpect(TaskTypeSet::All());
}
TEST_P(ExecutionFenceTest, StaggeredFences) {
auto best_effort_fence1 =
std::make_optional<ScopedBestEffortExecutionFence>();
PostTestTasks();
RunPostedTasksAndExpect(
Union({TaskType::kThreadPoolDefault},
GetParam().task_queue_types_during_best_effort_fence));
auto thread_pool_fence1 =
std::make_optional<ScopedThreadPoolExecutionFence>();
PostTestTasks();
RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);
auto best_effort_fence2 =
std::make_optional<ScopedBestEffortExecutionFence>();
auto thread_pool_fence2 =
std::make_optional<ScopedThreadPoolExecutionFence>();
PostTestTasks();
RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);
best_effort_fence1.reset();
RunPostedTasksAndExpect({});
thread_pool_fence1.reset();
RunPostedTasksAndExpect({});
best_effort_fence2.reset();
RunPostedTasksAndExpect(GetParam().task_queue_types_after_best_effort_fence);
thread_pool_fence2.reset();
RunPostedTasksAndExpect(
{TaskType::kThreadPoolDefault, TaskType::kThreadPoolBestEffort});
PostTestTasks();
RunPostedTasksAndExpect(TaskTypeSet::All());
}
}