Usage table LRU only retrieves a single entry.

[ Merge of http://go/wvgerrit/102167 ]

After changes made to how the usage table is defragged by the CDM,
it was determined that there is no use in selecting more than a single
entry from the usage table to evict.  The only failure that can occur
when evicting an entry is if the last entry is in use, in that case,
evicting other entries will still result in a failure.

This change cleans up the LRU algorithm and test cases to reflect
the new functionality.

Bug: 155230578
Test: Linux unit tests
Change-Id: I817c039670d9f72c0e4f6c3fdac45c98ed5b6b21
This commit is contained in:
Alex Dale
2020-07-06 14:27:00 -07:00
parent 1d136b4f94
commit 7ce8950c0f
3 changed files with 214 additions and 352 deletions

View File

@@ -3541,38 +3541,16 @@ TEST_F(UsageTableHeaderTest, LruLastUsedTime_LoadEntry) {
// operations.
TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) {
constexpr size_t kUnexpiredThreshold = 50; // Arbitrary
constexpr size_t kRemovalCount = 3; // Also artbirary
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
std::vector<uint32_t> removal_candidates;
uint32_t entry_to_remove = 0;
// Empty list.
EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount,
&removal_candidates));
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold,
&entry_to_remove));
// Output is null.
usage_entry_info_list = kUpgradedUsageEntryInfoList;
EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount,
nullptr));
// Zero size requests.
EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, 0,
&removal_candidates));
// Request more than is available. Not invalid, but an unlikely use
// case.
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold,
/* removal_count = */ usage_entry_info_list.size() * 2,
&removal_candidates));
// Only unexpired offline license, but threshold is not met.
usage_entry_info_list.resize(kRemovalCount);
for (size_t i = 0; i < kRemovalCount; ++i) {
usage_entry_info_list[i].storage_type = kStorageLicense;
usage_entry_info_list[i].last_use_time = kLruBaseTime;
usage_entry_info_list[i].offline_license_expiry_time = kLruBaseTime + 1;
}
EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, kRemovalCount,
&removal_candidates));
usage_entry_info_list, kLruBaseTime, kUnexpiredThreshold, nullptr));
}
// Check that the major priority buckets are respected.
@@ -3582,6 +3560,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_InvalidInput) {
TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) {
constexpr int64_t kOneDay = 24 * 60 * 60;
constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay;
constexpr uint32_t kInvalidEntry = 9999;
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
// Unexpired offline license.
@@ -3615,56 +3594,98 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) {
usage_entry_info_list.push_back(unknown_entry_info);
constexpr uint32_t unknown_entry_number = 3;
std::vector<uint32_t> removal_candidates;
// Case 1: If there is an entry with unknown storage type, it should
// be selected above any other entry.
uint32_t entry_to_remove = kInvalidEntry;
// Expect the unknown entry.
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3,
/* removal_count = */ 1, &removal_candidates));
const std::vector<uint32_t> unknown_entry_numbers = {unknown_entry_number};
EXPECT_THAT(removal_candidates, ContainerEq(unknown_entry_numbers));
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(unknown_entry_number, entry_to_remove);
usage_entry_info_list.pop_back(); // Removing unknown.
// Expect both expired offline and streaming license.
// Case 2a: Threshold not met, all entries are equally stale.
// The expired entry should be selected over the streaming license.
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3,
/* removal_count = */ 3, &removal_candidates));
const std::vector<uint32_t> expired_and_streaming_entry_numbers = {
expired_entry_number, streaming_entry_number};
EXPECT_THAT(removal_candidates,
ContainerEq(expired_and_streaming_entry_numbers));
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(expired_entry_number, entry_to_remove);
// With threshold met, expect all three.
// Case 2b: Threshold not met, streaming license is most stale.
usage_entry_info_list[streaming_entry_number].last_use_time--;
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0,
/* removal_count = */ 3, &removal_candidates));
const std::vector<uint32_t> all_license_entry_numbers = {
expired_entry_number, streaming_entry_number, unexpired_entry_number};
EXPECT_THAT(removal_candidates, ContainerEq(all_license_entry_numbers));
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(streaming_entry_number, entry_to_remove);
usage_entry_info_list.pop_back(); // Removing streaming.
usage_entry_info_list.pop_back(); // Removing expired offline.
// |usage_entry_info_list| only contains 1 expired and 1 unexpired offline
// license.
// Sanity check: while below threshold there will not be any possible
// candidates, expect failure. This is an unexpected case in normal
// operation.
EXPECT_FALSE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3,
/* removal_count = */ 1, &removal_candidates));
// Expect the unexpired license.
// Case 2c: Threshold met, equally stale entries. Expect the expired
// entry over the unexpired.
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0,
/* removal_count = */ 1, &removal_candidates));
const std::vector<uint32_t> unexpired_entry_numbers = {
unexpired_entry_number};
EXPECT_THAT(removal_candidates, ContainerEq(unexpired_entry_numbers));
/* unexpired_threshold = */ 0, &entry_to_remove));
EXPECT_EQ(expired_entry_number, entry_to_remove);
// Case 3a: Threshold not met, expired entry is the most stale.
entry_to_remove = kInvalidEntry;
usage_entry_info_list[expired_entry_number].last_use_time--;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(expired_entry_number, entry_to_remove);
// Case 3b: Threshold not met, unexpired entry is the most stale.
entry_to_remove = kInvalidEntry;
usage_entry_info_list[expired_entry_number].last_use_time++;
usage_entry_info_list[unexpired_entry_number].last_use_time--;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(expired_entry_number, entry_to_remove);
// Case 3c: Threshold met, unexpired entry is the most stale.
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0, &entry_to_remove));
EXPECT_EQ(unexpired_entry_number, entry_to_remove);
// Case 3d: Threshold met, expired entry is the most stale.
entry_to_remove = kInvalidEntry;
usage_entry_info_list[expired_entry_number].last_use_time--;
usage_entry_info_list[unexpired_entry_number].last_use_time++;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0, &entry_to_remove));
EXPECT_EQ(expired_entry_number, entry_to_remove);
usage_entry_info_list.pop_back(); // Removing expired offline.
// Case 4a: Threshold met, and only an unexpired offline license
// is available.
entry_to_remove = kInvalidEntry; // Invalidate value.
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0, &entry_to_remove));
EXPECT_EQ(unexpired_entry_number, entry_to_remove);
// Case 4b (stability check): Threshold not met, and only an
// unexpired offline license is available. This is an unexpected
// condition in normal operation, but the algorithm should still
// return an entry.
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 3, &entry_to_remove));
EXPECT_EQ(unexpired_entry_number, entry_to_remove);
}
// Testing algorithm with unexpired offline and streaming license. The
@@ -3673,6 +3694,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_BasicPriorities) {
TEST_F(UsageTableHeaderTest,
DetermineLicenseToRemove_NoExpiredAndBelowThreshold) {
constexpr int64_t kOneDay = 24 * 60 * 60;
constexpr uint32_t kInvalidEntry = 9999;
std::vector<CdmUsageEntryInfo> usage_entry_info_list =
kUpgradedUsageEntryInfoList;
const size_t offline_threshold = usage_entry_info_list.size() + 1;
@@ -3693,17 +3715,13 @@ TEST_F(UsageTableHeaderTest,
// Must exist at least one streaming license for test to work.
ASSERT_LT(0ull, usage_info_count);
std::vector<uint32_t> removal_candidates;
uint32_t entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kLruBaseTime, offline_threshold,
/* removal_count = */ 3, &removal_candidates));
&entry_to_remove));
EXPECT_EQ(usage_info_count, removal_candidates.size());
// Ensure that the proposed removal candidates are streaming.
for (uint32_t usage_entry_number : removal_candidates) {
EXPECT_EQ(kStorageUsageInfo,
usage_entry_info_list[usage_entry_number].storage_type);
}
EXPECT_EQ(kStorageUsageInfo,
usage_entry_info_list[entry_to_remove].storage_type);
}
// When the number of unexpired offline licenses are below the
@@ -3718,6 +3736,7 @@ TEST_F(UsageTableHeaderTest,
constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2;
// A threshold larger than the possible number of offline entries.
constexpr size_t kUnexpiredThreshold = kSetSize + 1;
constexpr uint32_t kInvalidEntry = 9999;
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
usage_entry_info_list.resize(kSetSize);
@@ -3746,126 +3765,16 @@ TEST_F(UsageTableHeaderTest,
expired_license_numbers.push_back(i);
}
std::vector<uint32_t> removal_candidates;
uint32_t entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime, kUnexpiredThreshold, 3,
&removal_candidates));
usage_entry_info_list, kCurrentTime, kUnexpiredThreshold,
&entry_to_remove));
// Sorting to ensure equality will work.
std::sort(removal_candidates.begin(), removal_candidates.end());
std::sort(expired_license_numbers.begin(), expired_license_numbers.end());
EXPECT_EQ(expired_license_numbers, removal_candidates);
}
// Test that if all of the license are unknown. For unknown licenses,
// the last use time has no effect on their selection.
TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_AllUnknown) {
constexpr int64_t kOneDay = 24 * 60 * 60;
constexpr size_t kSetSize = 10;
constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2;
constexpr size_t kRemovalCount = 3;
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
usage_entry_info_list.resize(kSetSize);
// Create a set of all unknown licenses.
for (uint32_t i = 0; i < kSetSize; ++i) {
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
usage_entry_info.storage_type = kStorageTypeUnknown;
usage_entry_info.key_set_id = "unknown_key_set_id";
usage_entry_info.last_use_time =
CdmRandom::RandomInRange(kLruBaseTime, kCurrentTime);
}
std::vector<uint32_t> removal_candidates;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime, 0, kRemovalCount,
&removal_candidates));
// There should always be 3 (assuming kSetSize >= 3).
EXPECT_EQ(kRemovalCount, removal_candidates.size());
}
// Should there be two or more license which have the same
// |last_use_time| but only 1 of them are to be selected (due to the
// number being requested), then the algorithm should randomly select
// between the ones available.
TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_RandomnessOfCutoff) {
constexpr int64_t kOneDay = 24 * 60 * 60;
constexpr size_t kSetSize = 10;
constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2;
constexpr size_t kTrials = 25;
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
// All will be streaming licenses.
usage_entry_info_list.resize(kSetSize);
for (size_t i = 0; i < kSetSize; ++i) {
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
usage_entry_info.storage_type = kStorageUsageInfo;
usage_entry_info.last_use_time = kLruBaseTime + kOneDay;
usage_entry_info.key_set_id = "nothing_unusual";
}
std::vector<uint32_t> expected_removals;
// Select two to be the most stale.
while (expected_removals.size() < 2) {
const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1);
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
if (usage_entry_info.key_set_id != "nothing_unusual") continue;
usage_entry_info_list[i].last_use_time = kLruBaseTime;
usage_entry_info.key_set_id = "most_stale";
expected_removals.push_back(i);
}
// Select another two to be slightly less stale.
std::vector<uint32_t> random_removals;
while (random_removals.size() < 2) {
const uint32_t i = CdmRandom::RandomInRange(kSetSize - 1);
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
if (usage_entry_info.key_set_id != "nothing_unusual") continue;
usage_entry_info_list[i].last_use_time = kLruBaseTime + 1;
usage_entry_info.key_set_id = "somewhat_stale";
random_removals.push_back(i);
}
// Create two sets for each possible outcome (one for each random
// removal).
std::vector<uint32_t> possible_removal_1 = expected_removals;
possible_removal_1.push_back(random_removals[0]);
std::sort(possible_removal_1.begin(), possible_removal_1.end());
std::vector<uint32_t> possible_removal_2 = expected_removals;
possible_removal_2.push_back(random_removals[1]);
std::sort(possible_removal_2.begin(), possible_removal_2.end());
std::vector<uint32_t> possible_removal_3 = expected_removals;
// Flags to check that both outcomes have occurred.
bool occurrence_1 = false;
bool occurrence_2 = false;
// Each set should be equally likely. Possible false-negative every
// 2^kTrials time.
for (size_t i = 0; i < kTrials; ++i) {
// Remove 3 out of the 4 possible candidates.
std::vector<uint32_t> removal_candidates;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime, 0, 3, &removal_candidates));
std::sort(removal_candidates.begin(), removal_candidates.end());
if (removal_candidates == possible_removal_1) {
occurrence_1 = true;
} else if (removal_candidates == possible_removal_2) {
occurrence_2 = true;
} else {
EXPECT_TRUE(false) << "Unexpected removal set";
}
}
EXPECT_TRUE(occurrence_1);
EXPECT_TRUE(occurrence_2);
EXPECT_THAT(expired_license_numbers, Contains(entry_to_remove));
}
// This test primarily tests the robustness of the algorithm for a full
// set of entries (200). Creates 3 stale streaming license and 3
// set of entries (200). Creates 1 stale streaming license and 1
// offline licenses which are more stale than the streaming.
//
// First, with the stale offline licenses unexpired, checks that the
@@ -3878,6 +3787,7 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) {
constexpr int64_t kOneDay = 24 * 60 * 60;
constexpr size_t kLargeSetSize = 200;
constexpr int64_t kCurrentTime = kLruBaseTime + kOneDay * 2;
constexpr uint32_t kInvalidEntry = 9999;
std::vector<CdmUsageEntryInfo> usage_entry_info_list;
usage_entry_info_list.resize(kLargeSetSize);
@@ -3896,72 +3806,57 @@ TEST_F(UsageTableHeaderTest, DetermineLicenseToRemove_LargeMixedSet) {
}
}
// Select 3 streaming license to be more stale than the rest.
std::vector<uint32_t> modified_usage_info_numbers;
while (modified_usage_info_numbers.size() < 3) {
// Select a streaming license to be more stale than the rest.
uint32_t modified_usage_info_number = kInvalidEntry;
while (modified_usage_info_number == kInvalidEntry) {
const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1);
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
// Skip streaming license that have already been modified and offline
// licenses.
if (usage_entry_info.storage_type != kStorageUsageInfo ||
usage_entry_info.key_set_id != "nothing_unusual")
continue;
usage_entry_info.last_use_time =
kLruBaseTime + 10 + modified_usage_info_numbers.size();
// Skip offline licenses.
if (usage_entry_info.storage_type != kStorageUsageInfo) continue;
usage_entry_info.last_use_time = kLruBaseTime + 10;
usage_entry_info.key_set_id = "stale_streaming";
modified_usage_info_numbers.push_back(i);
modified_usage_info_number = i;
}
std::sort(modified_usage_info_numbers.begin(),
modified_usage_info_numbers.end());
// Select 3 offline license to be even more stale, but unexpired.
std::vector<uint32_t> modified_offline_license_numbers;
while (modified_offline_license_numbers.size() < 3) {
// Select a offline license to be even more stale, but unexpired.
uint32_t modified_offline_license_number = kInvalidEntry;
while (modified_offline_license_number == kInvalidEntry) {
const uint32_t i = CdmRandom::RandomInRange(kLargeSetSize - 1);
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
// Skip offline license that have already been modified and streaming
// licenses.
if (usage_entry_info.storage_type != kStorageLicense ||
usage_entry_info.key_set_id != "nothing_unusual")
continue;
// Skip streaming licenses.
if (usage_entry_info.storage_type != kStorageLicense) continue;
usage_entry_info.last_use_time = kLruBaseTime;
usage_entry_info.key_set_id = "stale_offline";
modified_offline_license_numbers.push_back(i);
modified_offline_license_number = i;
}
std::sort(modified_offline_license_numbers.begin(),
modified_offline_license_numbers.end());
// Test using only streaming and expired offline licenses
// (which there are none).
std::vector<uint32_t> removal_candidates;
uint32_t entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates));
EXPECT_THAT(removal_candidates,
UnorderedElementsAreArray(modified_usage_info_numbers));
/* unexpired_threshold = */ kLargeSetSize, &entry_to_remove));
EXPECT_EQ(modified_usage_info_number, entry_to_remove);
// Test where the equality threshold is met, now the 3 unexpired
// licenses should be selected.
removal_candidates.clear();
// Test where the equality threshold is met, now the stale unexpired
// license should be selected.
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ 0, 3, &removal_candidates));
EXPECT_THAT(removal_candidates,
UnorderedElementsAreArray(modified_offline_license_numbers));
/* unexpired_threshold = */ 0, &entry_to_remove));
EXPECT_EQ(modified_offline_license_number, entry_to_remove);
// Make the 3 offline licenses expired.
for (uint32_t i : modified_offline_license_numbers) {
CdmUsageEntryInfo& usage_entry_info = usage_entry_info_list[i];
usage_entry_info.offline_license_expiry_time = kLruBaseTime;
}
// Make the stale offline license expired.
CdmUsageEntryInfo& offline_usage_entry_info =
usage_entry_info_list[modified_offline_license_number];
offline_usage_entry_info.offline_license_expiry_time = kLruBaseTime;
// Test again, expecting that the expired license should be considered.
removal_candidates.clear();
entry_to_remove = kInvalidEntry;
EXPECT_TRUE(UsageTableHeader::DetermineLicenseToRemoveForTesting(
usage_entry_info_list, kCurrentTime,
/* unexpired_threshold = */ kLargeSetSize, 3, &removal_candidates));
EXPECT_THAT(removal_candidates,
UnorderedElementsAreArray(modified_offline_license_numbers));
/* unexpired_threshold = */ kLargeSetSize, &entry_to_remove));
EXPECT_EQ(modified_offline_license_number, entry_to_remove);
}
TEST_F(UsageTableHeaderTest, PotentialTableCapacity_Unavailable) {