blob: fcb048ebbcbe68ea16f4a33261ad99b91fd1a405 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/cache_storage/cache_storage_scheduler.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace cache_storage_scheduler_unittest {
class TestTask {
public:
TestTask(CacheStorageScheduler* scheduler)
: scheduler_(scheduler),
id_(scheduler_->CreateId()),
callback_count_(0) {}
virtual void Run() {
callback_count_++;
run_loop_.Quit();
}
void Done() { scheduler_->CompleteOperationAndRunNext(id_); }
int callback_count() const { return callback_count_; }
CacheStorageSchedulerId id() const { return id_; }
base::RunLoop& run_loop() { return run_loop_; }
protected:
CacheStorageScheduler* scheduler_;
const CacheStorageSchedulerId id_;
base::RunLoop run_loop_;
int callback_count_;
};
class TestScheduler : public CacheStorageScheduler {
public:
TestScheduler()
: CacheStorageScheduler(CacheStorageSchedulerClient::kStorage,
base::ThreadTaskRunnerHandle::Get()) {}
void SetDoneStartingClosure(base::OnceClosure done_closure) {
CHECK(!done_closure_);
done_closure_ = std::move(done_closure);
}
protected:
void DoneStartingAvailableOperations() override {
if (done_closure_) {
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(done_closure_));
}
CacheStorageScheduler::DoneStartingAvailableOperations();
}
base::OnceClosure done_closure_;
};
class CacheStorageSchedulerTest : public testing::Test {
protected:
CacheStorageSchedulerTest()
: task_environment_(BrowserTaskEnvironment::IO_MAINLOOP),
task1_(&scheduler_),
task2_(&scheduler_),
task3_(&scheduler_) {}
BrowserTaskEnvironment task_environment_;
TestScheduler scheduler_;
TestTask task1_;
TestTask task2_;
TestTask task3_;
};
TEST_F(CacheStorageSchedulerTest, ScheduleOne) {
base::RunLoop done_loop;
scheduler_.SetDoneStartingClosure(done_loop.QuitClosure());
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
task1_.run_loop().Run();
done_loop.Run();
EXPECT_EQ(1, task1_.callback_count());
}
TEST_F(CacheStorageSchedulerTest, ScheduledOperations) {
base::RunLoop done_loop;
scheduler_.SetDoneStartingClosure(done_loop.QuitClosure());
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
EXPECT_TRUE(scheduler_.ScheduledOperations());
task1_.run_loop().Run();
done_loop.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_TRUE(scheduler_.ScheduledOperations());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
task1_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
}
TEST_F(CacheStorageSchedulerTest, ScheduleTwoExclusive) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
// Should only run the first exclusive op.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the second exclusive op after the first completes.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
}
TEST_F(CacheStorageSchedulerTest, ScheduleTwoShared) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
// Should run both shared ops in paralle.
task1_.run_loop().Run();
task2_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Completing the first op should trigger a check for new ops
// which will not be present here.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop3;
scheduler_.SetDoneStartingClosure(done_loop3.QuitClosure());
// Completing the second op should result in the scheduler
// becoming idle.
task2_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
done_loop3.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
}
TEST_F(CacheStorageSchedulerTest, ScheduleOneExclusiveOneShared) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
// Should only run the first exclusive op.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the second shared op after the first is completed.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
task2_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
}
TEST_F(CacheStorageSchedulerTest, ScheduleOneSharedOneExclusive) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
// Should only run the first shared op.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the second exclusive op after the first completes.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
task2_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
}
TEST_F(CacheStorageSchedulerTest, ScheduleTwoSharedOneExclusive) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task3_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task3_)));
// Should run the two shared ops in parallel.
task1_.run_loop().Run();
task2_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Completing the first shared op should not allow the exclusive op
// to run yet.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop3;
scheduler_.SetDoneStartingClosure(done_loop3.QuitClosure());
// The third exclusive op should run after both the preceding shared ops
// complete.
task2_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task3_.run_loop().Run();
done_loop3.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
task3_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
}
TEST_F(CacheStorageSchedulerTest, ScheduleOneExclusiveTwoShared) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task3_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task3_)));
// Should only run the first exclusive op.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run both the shared ops in parallel after the first exclusive
// op is completed.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
task3_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop3;
scheduler_.SetDoneStartingClosure(done_loop3.QuitClosure());
task2_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
done_loop3.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
task3_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
}
TEST_F(CacheStorageSchedulerTest, ScheduleOneSharedOneExclusiveOneShared) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "3"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task3_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task3_)));
// Should only run the first shared op.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the exclusive op after the first op is completed.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
EXPECT_TRUE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop3;
scheduler_.SetDoneStartingClosure(done_loop3.QuitClosure());
// Should run the last shared op after the preceding exclusive op
// is completed.
task2_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task3_.run_loop().Run();
done_loop3.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
task3_.Done();
EXPECT_FALSE(scheduler_.ScheduledOperations());
}
TEST_F(CacheStorageSchedulerTest, ScheduleTwoSharedNotParallel) {
// Disable parallelism
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kCacheStorageParallelOps, {{"max_shared_ops", "1"}});
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kShared,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
// Should only run one shared op since the max shared is set to 1.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the next shared op after the first completes.
task1_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_FALSE(scheduler_.IsRunningExclusiveOperation());
}
TEST_F(CacheStorageSchedulerTest, ScheduleByPriorityTwoNormalOneHigh) {
scheduler_.ScheduleOperation(
task1_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task1_)));
base::RunLoop done_loop1;
scheduler_.SetDoneStartingClosure(done_loop1.QuitClosure());
scheduler_.ScheduleOperation(
task2_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kNormal,
base::BindOnce(&TestTask::Run, base::Unretained(&task2_)));
scheduler_.ScheduleOperation(
task3_.id(), CacheStorageSchedulerMode::kExclusive,
CacheStorageSchedulerOp::kTest, CacheStorageSchedulerPriority::kHigh,
base::BindOnce(&TestTask::Run, base::Unretained(&task3_)));
// Should run the first normal priority op because the queue was empty
// when it was added.
task1_.run_loop().Run();
done_loop1.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_EQ(0, task3_.callback_count());
base::RunLoop done_loop3;
scheduler_.SetDoneStartingClosure(done_loop3.QuitClosure());
// Should run the high priority op next.
task1_.Done();
task3_.run_loop().Run();
done_loop3.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(0, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
base::RunLoop done_loop2;
scheduler_.SetDoneStartingClosure(done_loop2.QuitClosure());
// Should run the final normal priority op after the high priority op
// completes.
task3_.Done();
EXPECT_TRUE(scheduler_.ScheduledOperations());
task2_.run_loop().Run();
done_loop2.Run();
EXPECT_EQ(1, task1_.callback_count());
EXPECT_EQ(1, task2_.callback_count());
EXPECT_EQ(1, task3_.callback_count());
}
} // namespace cache_storage_scheduler_unittest
} // namespace content