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

@@ -114,11 +114,10 @@ class UsageTableHeader {
static bool DetermineLicenseToRemoveForTesting(
const std::vector<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* removal_candidates) {
int64_t current_time, size_t unexpired_threshold,
uint32_t* entry_to_remove) {
return DetermineLicenseToRemove(usage_entry_info_list, current_time,
unexpired_threshold, removal_count,
removal_candidates);
unexpired_threshold, entry_to_remove);
}
private:
@@ -156,7 +155,7 @@ class UsageTableHeader {
// device file that had not yet been upgraded to use the LRU data.
virtual bool LruUpgradeAllUsageEntries();
virtual bool GetRemovalCandidates(std::vector<uint32_t>* removal_candidates);
virtual bool GetRemovalCandidate(uint32_t* entry_to_remove);
int64_t GetCurrentTime() { return clock_ref_->GetCurrentTime(); }
@@ -166,7 +165,7 @@ class UsageTableHeader {
uint64_t staleness,
CdmUsageEntryStorageType storage_type);
// Uses an LRU-base algorithm to determine which licenses should be
// Uses an LRU-base algorithm to determine which license should be
// removed. This is intended to be used if the usage table is full
// and a new entry needs to be added.
//
@@ -181,8 +180,6 @@ class UsageTableHeader {
// 2) Unexpired offline licenses will only be considered for
// removal if the number of unexpired offline licenses exceeds
// |unexpired_threshold|.
// The number of licenses to be considered will be less than or
// equal to the requested |removal_count|.
//
// Unknown storage types will be considered above all other entry
// types.
@@ -195,21 +192,17 @@ class UsageTableHeader {
// [in] unexpired_threshold: The maximum number of unexpired
// offline licenses that are present, before offline
// licenses would be considered for removal.
// [in] removal_count: The desired number of removal candidate to
// find. Note that the actual number will be anywhere
// between 1 and |removal_count|. Must be greater than or
// equal to 1.
// [out] removal_candidates: List of usage entry numbers of the
// entries to be removed. Assume to be unaffected if the
// [out] entry_to_remove: Usage entry index of the entry selected
// to be removed. Assume to be unaffected if the
// function returns |false|.
//
// Returns:
// |true| if at least one removal candidate can be determined.
// |true| if an entry has been determined to be removed.
// Otherwise returns |false|.
static bool DetermineLicenseToRemove(
const std::vector<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* removal_candidates);
int64_t current_time, size_t unexpired_threshold,
uint32_t* entry_to_remove);
// This handle and file system is only to be used when accessing
// usage_table_header. Usage entries should use the file system provided

View File

@@ -5,6 +5,7 @@
#include "usage_table_header.h"
#include <algorithm>
#include <limits>
#include "cdm_random.h"
#include "crypto_session.h"
@@ -22,9 +23,6 @@ std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0);
std::string kOldUsageEntryPoviderSessionToken =
"nahZ6achSheiqua3TohQuei0ahwohv";
constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days
// Number of elements to be considered for removal using the LRU algorithm.
// TODO(b/155230578): Remove this constant.
constexpr size_t kLruRemovalSetSize = 3;
// Fraction of table capacity of number of unexpired offline licenses
// before they are considered to be removed. This could occur if
// there are not enough expired offline or streaming licenses to
@@ -946,13 +944,11 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files,
CdmResponseType UsageTableHeader::ReleaseOldestEntry(
metrics::CryptoMetrics* metrics) {
LOGV("Releasing oldest entry");
std::vector<uint32_t> removal_candidates;
if (!GetRemovalCandidates(&removal_candidates)) {
uint32_t entry_number_to_delete;
if (!GetRemovalCandidate(&entry_number_to_delete)) {
LOGE("Could not determine which license to remove");
return UNKNOWN_ERROR;
}
// Only release one entry.
const uint32_t entry_number_to_delete = removal_candidates.front();
const CdmUsageEntryInfo& usage_entry_info =
usage_entry_info_[entry_number_to_delete];
@@ -1115,15 +1111,13 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() {
return true;
}
bool UsageTableHeader::GetRemovalCandidates(
std::vector<uint32_t>* removal_candidates) {
bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) {
LOGI("Locking to determine removal candidates");
std::unique_lock<std::mutex> auto_lock(usage_table_header_lock_);
const size_t lru_unexpired_threshold =
kLruUnexpiredThresholdFraction * potential_table_capacity();
return DetermineLicenseToRemove(usage_entry_info_, GetCurrentTime(),
lru_unexpired_threshold, kLruRemovalSetSize,
removal_candidates);
lru_unexpired_threshold, entry_to_remove);
}
void UsageTableHeader::RecordLruEventMetrics(
@@ -1139,125 +1133,105 @@ void UsageTableHeader::RecordLruEventMetrics(
}
// Static.
// TODO(b/155230578): Change this function to only return 1 entry.
bool UsageTableHeader::DetermineLicenseToRemove(
const std::vector<CdmUsageEntryInfo>& usage_entry_info_list,
int64_t current_time, size_t unexpired_threshold, size_t removal_count,
std::vector<uint32_t>* removal_candidates) {
if (removal_candidates == nullptr) {
LOGE("Output parameter |removal_candidates| is null");
return false;
}
if (removal_count == 0) {
LOGE("|removal_count| cannot be zero");
int64_t current_time, size_t unexpired_threshold,
uint32_t* entry_to_remove) {
if (entry_to_remove == nullptr) {
LOGE("Output parameter |entry_to_remove| is null");
return false;
}
if (usage_entry_info_list.empty()) {
return false;
}
removal_candidates->clear();
std::vector<uint32_t> unknown_storage_entry_numbers;
// |entry_numbers| contains expired offline and streaming license.
std::vector<uint32_t> entry_numbers;
std::vector<uint32_t> unexpired_offline_license_entry_numbers;
// Separate the entries based on their priority properties.
for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size();
++entry_number) {
const CdmUsageEntryInfo& usage_entry_info =
usage_entry_info_list[entry_number];
if (usage_entry_info.storage_type == kStorageLicense) {
if (usage_entry_info.offline_license_expiry_time > current_time) {
// Unexpired offline.
unexpired_offline_license_entry_numbers.push_back(entry_number);
} else {
// Expired offline.
entry_numbers.push_back(entry_number);
}
} else if (usage_entry_info.storage_type == kStorageUsageInfo) {
// Streaming.
entry_numbers.push_back(entry_number);
} else {
// Unknown entries.
unknown_storage_entry_numbers.push_back(entry_number);
}
}
// Select any entries of unknown storage type.
if (!unknown_storage_entry_numbers.empty()) {
if (unknown_storage_entry_numbers.size() >= removal_count) {
// Case: There are enough entries with unknown storage types to
// fill the removal set.
removal_candidates->insert(
removal_candidates->begin(), unknown_storage_entry_numbers.begin(),
unknown_storage_entry_numbers.begin() + removal_count);
return true;
}
// Fill whatever are available, and check for more.
*removal_candidates = std::move(unknown_storage_entry_numbers);
}
// Sort licenses based on last used time.
const auto compare_last_used = [&](uint32_t i, uint32_t j) {
// Returns true if entry of first index is more stale than the
// entry of the second index.
const auto is_more_stale = [&](uint32_t i, uint32_t j) -> bool {
return usage_entry_info_list[i].last_use_time <
usage_entry_info_list[j].last_use_time;
};
// Check if unexpired licenses should be considered too.
if (unexpired_offline_license_entry_numbers.size() > unexpired_threshold) {
std::sort(unexpired_offline_license_entry_numbers.begin(),
unexpired_offline_license_entry_numbers.end(), compare_last_used);
if (unexpired_offline_license_entry_numbers.size() > removal_count) {
unexpired_offline_license_entry_numbers.resize(removal_count);
// Find the most stale expired offline / streaming license and the
// most stale unexpired offline entry. Count the number of unexpired
// entries. If any entry is of storage type unknown, then it should
// be removed.
constexpr uint32_t kNoEntry = std::numeric_limits<uint32_t>::max();
uint32_t stalest_expired_offline_license = kNoEntry;
uint32_t stalest_unexpired_offline_license = kNoEntry;
uint32_t stalest_streaming_license = kNoEntry;
size_t unexpired_license_count = 0;
for (uint32_t entry_number = 0; entry_number < usage_entry_info_list.size();
++entry_number) {
const CdmUsageEntryInfo& usage_entry_info =
usage_entry_info_list[entry_number];
if (usage_entry_info.storage_type != kStorageLicense &&
usage_entry_info.storage_type != kStorageUsageInfo) {
// Unknown storage type entries. Remove this entry.
*entry_to_remove = entry_number;
return true;
}
if (usage_entry_info.storage_type == kStorageLicense &&
usage_entry_info.offline_license_expiry_time > current_time) {
// Unexpired offline.
++unexpired_license_count;
if (stalest_unexpired_offline_license == kNoEntry ||
is_more_stale(entry_number, stalest_unexpired_offline_license)) {
stalest_unexpired_offline_license = entry_number;
}
} else if (usage_entry_info.storage_type == kStorageLicense) {
// Expired offline.
if (stalest_expired_offline_license == kNoEntry ||
is_more_stale(entry_number, stalest_expired_offline_license)) {
stalest_expired_offline_license = entry_number;
}
} else {
// Streaming.
if (stalest_streaming_license == kNoEntry ||
is_more_stale(entry_number, stalest_streaming_license)) {
stalest_streaming_license = entry_number;
}
}
// Merge the sets.
entry_numbers.insert(entry_numbers.end(),
unexpired_offline_license_entry_numbers.begin(),
unexpired_offline_license_entry_numbers.end());
}
// Sort expired offline and streaming license based on last used time.
std::sort(entry_numbers.begin(), entry_numbers.end(), compare_last_used);
if ((entry_numbers.size() + removal_candidates->size()) <= removal_count) {
// Under testing conditions, it is possible for there to be fewer usage
// entries than there are being requested.
// Move whatever values are available to the removal candidates.
removal_candidates->insert(removal_candidates->end(), entry_numbers.begin(),
entry_numbers.end());
return removal_candidates->size() > 0;
if (stalest_expired_offline_license == kNoEntry &&
stalest_streaming_license == kNoEntry &&
unexpired_license_count <= unexpired_threshold) {
// Unexpected situation, could be an issue with the threshold.
LOGW(
"Table only contains unexpired offline licenses, "
"but threshold not met: size = %zu, count = %zu, threshold = %zu",
usage_entry_info_list.size(), unexpired_license_count,
unexpired_threshold);
*entry_to_remove = stalest_unexpired_offline_license;
return true;
}
const size_t remaining_removal_count =
removal_count - removal_candidates->size();
// Based on the last use time the |remaining_removal_count|-th
// least recently used entry, filter out all elements which have
// been used more recently than it. This might result in a set
// which is larger than the desired size.
const int64_t cutoff_last_use_time =
usage_entry_info_list[entry_numbers[remaining_removal_count - 1]]
.last_use_time;
const auto equal_to_cutoff = [&](uint32_t entry_number) {
return usage_entry_info_list[entry_number].last_use_time ==
cutoff_last_use_time;
const auto select_most_stale = [&](uint32_t a, uint32_t b) -> uint32_t {
if (a == kNoEntry) return b;
if (b == kNoEntry) return a;
return is_more_stale(a, b) ? a : b;
};
const auto first_cutoff_it =
std::find_if(entry_numbers.begin(), entry_numbers.end(), equal_to_cutoff);
// Only consider an unexpired entry if the threshold is reached.
if (unexpired_license_count > unexpired_threshold) {
const uint32_t temp = select_most_stale(stalest_unexpired_offline_license,
stalest_streaming_license);
*entry_to_remove = select_most_stale(temp, stalest_expired_offline_license);
} else {
*entry_to_remove = select_most_stale(stalest_streaming_license,
stalest_expired_offline_license);
}
const auto after_last_cutoff_it =
std::find_if_not(first_cutoff_it, entry_numbers.end(), equal_to_cutoff);
// To avoid always selecting the greatest entry number (due to the
// sort & reverse), we randomize the set.
std::shuffle(first_cutoff_it, after_last_cutoff_it,
std::default_random_engine(CdmRandom::Rand()));
removal_candidates->insert(removal_candidates->end(), entry_numbers.cbegin(),
entry_numbers.cbegin() + remaining_removal_count);
if (*entry_to_remove == kNoEntry) {
// Illegal state check. The loop above should have found at least
// one entry given that |usage_entry_info_list| is not empty.
LOGE("No entry could be used for removal: size = %zu",
usage_entry_info_list.size());
return false;
}
return true;
}

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);
}
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) {