910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#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::_;

// Types of task to post while a fence is up.
enum class TaskType {
  // ThreadPool task with default priority.
  kThreadPoolDefault,
  // ThreadPool task with best-effort priority.
  kThreadPoolBestEffort,
  // Task posted to a TaskQueue with default priority.
  kTaskQueueDefault,
  // Task posted to a TaskQueue with best-effort priority.
  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 << "]";
}

}  // namespace

struct TestParams {
  // Whether or not ScopedBestEffortExecutionFence should block TaskQueue tasks.
  bool block_best_effort_task_queue = false;

  // All TaskQueue task types that should run while a
  // ScopedBestEffortExecutionFence is up.
  TaskTypeSet task_queue_types_during_best_effort_fence;

  // All TaskQueue task types that should run as soon as the last
  // ScopedBestEffortExecutionFence comes down.
  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 {
    // Flush every task runner being tested.
    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();
  }

  // Wait for all tasks to get a chance to run.
  void TinyWait() {
    task_env_.GetMainThreadTaskRunner()->PostDelayedTask(
        FROM_HERE, task_env_.QuitClosure(), TestTimeouts::tiny_timeout());
    task_env_.RunUntilQuit();
  }

  // Wait for all posted tasks to get a chance to run, and then expect that one
  // each of `expected_tasks` ran since the last call.
  void RunPostedTasksAndExpect(TaskTypeSet expected_tasks,
                               const Location& location = Location::Current()) {
    SCOPED_TRACE(location.ToString());

    // Wait for all expected tasks to run. If a task is blocked incorrectly, the
    // test will time out.
    {
      AutoLock lock(tasks_that_ran_lock_);
      while (!tasks_that_ran_.HasAll(expected_tasks)) {
        AutoUnlock unlock(tasks_that_ran_lock_);
        TinyWait();
      }
    }

    // If we expect any blocked tasks, wait a bit to make sure they don't run.
    // There's a chance that a task won't be scheduled until after TinyWait(),
    // but it's small. Since this is testing for tasks that run when they're not
    // supposed to, missing the timeout would be a false negative. So a flaky
    // test should be considered a failure - it usually fails (correctly
    // detecting an error) but occasionally succeeds (incorrectly).
    if (expected_tasks != TaskTypeSet::All()) {
      TinyWait();
    }

    // Whew. Now make sure the exact expected task types ran.
    AutoLock lock(tasks_that_ran_lock_);
    EXPECT_EQ(tasks_that_ran_, expected_tasks);
    tasks_that_ran_.Clear();
  }

  // Post a task of each type.
  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};

  // A TaskRunner for each TaskType.
  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 protecting `tasks_that_ran_`. This doesn't need to be held while
  // bringing down a fence since the test waits for all tasks to finish running
  // before modifying the fence on the main thread, so there's no chance for
  // tasks on other threads to run before taking the lock. (Unless there's a
  // flake as described in RunPostedTasksAndExpect(), but then taking the lock
  // would hide the flake - a task might incorrectly start running with the
  // fence still up, but context switch before taking the lock, and then block
  // while the main thread takes the lock and checks `tasks_that_ran_`, making
  // it appear to run after the fence goes down.)
  Lock tasks_that_ran_lock_;

  // Each type of tasks that executes between calls to
  // RunPostedTasksAndExpect(). This is updated from multiple sequences and read
  // from the main thread.
  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;

    // While this fence is up, only default-priority tasks should run.
    PostTestTasks();
    RunPostedTasksAndExpect(
        Union({TaskType::kThreadPoolDefault},
              GetParam().task_queue_types_during_best_effort_fence));
  }

  // After bringing the fence down, unblocked best-effort tasks should run.
  RunPostedTasksAndExpect(
      Union({TaskType::kThreadPoolBestEffort},
            GetParam().task_queue_types_after_best_effort_fence));

  // Now that the fence is down all tasks should run.
  PostTestTasks();
  RunPostedTasksAndExpect(TaskTypeSet::All());
}

