Skip to content

Commit b7f342a

Browse files
committed
EGL Multifile Blobcache: Handle lost cache
During execution, if the app's cache is cleared from outside our control, two issues occur: * New entries won't persist to disk because the multifile directory is missing. * applyLRU will abort eviction, allowing entries beyond the limits. To address this, this CL: * Adds missing directory detection to our deferred write thread which then attempts to recreate it and continue. * Updates eviction to issue a warning rather than abort so it can update tracking and continue. For missing entries, the app will get hits from hotcache, but anything beyond that will become a miss. Additional tests: * RecoverFromLostCache * EvictAfterLostCache Based on work by: Igor Nazarov <i.nazarov@samsung.com> Test: libEGL_test, EGL_test, ANGLE trace tests, apps Bug: b/351867582, b/380483358 Flag: com.android.graphics.egl.flags.multifile_blobcache_advanced_usage Change-Id: I13c8cdf58c957163eed4498c0d4be180574bf03e
1 parent 99e8f2c commit b7f342a

2 files changed

Lines changed: 161 additions & 6 deletions

File tree

opengl/libs/EGL/MultifileBlobCache.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s
137137
// Check that our cacheVersion and buildId match
138138
struct stat st;
139139
if (stat(mMultifileDirName.c_str(), &st) == 0) {
140-
if (checkStatus(mMultifileDirName.c_str())) {
140+
if (checkStatus(mMultifileDirName)) {
141141
statusGood = true;
142142
} else {
143143
ALOGV("INIT: Cache status has changed, clearing the cache");
@@ -851,8 +851,8 @@ bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit)
851851
// Remove it from the system
852852
std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
853853
if (remove(entryPath.c_str()) != 0) {
854-
ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
855-
return false;
854+
// Continue evicting invalid item (app's cache might be cleared)
855+
ALOGW("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
856856
}
857857

858858
// Increment the iterator before clearing the entry
@@ -945,9 +945,36 @@ void MultifileBlobCache::processTask(DeferredTask& task) {
945945
// Create the file or reset it if already present, read+write for user only
946946
int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
947947
if (fd == -1) {
948-
ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
949-
fullPath.c_str(), std::strerror(errno));
950-
return;
948+
if (flags::multifile_blobcache_advanced_usage()) {
949+
struct stat st;
950+
if (stat(mMultifileDirName.c_str(), &st) == -1) {
951+
ALOGW("Cache directory missing (app's cache cleared?). Recreating...");
952+
953+
// Restore the multifile directory
954+
if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
955+
ALOGE("Cache error in SET - Unable to create directory (%s), errno "
956+
"(%i)",
957+
mMultifileDirName.c_str(), errno);
958+
return;
959+
}
960+
961+
// Create new status file
962+
if (!createStatus(mMultifileDirName.c_str())) {
963+
ALOGE("Cache error in SET - Failed to create status file!");
964+
return;
965+
}
966+
967+
// Try to open the file again
968+
fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
969+
S_IRUSR | S_IWUSR);
970+
}
971+
}
972+
973+
if (fd == -1) {
974+
ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
975+
fullPath.c_str(), std::strerror(errno));
976+
return;
977+
}
951978
}
952979

953980
ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());

opengl/libs/EGL/MultifileBlobCache_test.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class MultifileBlobCacheTest : public ::testing::Test {
5959
std::vector<std::string> getCacheEntries();
6060

6161
void clearProperties();
62+
bool clearCache();
6263

6364
std::unique_ptr<TemporaryFile> mTempFile;
6465
std::unique_ptr<MultifileBlobCache> mMBC;
@@ -727,4 +728,131 @@ TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) {
727728
}
728729
}
729730

731+
bool MultifileBlobCacheTest::clearCache() {
732+
std::string cachePath = &mTempFile->path[0];
733+
std::string multifileDirName = cachePath + ".multifile";
734+
735+
DIR* dir = opendir(multifileDirName.c_str());
736+
if (dir == nullptr) {
737+
printf("Error opening directory: %s\n", multifileDirName.c_str());
738+
return false;
739+
}
740+
741+
struct dirent* entry;
742+
while ((entry = readdir(dir)) != nullptr) {
743+
// Skip "." and ".." entries
744+
if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") {
745+
continue;
746+
}
747+
748+
std::string entryPath = multifileDirName + "/" + entry->d_name;
749+
750+
// Delete the entry (we assert it's a file, nothing nested here)
751+
if (unlink(entryPath.c_str()) != 0) {
752+
printf("Error deleting file: %s\n", entryPath.c_str());
753+
closedir(dir);
754+
return false;
755+
}
756+
}
757+
758+
closedir(dir);
759+
760+
// Delete the empty directory itself
761+
if (rmdir(multifileDirName.c_str()) != 0) {
762+
printf("Error deleting directory %s, error %s\n", multifileDirName.c_str(),
763+
std::strerror(errno));
764+
return false;
765+
}
766+
767+
return true;
768+
}
769+
770+
// Recover from lost cache in the case of app clearing it
771+
TEST_F(MultifileBlobCacheTest, RecoverFromLostCache) {
772+
if (!flags::multifile_blobcache_advanced_usage()) {
773+
GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
774+
}
775+
776+
int entry = 0;
777+
int result = 0;
778+
779+
uint32_t kEntryCount = 10;
780+
781+
// Add some entries
782+
for (entry = 0; entry < kEntryCount; entry++) {
783+
mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
784+
ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
785+
ASSERT_EQ(entry, result);
786+
}
787+
788+
// For testing, wait until the entries have completed writing
789+
mMBC->finish();
790+
791+
// Manually delete the cache!
792+
ASSERT_TRUE(clearCache());
793+
794+
// Cache should not contain any entries
795+
for (entry = 0; entry < kEntryCount; entry++) {
796+
ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
797+
}
798+
799+
// Ensure we can still add new ones
800+
for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
801+
mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
802+
ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
803+
ASSERT_EQ(entry, result);
804+
}
805+
806+
// Close the cache so everything writes out
807+
mMBC->finish();
808+
mMBC.reset();
809+
810+
// Open the cache again
811+
mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
812+
&mTempFile->path[0]));
813+
814+
// Before fixes, writing the second entries to disk should have failed due to missing
815+
// cache dir. But now they should have survived our shutdown above.
816+
for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
817+
ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
818+
ASSERT_EQ(entry, result);
819+
}
820+
}
821+
822+
// Ensure cache eviction succeeds if the cache is deleted
823+
TEST_F(MultifileBlobCacheTest, EvictAfterLostCache) {
824+
if (!flags::multifile_blobcache_advanced_usage()) {
825+
GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
826+
}
827+
828+
int entry = 0;
829+
int result = 0;
830+
831+
uint32_t kEntryCount = 10;
832+
833+
// Add some entries
834+
for (entry = 0; entry < kEntryCount; entry++) {
835+
mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
836+
ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
837+
ASSERT_EQ(entry, result);
838+
}
839+
840+
// For testing, wait until the entries have completed writing
841+
mMBC->finish();
842+
843+
// Manually delete the cache!
844+
ASSERT_TRUE(clearCache());
845+
846+
// Now start adding entries to trigger eviction, cache should survive
847+
for (entry = kEntryCount; entry < 2 * kMaxTotalEntries; entry++) {
848+
mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
849+
ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
850+
ASSERT_EQ(entry, result);
851+
}
852+
853+
// We should have triggered multiple evictions above and remain at or below the
854+
// max amount of entries
855+
ASSERT_LE(getCacheEntries().size(), kMaxTotalEntries);
856+
}
857+
730858
} // namespace android

0 commit comments

Comments
 (0)