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
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 74352d4..fb765a7 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -59,6 +59,7 @@
std::vector<std::string> getCacheEntries();
void clearProperties();
+ bool clearCache();
std::unique_ptr<TemporaryFile> mTempFile;
std::unique_ptr<MultifileBlobCache> mMBC;
@@ -727,4 +728,131 @@
}
}
+bool MultifileBlobCacheTest::clearCache() {
+ std::string cachePath = &mTempFile->path[0];
+ std::string multifileDirName = cachePath + ".multifile";
+
+ DIR* dir = opendir(multifileDirName.c_str());
+ if (dir == nullptr) {
+ printf("Error opening directory: %s\n", multifileDirName.c_str());
+ return false;
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != nullptr) {
+ // Skip "." and ".." entries
+ if (std::string(entry->d_name) == "." || std::string(entry->d_name) == "..") {
+ continue;
+ }
+
+ std::string entryPath = multifileDirName + "/" + entry->d_name;
+
+ // Delete the entry (we assert it's a file, nothing nested here)
+ if (unlink(entryPath.c_str()) != 0) {
+ printf("Error deleting file: %s\n", entryPath.c_str());
+ closedir(dir);
+ return false;
+ }
+ }
+
+ closedir(dir);
+
+ // Delete the empty directory itself
+ if (rmdir(multifileDirName.c_str()) != 0) {
+ printf("Error deleting directory %s, error %s\n", multifileDirName.c_str(),
+ std::strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+// Recover from lost cache in the case of app clearing it
+TEST_F(MultifileBlobCacheTest, RecoverFromLostCache) {
+ if (!flags::multifile_blobcache_advanced_usage()) {
+ GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+ }
+
+ int entry = 0;
+ int result = 0;
+
+ uint32_t kEntryCount = 10;
+
+ // Add some entries
+ for (entry = 0; entry < kEntryCount; entry++) {
+ mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+ ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ ASSERT_EQ(entry, result);
+ }
+
+ // For testing, wait until the entries have completed writing
+ mMBC->finish();
+
+ // Manually delete the cache!
+ ASSERT_TRUE(clearCache());
+
+ // Cache should not contain any entries
+ for (entry = 0; entry < kEntryCount; entry++) {
+ ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ }
+
+ // Ensure we can still add new ones
+ for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+ mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+ ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ ASSERT_EQ(entry, result);
+ }
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Open the cache again
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Before fixes, writing the second entries to disk should have failed due to missing
+ // cache dir. But now they should have survived our shutdown above.
+ for (entry = kEntryCount; entry < kEntryCount * 2; entry++) {
+ ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ ASSERT_EQ(entry, result);
+ }
+}
+
+// Ensure cache eviction succeeds if the cache is deleted
+TEST_F(MultifileBlobCacheTest, EvictAfterLostCache) {
+ if (!flags::multifile_blobcache_advanced_usage()) {
+ GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag";
+ }
+
+ int entry = 0;
+ int result = 0;
+
+ uint32_t kEntryCount = 10;
+
+ // Add some entries
+ for (entry = 0; entry < kEntryCount; entry++) {
+ mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+ ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ ASSERT_EQ(entry, result);
+ }
+
+ // For testing, wait until the entries have completed writing
+ mMBC->finish();
+
+ // Manually delete the cache!
+ ASSERT_TRUE(clearCache());
+
+ // Now start adding entries to trigger eviction, cache should survive
+ for (entry = kEntryCount; entry < 2 * kMaxTotalEntries; entry++) {
+ mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry));
+ ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result)));
+ ASSERT_EQ(entry, result);
+ }
+
+ // We should have triggered multiple evictions above and remain at or below the
+ // max amount of entries
+ ASSERT_LE(getCacheEntries().size(), kMaxTotalEntries);
+}
+
} // namespace android