TEST_P(ExecutionFenceTest, ThreadPoolFence) {
  {
    ScopedThreadPoolExecutionFence thread_pool_fence;

    // While this fence is up, only TaskQueue tasks should run.
    PostTestTasks();
    RunPostedTasksAndExpect(
        {TaskType::kTaskQueueDefault, TaskType::kTaskQueueBestEffort});
  }

  // After bringing the fence down, unblocked ThreadPool tasks should run.
  RunPostedTasksAndExpect(
      {TaskType::kThreadPoolDefault, TaskType::kThreadPoolBestEffort});

  // No more fences. All posted tasks run.
  PostTestTasks();
  RunPostedTasksAndExpect(TaskTypeSet::All());
}

TEST_P(ExecutionFenceTest, NestedFences) {
  auto best_effort_fence1 =
      std::make_optional<ScopedBestEffortExecutionFence>();
  auto best_effort_fence2 =
      std::make_optional<ScopedBestEffortExecutionFence>();

  // While these fences are up, only default-priority tasks should run.
  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>();

  // Now both types of fence are up, so only TaskQueue tasks should run.
  PostTestTasks();
  RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);

  thread_pool_fence2.reset();

  // Still a fence up, so nothing should be unblocked.
  RunPostedTasksAndExpect({});

  // New ThreadPool tasks still shouldn't run.
  PostTestTasks();
  RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);

  thread_pool_fence1.reset();

  // After bringing the last ThreadPool fence down, unblocked ThreadPool
  // tasks should run.
  RunPostedTasksAndExpect({TaskType::kThreadPoolDefault});

  // But new best-effort tasks shouldn't.
  PostTestTasks();
  RunPostedTasksAndExpect(
      Union({TaskType::kThreadPoolDefault},
            GetParam().task_queue_types_during_best_effort_fence));

  best_effort_fence2.reset();

  // Still a best-effort fence up, so nothing should be unblocked.
  RunPostedTasksAndExpect({});

  // New best-effort tasks still shouldn't run.
  PostTestTasks();
  RunPostedTasksAndExpect(
      Union({TaskType::kThreadPoolDefault},
            GetParam().task_queue_types_during_best_effort_fence));

  best_effort_fence1.reset();

  // After bringing the last fence down, unblocked best-effort tasks should
  // run.
  RunPostedTasksAndExpect(
      Union({TaskType::kThreadPoolBestEffort},
            GetParam().task_queue_types_after_best_effort_fence));

  // No more fences. All posted tasks run.
  PostTestTasks();
  RunPostedTasksAndExpect(TaskTypeSet::All());
}

TEST_P(ExecutionFenceTest, StaggeredFences) {
  auto best_effort_fence1 =
      std::make_optional<ScopedBestEffortExecutionFence>();

  // Best-effort tasks don't run.
  PostTestTasks();
  RunPostedTasksAndExpect(
      Union({TaskType::kThreadPoolDefault},
            GetParam().task_queue_types_during_best_effort_fence));

  auto thread_pool_fence1 =
      std::make_optional<ScopedThreadPoolExecutionFence>();

  // Best-effort and ThreadPool tasks don't run.
  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>();

  // Best-effort and ThreadPool tasks still don't run.
  PostTestTasks();
  RunPostedTasksAndExpect(GetParam().task_queue_types_during_best_effort_fence);

  // Bring down the first best-effort fence. Another one's still up, so
  // nothing's unblocked.
  best_effort_fence1.reset();
  RunPostedTasksAndExpect({});

  // Bring down the first ThreadPool fence. Another one's still up, so nothing's
  // unblocked.
  thread_pool_fence1.reset();
  RunPostedTasksAndExpect({});

  // Bring down the second best-effort fence. Only best-effort TaskQueue tasks
  // are unblocked.
  best_effort_fence2.reset();
  RunPostedTasksAndExpect(GetParam().task_queue_types_after_best_effort_fence);

  // Bring down the second ThreadPool fence. All tasks are now unblocked.
  thread_pool_fence2.reset();
  RunPostedTasksAndExpect(
      {TaskType::kThreadPoolDefault, TaskType::kThreadPoolBestEffort});

  // No more fences. All posted tasks run.
  PostTestTasks();
  RunPostedTasksAndExpect(TaskTypeSet::All());
}

}  // namespace base