blob: 6c585b48a89f79e730cd6f853944edc2fddac7d6 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/service/scheduler.h"
#include <algorithm>
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "gpu/command_buffer/service/sync_point_manager.h"
#include "gpu/config/gpu_finch_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu {
template <typename T>
void RunFunctor(T functor) {
functor();
}
template <typename T>
base::OnceClosure GetClosure(T functor) {
return base::BindOnce(&RunFunctor<T>, functor);
}
class SchedulerTest : public base::test::WithFeatureOverride,
public testing::Test {
public:
SchedulerTest()
: base::test::WithFeatureOverride(features::kSyncPointGraphValidation),
task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
scheduler_(&sync_point_manager_) {
CHECK_EQ(GetParam(), sync_point_manager_.graph_validation_enabled());
}
protected:
SyncPointManager* sync_point_manager() { return &sync_point_manager_; }
Scheduler* scheduler() { return &scheduler_; }
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner() {
return base::SingleThreadTaskRunner::GetCurrentDefault();
}
bool graph_validation_enabled() const {
return sync_point_manager_.graph_validation_enabled();
}
void RunAllPendingTasks() {
base::RunLoop run_loop;
SequenceId sequence_id =
scheduler()->CreateSequence(SchedulingPriority::kLow, task_runner());
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id, run_loop.QuitClosure(), std::vector<SyncToken>()));
run_loop.Run();
scheduler()->DestroySequence(sequence_id);
}
base::test::SingleThreadTaskEnvironment task_environment_;
private:
SyncPointManager sync_point_manager_;
Scheduler scheduler_;
};
TEST_P(SchedulerTest, ScheduledTasksRunInOrder) {
SequenceId sequence_id =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
int count = 0;
int ran1 = 0;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id,
GetClosure([&] { ran1 = ++count; }),
std::vector<SyncToken>()));
int ran2 = 0;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id,
GetClosure([&] { ran2 = ++count; }),
std::vector<SyncToken>()));
base::RunLoop run_loop;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id, run_loop.QuitClosure(),
std::vector<SyncToken>()));
run_loop.Run();
EXPECT_EQ(ran1, 1);
EXPECT_EQ(ran2, 2);
scheduler()->DestroySequence(sequence_id);
}
TEST_P(SchedulerTest, ScheduledTasksRunAfterReporting) {
SequenceId sequence_id =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
bool ran = false;
bool reported = false;
scheduler()->ScheduleTask(
Scheduler::Task(sequence_id, GetClosure([&] {
EXPECT_TRUE(reported);
ran = true;
}),
std::vector<SyncToken>(),
base::BindOnce(
[&](bool& ran, bool& reported, base::TimeTicks t) {
EXPECT_FALSE(ran);
reported = true;
},
std::ref(ran), std::ref(reported))));
base::RunLoop run_loop;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id, run_loop.QuitClosure(),
std::vector<SyncToken>()));
run_loop.Run();
EXPECT_TRUE(ran);
scheduler()->DestroySequence(sequence_id);
}
TEST_P(SchedulerTest, ContinuedTasksRunFirst) {
SequenceId sequence_id =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
int count = 0;
int ran1 = 0;
int continued1 = 0;
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id, GetClosure([&] {
scheduler()->ContinueTask(sequence_id,
GetClosure([&] { continued1 = ++count; }));
ran1 = ++count;
}),
std::vector<SyncToken>()));
int ran2 = 0;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id,
GetClosure([&] { ran2 = ++count; }),
std::vector<SyncToken>()));
base::RunLoop run_loop;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id, run_loop.QuitClosure(),
std::vector<SyncToken>()));
run_loop.Run();
EXPECT_EQ(ran1, 1);
EXPECT_EQ(continued1, 2);
EXPECT_EQ(ran2, 3);
scheduler()->DestroySequence(sequence_id);
}
class SchedulerTaskRunOrderTest : public SchedulerTest {
public:
SchedulerTaskRunOrderTest() = default;
~SchedulerTaskRunOrderTest() override {
while (!sequence_info_.empty()) {
DestroySequence(sequence_info_.begin()->first);
}
}
protected:
void CreateSequence(int sequence_key, SchedulingPriority priority) {
CommandBufferId command_buffer_id =
CommandBufferId::FromUnsafeValue(sequence_key);
SequenceId sequence_id = scheduler()->CreateSequence(
priority, task_runner(), kNamespaceId, command_buffer_id);
sequence_info_.emplace(sequence_key,
SequenceInfo(sequence_id, command_buffer_id));
}
void CreateExternalSequence(int sequence_key) {
auto order_data = sync_point_manager()->CreateSyncPointOrderData();
auto command_buffer_id = CommandBufferId::FromUnsafeValue(sequence_key);
auto release_state = sync_point_manager()->CreateSyncPointClientState(
kNamespaceId, command_buffer_id, order_data->sequence_id());
sequence_info_.emplace(
sequence_key,
SequenceInfo(std::move(order_data), command_buffer_id, release_state));
}
void DestroySequence(int sequence_key) {
auto info_it = sequence_info_.find(sequence_key);
ASSERT_TRUE(info_it != sequence_info_.end());
if (info_it->second.order_data) {
info_it->second.release_state->Destroy();
info_it->second.order_data->Destroy();
} else {
scheduler()->DestroySequence(info_it->second.sequence_id);
}
sequence_info_.erase(info_it);
}
void CreateSyncToken(int sequence_key, int release_sync) {
auto info_it = sequence_info_.find(sequence_key);
ASSERT_TRUE(info_it != sequence_info_.end());
uint64_t release = release_sync + 1;
sync_tokens_.emplace(
release_sync,
SyncToken(kNamespaceId, info_it->second.command_buffer_id, release));
}
TaskCallback GetTaskCallback(int sequence_key, int release_sync) {
const int task_id = num_tasks_scheduled_++;
if (release_sync >= 0) {
CreateSyncToken(sequence_key, release_sync);
}
auto info_it = sequence_info_.find(sequence_key);
CHECK(info_it != sequence_info_.end());
return base::BindLambdaForTesting(
[this, task_id](FenceSyncReleaseDelegate* release_delegate) {
if (release_delegate) {
release_delegate->Release();
}
tasks_executed_.push_back(task_id);
});
}
base::OnceClosure GetExternalTaskClosure(int sequence_key, int release_sync) {
const int task_id = num_tasks_scheduled_++;
if (release_sync >= 0) {
CreateSyncToken(sequence_key, release_sync);
}
auto info_it = sequence_info_.find(sequence_key);
CHECK(info_it != sequence_info_.end());
CHECK(info_it->second.external());
// Simulate external sequence, when tasks are run outside of this
// gpu::Scheduler
auto order_data = info_it->second.order_data;
uint32_t order_num = order_data->GenerateUnprocessedOrderNumber();
return GetClosure([this, task_id, sequence_key, release_sync, order_num] {
auto info_it = sequence_info_.find(sequence_key);
ASSERT_TRUE(info_it != sequence_info_.end());
info_it->second.order_data->BeginProcessingOrderNumber(order_num);
if (release_sync >= 0) {
sync_point_manager()->EnsureFenceSyncReleased(
sync_tokens_[release_sync], ReleaseCause::kExplicitClientRelease);
}
this->tasks_executed_.push_back(task_id);
info_it->second.order_data->FinishProcessingOrderNumber(order_num);
});
}
void ScheduleTask(int sequence_key, int wait_sync, int release_sync) {
ScheduleTask(sequence_key, std::vector<int>{wait_sync}, release_sync);
}
void ScheduleTask(int sequence_key,
const std::vector<int>& wait_syncs,
int release_sync) {
auto task_callback = GetTaskCallback(sequence_key, release_sync);
auto info_it = sequence_info_.find(sequence_key);
ASSERT_TRUE(info_it != sequence_info_.end());
DCHECK(!info_it->second.external());
std::vector<SyncToken> waits;
for (int wait_sync : wait_syncs) {
if (wait_sync >= 0) {
waits.push_back(sync_tokens_[wait_sync]);
}
}
SyncToken release;
if (release_sync >= 0) {
release = sync_tokens_[release_sync];
}
scheduler()->ScheduleTask(Scheduler::Task(
info_it->second.sequence_id, std::move(task_callback), waits, release));
}
const std::vector<int>& tasks_executed() { return tasks_executed_; }
base::SingleThreadTaskRunner* GetTaskRunnerFromSequence(int sequence_key) {
auto info_it = sequence_info_.find(sequence_key);
if (info_it == sequence_info_.end())
return nullptr;
return scheduler()->GetTaskRunnerForTesting(info_it->second.sequence_id);
}
private:
const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO;
int num_tasks_scheduled_ = 0;
struct SequenceInfo {
SequenceInfo(SequenceId sequence_id, CommandBufferId command_buffer_id)
: sequence_id(sequence_id), command_buffer_id(command_buffer_id) {}
SequenceInfo(scoped_refptr<SyncPointOrderData> order_data,
CommandBufferId command_buffer_id,
scoped_refptr<SyncPointClientState> release_state)
: sequence_id(order_data->sequence_id()),
command_buffer_id(command_buffer_id),
order_data(order_data),
release_state(release_state) {}
bool external() const { return !!order_data; }
SequenceId sequence_id;
CommandBufferId command_buffer_id;
// `order_data` and `release_state` are only set for external sequences.
scoped_refptr<SyncPointOrderData> order_data;
scoped_refptr<SyncPointClientState> release_state;
};
std::map<int, const SequenceInfo> sequence_info_;
std::map<int, const SyncToken> sync_tokens_;
std::vector<int> tasks_executed_;
};
TEST_P(SchedulerTaskRunOrderTest, SequencesRunInPriorityOrder) {
CreateSequence(0, SchedulingPriority::kLow);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSequence(2, SchedulingPriority::kHigh);
ScheduleTask(0, -1, -1); // task 0: seq 0, no wait, no release
ScheduleTask(1, -1, -1); // task 1: seq 1, no wait, no release
ScheduleTask(2, -1, -1); // task 2: seq 2, no wait, no release
RunAllPendingTasks();
const int expected_task_order[] = {2, 1, 0};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, SequencesOfSamePriorityRunInOrder) {
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSequence(2, SchedulingPriority::kNormal);
CreateSequence(3, SchedulingPriority::kNormal);
ScheduleTask(0, -1, -1); // task 0: seq 0, no wait, no release
ScheduleTask(1, -1, -1); // task 1: seq 1, no wait, no release
ScheduleTask(2, -1, -1); // task 2: seq 2, no wait, no release
ScheduleTask(3, -1, -1); // task 3: seq 2, no wait, no release
RunAllPendingTasks();
const int expected_task_order[] = {0, 1, 2, 3};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, SequenceWaitsForFence) {
CreateSequence(0, SchedulingPriority::kHigh);
CreateSequence(1, SchedulingPriority::kNormal);
ScheduleTask(1, -1, 0); // task 0: seq 1, no wait, release 0
ScheduleTask(0, 0, -1); // task 1: seq 0, wait 0, no release
RunAllPendingTasks();
const int expected_task_order[] = {0, 1};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, SequenceWaitsForFenceExternal) {
CreateSequence(0, SchedulingPriority::kHigh);
CreateExternalSequence(1);
// Create task 0 on seq 1 that will release 0, but don't post it.
auto external_task = GetExternalTaskClosure(1, 0);
ScheduleTask(0, 0, -1); // task 1: seq 0, wait 0, no release
// task runner for all the sequences created here from same thread is same.
// only sequences created on different threads have different task runner.
GetTaskRunnerFromSequence(0)->PostTask(FROM_HERE, std::move(external_task));
RunAllPendingTasks();
const int expected_task_order[] = {0, 1};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, WaitOrderNumSmallerThanReleaseOrderNum) {
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSyncToken(1, 0); // declare sync_token 0 on seq 1
ScheduleTask(0, 0, -1); // task 0: seq 0, wait 0, no release
ScheduleTask(1, -1, 0); // task 1: seq 1, no wait, release 0
RunAllPendingTasks();
std::vector<int> expected_task_order;
if (!graph_validation_enabled()) {
// In this mode, the wait order number must be larger than the corresponding
// release number. The wait of task 0 is considered invalid.
// Task 0 does not wait on unrelease sync token 0.
expected_task_order = {0, 1};
} else {
// In this mode, there is no requirement that the wait order number is
// larger than the corresponding release number, so task 0 waits on task 1
// to release the sync token.
expected_task_order = {1, 0};
}
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
// Tests that Scheduler::RebuildSchedulingQueueIfNeeded inserts all non-running
// sequences into the queue - even if a sequence is completely blocked.
TEST_P(SchedulerTaskRunOrderTest, SchedulingQueueContainsBlockedSequences) {
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kLow);
CreateSequence(2, SchedulingPriority::kHigh);
ScheduleTask(0, -1, -1); // task 0: seq 0, no wait, no release
ScheduleTask(1, -1, 0); // task 1: seq 1, no wait, release 0
ScheduleTask(2, 0, -1); // task 2: seq 2, wait 0, no release
RunAllPendingTasks();
const int expected_task_order[] = {1, 2, 0};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, ReleaseSequenceHasPriorityOfWaiter) {
CreateSequence(0, SchedulingPriority::kLow);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSequence(2, SchedulingPriority::kHigh);
ScheduleTask(0, -1, 0); // task 0: seq 0, no wait, release 0
ScheduleTask(1, 0, -1); // task 1: seq 1, wait 0, no release
ScheduleTask(2, -1, -1); // task 2: seq 2, no wait, no release
RunAllPendingTasks();
const int expected_task_order[] = {2, 0, 1};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, ReleaseSequenceRevertsToDefaultPriority) {
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kLow);
CreateSequence(2, SchedulingPriority::kHigh);
ScheduleTask(0, -1, -1); // task 0: seq 0, no wait, no release
ScheduleTask(1, -1, 0); // task 1: seq 1, no wait, release 0
ScheduleTask(2, 0, -1); // task 2: seq 2, wait 0, no release
DestroySequence(2);
RunAllPendingTasks();
const int expected_task_order[] = {0, 1};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, ReleaseSequenceCircularRelease) {
CreateSequence(0, SchedulingPriority::kLow);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSequence(2, SchedulingPriority::kHigh);
ScheduleTask(0, -1, -1); // task 0: seq 0, no wait, no release
ScheduleTask(1, -1, -1); // task 1: seq 1, no wait, no release
ScheduleTask(2, -1, -1); // task 2: seq 2, no wait, no release
ScheduleTask(0, -1, 0); // task 3: seq 0, no wait, release 0
ScheduleTask(0, -1, -1); // task 4: seq 0, no wait, no release
ScheduleTask(1, 0, 1); // task 5: seq 1, wait 0, release 1
ScheduleTask(1, -1, -1); // task 6: seq 1, no wait, no release
ScheduleTask(2, 1, 2); // task 7: seq 2, wait 1, release 2
ScheduleTask(2, -1, -1); // task 8: seq 2, no wait, no release
ScheduleTask(0, 2, 3); // task 9: seq 0, wait 2, releases 3
ScheduleTask(1, 3, 4); // task 10: seq 1, wait 3, releases 4
ScheduleTask(2, 4, -1); // task 11: seq 2, wait 4, no release
ScheduleTask(0, -1, -1); // task 12: seq 0, no wait, no release
ScheduleTask(1, -1, -1); // task 13: seq 1, no wait, no release
ScheduleTask(2, -1, -1); // task 14: seq 2, no wait, no release
RunAllPendingTasks();
// Below is the job graph implied by the above code. The scheduler traverses
// the graph using DFS. At each node, it visits the highest descendent whose
// predecessors have all been visited. The traversal for a path stops if there
// are no such descendents. It then continues from the first ancestor that has
// a valid descendent.
/*
┌────────────────┐
│task 2 │
└┬──────────────┬┘
┌▽─────────┐ │
│task 1 │ │
└┬─────────┬┘ │
┌▽────┐ │ │
│task 0│ │ │
└┬─────┘ │ │
┌▽───────┐│ │
│task 3 ││ │
└┬───────┬┘│ │
┌▽────┐┌▽▽──┐│
│task 4││task 5││
└┬─────┘└┬───┬─┘│
│┌─────▽─┐┌▽─▽─┐
││task 6 ││task 7│
│└┬───────┘└┬─┬───┘
┌│─┘ │ │
│└┐ ┌─────┘ │
│┌▽──▽┐┌────▽┐
││task 9││task 8│
│└────┬┬┘└─────┬┘
│ │└───┐ │
└─────│───┐│ └──┐
┌────▽─┐┌▽▽───┐│
│task 12││task 10││
└───────┘└┬──┬───┘│
┌────────▽┐┌▽──▽─┐
│task 13 ││task 11│
└──────────┘└┬──────┘
┌───────────▽┐
│task 14 │
└─────────────┘
*/
const int expected_task_order[] = {2, 1, 0, 3, 5, 7, 8, 6,
4, 9, 10, 11, 14, 13, 12};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTaskRunOrderTest, WaitOnSelfShouldNotBlockSequence) {
CreateSequence(0, SchedulingPriority::kHigh);
CreateSyncToken(0, 0); // declare sync_token 0 on seq 1
// Dummy order number to avoid the wait_order_num <= processed_order_num + 1
// check in SyncPointOrderData::ValidateReleaseOrderNum.
sync_point_manager()->GenerateOrderNumber();
ScheduleTask(0, 0, -1); // task 0: seq 0, wait 0, no release
RunAllPendingTasks();
const int expected_task_order[] = {0};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerTest, ShouldNotYieldWhenNoTasksToRun) {
CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
CommandBufferId command_buffer_id = CommandBufferId::FromUnsafeValue(1);
SequenceId sequence_id1 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner(),
namespace_id, command_buffer_id);
SyncToken sync_token(namespace_id, command_buffer_id, 1);
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id1,
GetClosure([&] { EXPECT_FALSE(scheduler()->ShouldYield(sequence_id1)); }),
std::vector<SyncToken>(), sync_token));
// Schedule a task on another sequence that depends on the above task.
// ShouldYield should return false because the sequence below isn't runnable
// until the above task completes.
SequenceId sequence_id2 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
scheduler()->ScheduleTask(
Scheduler::Task(sequence_id2, GetClosure([] {}), {sync_token}));
RunAllPendingTasks();
scheduler()->DestroySequence(sequence_id1);
scheduler()->DestroySequence(sequence_id2);
}
TEST_P(SchedulerTest, ReleaseSequenceShouldYield) {
CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
CommandBufferId command_buffer_id = CommandBufferId::FromUnsafeValue(1);
SequenceId sequence_id1 = scheduler()->CreateSequence(
SchedulingPriority::kLow, task_runner(), namespace_id, command_buffer_id);
SyncToken sync_token(namespace_id, command_buffer_id, 1);
int count = 0;
int ran1 = 0;
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id1,
base::BindLambdaForTesting(
[&](FenceSyncReleaseDelegate* release_delegate) {
EXPECT_FALSE(scheduler()->ShouldYield(sequence_id1));
release_delegate->Release();
EXPECT_TRUE(scheduler()->ShouldYield(sequence_id1));
ran1 = ++count;
}),
std::vector<SyncToken>(), sync_token));
int ran2 = 0;
SequenceId sequence_id2 =
scheduler()->CreateSequence(SchedulingPriority::kHigh, task_runner());
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id2, GetClosure([&] { ran2 = ++count; }), {sync_token}));
RunAllPendingTasks();
EXPECT_EQ(ran1, 1);
EXPECT_EQ(ran2, 2);
EXPECT_TRUE(sync_point_manager()->IsSyncTokenReleased(sync_token));
scheduler()->DestroySequence(sequence_id1);
scheduler()->DestroySequence(sequence_id2);
}
// Tests a situation where a sequence's WaitFence has an order number less than
// the sequence's first order number, because the sequence is currently running,
// and called ShouldYield before release the WaitFence.
TEST_P(SchedulerTest, ShouldYieldIsValidWhenSequenceReleaseIsPending) {
CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
SequenceId sequence_id1 =
scheduler()->CreateSequence(SchedulingPriority::kHigh, task_runner(),
namespace_id, command_buffer_id1);
CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
SequenceId sequence_id2 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner(),
namespace_id, command_buffer_id2);
SyncToken sync_token1(namespace_id, command_buffer_id1, 1);
SyncToken sync_token2(namespace_id, command_buffer_id2, 2);
// Job 1.1 doesn't depend on anything.
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id1,
GetClosure([&] { EXPECT_FALSE(scheduler()->ShouldYield(sequence_id1)); }),
{}, sync_token1));
// Job 2.1 depends on Job 1.1.
scheduler()->ScheduleTask(Scheduler::Task(sequence_id2, GetClosure([&] {}),
{sync_token1}, sync_token2));
// Job 1.2 depends on Job 2.1.
scheduler()->ScheduleTask(
Scheduler::Task(sequence_id1, GetClosure([&] {}), {sync_token2}));
RunAllPendingTasks();
scheduler()->DestroySequence(sequence_id1);
scheduler()->DestroySequence(sequence_id2);
}
TEST_P(SchedulerTest, ReentrantEnableSequenceShouldNotDeadlock) {
SequenceId sequence_id1 =
scheduler()->CreateSequence(SchedulingPriority::kHigh, task_runner());
CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
scoped_refptr<SyncPointClientState> release_state1 =
sync_point_manager()->CreateSyncPointClientState(
namespace_id, command_buffer_id1, sequence_id1);
SequenceId sequence_id2 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
auto scoped_release_state2 = scheduler()->CreateSyncPointClientState(
sequence_id2, namespace_id, command_buffer_id2);
uint64_t release = 1;
SyncToken sync_token(namespace_id, command_buffer_id2, release);
int count = 0;
int ran1, ran2 = 0;
// Schedule task on sequence 2 first so that the sync token wait isn't a nop.
// BeginProcessingOrderNumber for this task will run the EnableSequence
// callback. This should not deadlock.
scheduler()->ScheduleTask(Scheduler::Task(sequence_id2,
GetClosure([&] { ran2 = ++count; }),
std::vector<SyncToken>()));
// This will run first because of the higher priority and no scheduling sync
// token dependencies.
scheduler()->ScheduleTask(Scheduler::Task(
sequence_id1, GetClosure([&] {
ran1 = ++count;
sync_point_manager()->Wait(
sync_token, sequence_id1,
sync_point_manager()->GenerateOrderNumber(),
base::BindOnce(&Scheduler::EnableSequence,
base::Unretained(scheduler()), sequence_id1));
scheduler()->DisableSequence(sequence_id1);
}),
std::vector<SyncToken>()));
RunAllPendingTasks();
EXPECT_EQ(ran1, 1);
EXPECT_EQ(ran2, 2);
EXPECT_FALSE(sync_point_manager()->IsSyncTokenReleased(sync_token));
release_state1->Destroy();
scoped_release_state2.Reset();
scheduler()->DestroySequence(sequence_id1);
scheduler()->DestroySequence(sequence_id2);
}
TEST_P(SchedulerTest, CanSetSequencePriority) {
SequenceId sequence_id1 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
SequenceId sequence_id2 =
scheduler()->CreateSequence(SchedulingPriority::kLow, task_runner());
SequenceId sequence_id3 =
scheduler()->CreateSequence(SchedulingPriority::kHigh, task_runner());
int count = 0;
int ran1 = 0, ran2 = 0, ran3 = 0;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id1,
GetClosure([&] { ran1 = ++count; }),
std::vector<SyncToken>()));
scheduler()->ScheduleTask(Scheduler::Task(sequence_id2,
GetClosure([&] { ran2 = ++count; }),
std::vector<SyncToken>()));
scheduler()->ScheduleTask(Scheduler::Task(sequence_id3,
GetClosure([&] { ran3 = ++count; }),
std::vector<SyncToken>()));
scheduler()->SetSequencePriority(sequence_id2, SchedulingPriority::kHigh);
RunAllPendingTasks();
EXPECT_EQ(ran2, 1);
EXPECT_EQ(ran3, 2);
EXPECT_EQ(ran1, 3);
ran1 = ran2 = ran3 = 0;
scheduler()->ScheduleTask(Scheduler::Task(sequence_id1,
GetClosure([&] { ran1 = ++count; }),
std::vector<SyncToken>()));
scheduler()->ScheduleTask(Scheduler::Task(sequence_id2,
GetClosure([&] { ran2 = ++count; }),
std::vector<SyncToken>()));
scheduler()->ScheduleTask(Scheduler::Task(sequence_id3,
GetClosure([&] { ran3 = ++count; }),
std::vector<SyncToken>()));
scheduler()->SetSequencePriority(
sequence_id2, scheduler()->GetSequenceDefaultPriority(sequence_id2));
RunAllPendingTasks();
EXPECT_EQ(ran3, 4);
EXPECT_EQ(ran1, 5);
EXPECT_EQ(ran2, 6);
scheduler()->DestroySequence(sequence_id1);
scheduler()->DestroySequence(sequence_id2);
scheduler()->DestroySequence(sequence_id3);
}
TEST_P(SchedulerTest, StreamPriorities) {
SequenceId seq_id1 =
scheduler()->CreateSequence(SchedulingPriority::kLow, task_runner());
SequenceId seq_id2 =
scheduler()->CreateSequence(SchedulingPriority::kNormal, task_runner());
SequenceId seq_id3 =
scheduler()->CreateSequence(SchedulingPriority::kHigh, task_runner());
CommandBufferNamespace namespace_id = CommandBufferNamespace::GPU_IO;
CommandBufferId command_buffer_id1 = CommandBufferId::FromUnsafeValue(1);
CommandBufferId command_buffer_id2 = CommandBufferId::FromUnsafeValue(2);
{
base::AutoLock auto_lock(scheduler()->lock());
Scheduler::Sequence* seq1 = scheduler()->GetSequence(seq_id1);
Scheduler::Sequence* seq2 = scheduler()->GetSequence(seq_id2);
Scheduler::Sequence* seq3 = scheduler()->GetSequence(seq_id3);
// Initial priorities.
EXPECT_EQ(SchedulingPriority::kLow, seq1->current_priority());
EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
SyncToken sync_token1(namespace_id, command_buffer_id1, 1);
SyncToken sync_token2(namespace_id, command_buffer_id2, 1);
// Make sure that waiting for fences does not change sequence priorities.
seq2->AddTask(base::OnceClosure(), {sync_token1}, /*release=*/{},
/*report_callback=*/{});
seq3->AddTask(base::OnceClosure(), {sync_token2}, /*release=*/{},
/*report_callback=*/{});
EXPECT_EQ(SchedulingPriority::kLow, seq1->current_priority());
EXPECT_EQ(SchedulingPriority::kNormal, seq2->current_priority());
EXPECT_EQ(SchedulingPriority::kHigh, seq3->current_priority());
}
scheduler()->DestroySequence(seq_id1);
scheduler()->DestroySequence(seq_id2);
scheduler()->DestroySequence(seq_id3);
}
// Tests Scheduler behavior when graph validation of sync points is enabled.
// The tests verify that the integration with TaskGraph works properly. More
// comprehensive testing of validation behavior is done in
// task_graph_unittest.cc.
class SchedulerGraphValidationTest : public SchedulerTaskRunOrderTest {
public:
SchedulerGraphValidationTest() = default;
~SchedulerGraphValidationTest() override = default;
protected:
void SetUp() override {
SchedulerTaskRunOrderTest::SetUp();
CHECK(graph_validation_enabled());
}
};
TEST_P(SchedulerGraphValidationTest, ValidationWaitWithoutRelease) {
// Two tasks on the same sequence wait for unreleased fences.
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSequence(2, SchedulingPriority::kNormal);
CreateSyncToken(1, 0); // declare sync_token 0 on seq 1
CreateSyncToken(1, 1); // declare sync_token 1 on seq 1
CreateSyncToken(2, 2); // declare sync_token 2 on seq 2
CreateSyncToken(2, 3); // declare sync_token 3 on seq 2
ScheduleTask(0, {0, 3}, -1); // task 0: seq 0, wait {0,3}, no release
RunAllPendingTasks();
EXPECT_TRUE(tasks_executed().empty());
// Submit a task close to the time when the validation timer will be fired.
task_environment_.FastForwardBy(TaskGraph::kMaxValidationDelay -
TaskGraph::kMinValidationDelay +
base::Seconds(1));
ScheduleTask(0, {1, 2}, -1); // task 1: seq 0, wait {1,2}, no release
// Cause the validation timer to fire.
task_environment_.FastForwardBy(TaskGraph::kMinValidationDelay);
RunAllPendingTasks();
// Only task 0 is supposed to be executed.
// Task 1 has sync_token 1 that is not satisfied. And it is too new to be
// validated.
std::vector<int> expected_task_order = {0};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
// The validation timer should be fired again and resolve the invalid wait
// of task 1.
task_environment_.FastForwardBy(TaskGraph::kMaxValidationDelay +
base::Seconds(1));
RunAllPendingTasks();
expected_task_order = {0, 1};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
TEST_P(SchedulerGraphValidationTest, ValidationCircularWaits) {
// Task 0 waits for task 1; while task 1 waits for task 2:
//
// seq 0 seq 1
// | | | |
// |(task 0)|---->|(task 1)|
// | | /| |
// |(task 2)|<--/ | |
// | | | |
CreateSequence(0, SchedulingPriority::kNormal);
CreateSequence(1, SchedulingPriority::kNormal);
CreateSyncToken(1, 0); // declare sync_token 0 on seq 1
CreateSyncToken(0, 1); // declare sync_token 1 on seq 0
ScheduleTask(0, 0, -1); // task 0: seq 0, wait 0, no release
// Submit task 1 on sequence 1 later. Validation on sequence 0 will be
// triggered first.
task_environment_.FastForwardBy(TaskGraph::kMaxValidationDelay -
base::Seconds(1));
ScheduleTask(1, 1, 0); // task 1: seq 1, wait 1, release 0
ScheduleTask(0, -1, 1); // task 2: seq 0, no wait, release 1
RunAllPendingTasks();
EXPECT_TRUE(tasks_executed().empty());
// Trigger validation on sequence 0.
task_environment_.FastForwardBy(base::Seconds(2));
RunAllPendingTasks();
std::vector<int> expected_task_order{1, 0, 2};
EXPECT_THAT(tasks_executed(), testing::ElementsAreArray(expected_task_order));
}
INSTANTIATE_TEST_SUITE_P(All, SchedulerTest, testing::Values(false, true));
INSTANTIATE_TEST_SUITE_P(All,
SchedulerTaskRunOrderTest,
testing::Values(false, true));
// Only test the case of IsSyncPointGraphValidationEnabled() being true.
INSTANTIATE_TEST_SUITE_P(All,
SchedulerGraphValidationTest,
testing::Values(true));
} // namespace gpu