diff options
Diffstat (limited to 'opengl/libs')
-rw-r--r-- | opengl/libs/Android.bp | 15 | ||||
-rw-r--r-- | opengl/libs/EGL/Loader.cpp | 3 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.cpp | 233 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.h | 37 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache_test.cpp | 413 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_flags.aconfig | 13 | ||||
-rw-r--r-- | opengl/libs/EGL/fuzzer/Android.bp | 1 |
7 files changed, 675 insertions, 40 deletions
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index b19a862b6c..91250b9945 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -63,6 +63,18 @@ ndk_library { unversioned_until: "current", } +aconfig_declarations { + name: "egl_flags", + package: "com.android.graphics.egl.flags", + container: "system", + srcs: ["EGL/egl_flags.aconfig"], +} + +cc_aconfig_library { + name: "libegl_flags", + aconfig_declarations: "egl_flags", +} + cc_defaults { name: "gl_libs_defaults", cflags: [ @@ -136,6 +148,7 @@ cc_library_static { ], export_include_dirs: ["EGL"], shared_libs: [ + "libegl_flags", "libz", ], } @@ -166,6 +179,7 @@ cc_library_shared { "android.hardware.configstore@1.0", "android.hardware.configstore-utils", "libbase", + "libegl_flags", "libhidlbase", "libnativebridge_lazy", "libnativeloader_lazy", @@ -202,6 +216,7 @@ cc_test { "EGL/MultifileBlobCache_test.cpp", ], shared_libs: [ + "libegl_flags", "libutils", "libz", ], diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index fed6afc8e0..7012df21ae 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -603,6 +603,9 @@ Loader::driver_t* Loader::attempt_to_load_angle(egl_connection_t* cnx) { driver_t* hnd = nullptr; // ANGLE doesn't ship with GLES library, and thus we skip GLES driver. + // b/370113081: if there is no libEGL_angle.so in namespace ns, libEGL_angle.so in system + // partition will be loaded instead. If there is no libEGL_angle.so in system partition, no + // angle libs are loaded, and app that sets to use ANGLE will crash. void* dso = load_angle("EGL", ns); if (dso) { initialize_api(dso, cnx, EGL); diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp index f7e33b383f..04c525e93d 100644 --- a/opengl/libs/EGL/MultifileBlobCache.cpp +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -38,11 +38,20 @@ #include <utils/JenkinsHash.h> +#include <com_android_graphics_egl_flags.h> + +using namespace com::android::graphics::egl; + using namespace std::literals; constexpr uint32_t kMultifileMagic = 'MFB$'; constexpr uint32_t kCrcPlaceholder = 0; +// When removing files, what fraction of the overall limit should be reached when removing files +// A divisor of two will decrease the cache to 50%, four to 25% and so on +// We use the same limit to manage size and entry count +constexpr uint32_t kCacheLimitDivisor = 2; + namespace { // Helper function to close entries or free them @@ -72,6 +81,7 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s mMaxTotalEntries(maxTotalEntries), mTotalCacheSize(0), mTotalCacheEntries(0), + mTotalCacheSizeDivisor(kCacheLimitDivisor), mHotCacheLimit(0), mHotCacheSize(0), mWorkerThreadIdle(true) { @@ -80,8 +90,13 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s return; } - // Set the cache version, override if debug value set + // Set the cache version mCacheVersion = kMultifileBlobCacheVersion; + // Bump the version if we're using flagged features + if (flags::multifile_blobcache_advanced_usage()) { + mCacheVersion++; + } + // Override if debug value set int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1); if (debugCacheVersion >= 0) { ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion); @@ -122,7 +137,7 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s // Check that our cacheVersion and buildId match struct stat st; if (stat(mMultifileDirName.c_str(), &st) == 0) { - if (checkStatus(mMultifileDirName.c_str())) { + if (checkStatus(mMultifileDirName)) { statusGood = true; } else { ALOGV("INIT: Cache status has changed, clearing the cache"); @@ -237,11 +252,9 @@ MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, s ALOGV("INIT: Entry %u is good, tracking it now.", entryHash); - // Track details for rapid lookup later - trackEntry(entryHash, header.valueSize, fileSize, st.st_atime); - - // Track the total size - increaseTotalCacheSize(fileSize); + // Track details for rapid lookup later and update total size + // Note access time is a full timespec instead of just seconds + trackEntry(entryHash, header.valueSize, fileSize, st.st_atim); // Preload the entry for fast retrieval if ((mHotCacheSize + fileSize) < mHotCacheLimit) { @@ -317,6 +330,28 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi // Generate a hash of the key and use it to track this entry uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize); + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // See if we already have this file + if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) { + // Remove previous entry from hot cache + removeFromHotCache(entryHash); + + // Remove previous entry and update the overall cache size + removeEntry(entryHash); + + // If valueSize is zero, this is an indication that the user wants to remove the entry from + // cache It has already been removed from tracking, now remove it from disk It is safe to do + // this immediately because we drained the write queue in removeFromHotCache + if (valueSize == 0) { + ALOGV("SET: Zero size detected for existing entry, removing %u from cache", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGW("SET: Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + return; + } + } + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; // If we're going to be over the cache limit, kick off a trim to clear space @@ -339,13 +374,12 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize), static_cast<const void*>(value), valueSize); - std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); - - // Track the size and access time for quick recall - trackEntry(entryHash, valueSize, fileSize, time(0)); - - // Update the overall cache size - increaseTotalCacheSize(fileSize); + // Track the size and access time for quick recall and update the overall cache size + struct timespec time = {0, 0}; + if (flags::multifile_blobcache_advanced_usage()) { + clock_gettime(CLOCK_REALTIME, &time); + } + trackEntry(entryHash, valueSize, fileSize, time); // Keep the entry in hot cache for quick retrieval ALOGV("SET: Adding %u to hot cache.", entryHash); @@ -427,6 +461,14 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize if (mHotCache.find(entryHash) != mHotCache.end()) { ALOGV("GET: HotCache HIT for entry %u", entryHash); cacheEntry = mHotCache[entryHash].entryBuffer; + + if (flags::multifile_blobcache_advanced_usage()) { + // Update last access time on disk + struct timespec times[2]; + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_OMIT; + utimensat(0, fullPath.c_str(), times, 0); + } } else { ALOGV("GET: HotCache MISS for entry: %u", entryHash); @@ -455,6 +497,14 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize cacheEntry = reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (flags::multifile_blobcache_advanced_usage()) { + // Update last access time and omit last modify time + struct timespec times[2]; + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_OMIT; + futimens(fd, times); + } + // We can close the file now and the mmap will remain close(fd); @@ -491,6 +541,13 @@ EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize return 0; } + if (flags::multifile_blobcache_advanced_usage()) { + // Update the entry time for this hash, so it reflects LRU + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + updateEntryTime(entryHash, time); + } + // Remaining entry following the key is the value uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); memcpy(value, cachedValue, cachedValueSize); @@ -626,9 +683,47 @@ bool MultifileBlobCache::checkStatus(const std::string& baseDir) { } void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime) { + const timespec& accessTime) { +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + // When we add this entry to the map, it is sorted by accessTime + MultifileEntryStatsMapIter entryStatsIter = + mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime), + std::forward_as_tuple(entryHash, valueSize, fileSize)); + + // Track all entries with quick access to its stats + mEntries.emplace(entryHash, entryStatsIter); +#else + (void)accessTime; mEntries.insert(entryHash); - mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; + mEntryStats[entryHash] = {entryHash, valueSize, fileSize}; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + + increaseTotalCacheSize(fileSize); +} + +bool MultifileBlobCache::removeEntry(uint32_t entryHash) { + auto entryIter = mEntries.find(entryHash); + if (entryIter == mEntries.end()) { + return false; + } + +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + MultifileEntryStatsMapIter entryStatsIter = entryIter->second; + MultifileEntryStats entryStats = entryStatsIter->second; + decreaseTotalCacheSize(entryStats.fileSize); +#else + auto entryStatsIter = mEntryStats.find(entryHash); + if (entryStatsIter == mEntryStats.end()) { + ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash); + return false; + } + decreaseTotalCacheSize(entryStatsIter->second.fileSize); +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + + mEntryStats.erase(entryStatsIter); + mEntries.erase(entryIter); + + return true; } bool MultifileBlobCache::contains(uint32_t hashEntry) const { @@ -636,7 +731,40 @@ bool MultifileBlobCache::contains(uint32_t hashEntry) const { } MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + auto entryIter = mEntries.find(entryHash); + if (entryIter == mEntries.end()) { + return {}; + } + + MultifileEntryStatsMapIter entryStatsIter = entryIter->second; + MultifileEntryStats entryStats = entryStatsIter->second; + return entryStats; +#else return mEntryStats[entryHash]; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) +} + +void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) { +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + // This function updates the ordering of the map by removing the old iterators + // and re-adding them. If should be perforant as it does not perform a full re-sort. + // First, pull out the old entryStats + auto entryIter = mEntries.find(entryHash); + MultifileEntryStatsMapIter entryStatsIter = entryIter->second; + MultifileEntryStats entryStats = std::move(entryStatsIter->second); + + // Remove the old iterators + mEntryStats.erase(entryStatsIter); + mEntries.erase(entryIter); + + // Insert the new with updated time + entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats))); + mEntries.emplace(entryHash, entryStatsIter); +#else + (void)entryHash; + (void)newTime; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) } void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { @@ -718,31 +846,32 @@ bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) { // Walk through our map of sorted last access times and remove files until under the limit for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + const MultifileEntryStats& entryStats = cacheEntryIter->second; + uint32_t entryHash = entryStats.entryHash; +#else uint32_t entryHash = cacheEntryIter->first; + const MultifileEntryStats& entryStats = cacheEntryIter->second; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) ALOGV("LRU: Removing entryHash %u", entryHash); - // Track the overall size - MultifileEntryStats entryStats = getEntryStats(entryHash); - decreaseTotalCacheSize(entryStats.fileSize); - // Remove it from hot cache if present removeFromHotCache(entryHash); // Remove it from the system std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash); if (remove(entryPath.c_str()) != 0) { - ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); - return false; + // Continue evicting invalid item (app's cache might be cleared) + ALOGW("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno)); } // Increment the iterator before clearing the entry cacheEntryIter++; - // Delete the entry from our tracking - size_t count = mEntryStats.erase(entryHash); - if (count != 1) { - ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash); + // Delete the entry from our tracking and update the overall cache size + if (!removeEntry(entryHash)) { + ALOGE("LRU: Failed to remove entryHash %u", entryHash); return false; } @@ -794,11 +923,6 @@ bool MultifileBlobCache::clearCache() { return true; } -// When removing files, what fraction of the overall limit should be reached when removing files -// A divisor of two will decrease the cache to 50%, four to 25% and so on -// We use the same limit to manage size and entry count -constexpr uint32_t kCacheLimitDivisor = 2; - // Calculate the cache size and remove old entries until under the limit void MultifileBlobCache::trimCache() { // Wait for all deferred writes to complete @@ -806,8 +930,10 @@ void MultifileBlobCache::trimCache() { waitForWorkComplete(); ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu", - mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor); - if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) { + mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor); + + if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor, + mMaxTotalEntries / mTotalCacheSizeDivisor)) { ALOGE("Error when clearing multifile shader cache"); return; } @@ -830,9 +956,36 @@ void MultifileBlobCache::processTask(DeferredTask& task) { // Create the file or reset it if already present, read+write for user only int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd == -1) { - ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", - fullPath.c_str(), std::strerror(errno)); - return; + if (flags::multifile_blobcache_advanced_usage()) { + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == -1) { + ALOGW("Cache directory missing (app's cache cleared?). Recreating..."); + + // Restore the multifile directory + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Cache error in SET - Unable to create directory (%s), errno " + "(%i)", + mMultifileDirName.c_str(), errno); + return; + } + + // Create new status file + if (!createStatus(mMultifileDirName.c_str())) { + ALOGE("Cache error in SET - Failed to create status file!"); + return; + } + + // Try to open the file again + fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + } + } + + if (fd == -1) { + ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s", + fullPath.c_str(), std::strerror(errno)); + return; + } } ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); @@ -849,6 +1002,14 @@ void MultifileBlobCache::processTask(DeferredTask& task) { return; } + if (flags::multifile_blobcache_advanced_usage()) { + // Update last access time and last modify time + struct timespec times[2]; + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_NOW; + futimens(fd, times); + } + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); close(fd); diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h index 65aa2db344..3bd393f068 100644 --- a/opengl/libs/EGL/MultifileBlobCache.h +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -32,6 +32,10 @@ #include "FileBlobCache.h" +#include <com_android_graphics_egl_flags.h> + +using namespace com::android::graphics::egl; + namespace android { constexpr uint32_t kMultifileBlobCacheVersion = 2; @@ -45,9 +49,9 @@ struct MultifileHeader { }; struct MultifileEntryStats { + uint32_t entryHash; EGLsizeiANDROID valueSize; size_t fileSize; - time_t accessTime; }; struct MultifileStatus { @@ -100,6 +104,26 @@ private: size_t mBufferSize; }; +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) +struct MultifileTimeLess { + bool operator()(const struct timespec& t1, const struct timespec& t2) const { + if (t1.tv_sec == t2.tv_sec) { + // If seconds are equal, check nanoseconds + return t1.tv_nsec < t2.tv_nsec; + } else { + // Otherwise, compare seconds + return t1.tv_sec < t2.tv_sec; + } + } +}; + +// The third parameter here causes all entries to be sorted by access time, +// so oldest will be accessed first in applyLRU +using MultifileEntryStatsMap = + std::multimap<struct timespec, MultifileEntryStats, MultifileTimeLess>; +using MultifileEntryStatsMapIter = MultifileEntryStatsMap::iterator; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + class MultifileBlobCache { public: MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, @@ -115,6 +139,7 @@ public: size_t getTotalSize() const { return mTotalCacheSize; } size_t getTotalEntries() const { return mTotalCacheEntries; } + size_t getTotalCacheSizeDivisor() const { return mTotalCacheSizeDivisor; } const std::string& getCurrentBuildId() const { return mBuildId; } void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; } @@ -124,10 +149,11 @@ public: private: void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, - time_t accessTime); + const timespec& accessTime); bool contains(uint32_t entryHash) const; bool removeEntry(uint32_t entryHash); MultifileEntryStats getEntryStats(uint32_t entryHash); + void updateEntryTime(uint32_t entryHash, const timespec& newTime); bool createStatus(const std::string& baseDir); bool checkStatus(const std::string& baseDir); @@ -151,8 +177,14 @@ private: std::string mBuildId; uint32_t mCacheVersion; +#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + std::unordered_map<uint32_t, MultifileEntryStatsMapIter> mEntries; + MultifileEntryStatsMap mEntryStats; +#else std::unordered_set<uint32_t> mEntries; std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats; +#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE) + std::unordered_map<uint32_t, MultifileHotCache> mHotCache; size_t mMaxKeySize; @@ -161,6 +193,7 @@ private: size_t mMaxTotalEntries; size_t mTotalCacheSize; size_t mTotalCacheEntries; + size_t mTotalCacheSizeDivisor; size_t mHotCacheLimit; size_t mHotCacheEntryLimit; size_t mHotCacheSize; diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp index 90a0f1ee06..85fb29ec40 100644 --- a/opengl/libs/EGL/MultifileBlobCache_test.cpp +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -21,10 +21,15 @@ #include <fcntl.h> #include <gtest/gtest.h> #include <stdio.h> +#include <utils/JenkinsHash.h> #include <fstream> #include <memory> +#include <com_android_graphics_egl_flags.h> + +using namespace com::android::graphics::egl; + using namespace std::literals; namespace android { @@ -55,6 +60,7 @@ protected: std::vector<std::string> getCacheEntries(); void clearProperties(); + bool clearCache(); std::unique_ptr<TemporaryFile> mTempFile; std::unique_ptr<MultifileBlobCache> mMBC; @@ -314,7 +320,7 @@ std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() { struct stat info; if (stat(multifileDirName.c_str(), &info) == 0) { - // We have a multifile dir. Skip the status file and return the only entry. + // We have a multifile dir. Skip the status file and return the entries. DIR* dir; struct dirent* entry; if ((dir = opendir(multifileDirName.c_str())) != nullptr) { @@ -325,6 +331,7 @@ std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() { if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) { continue; } + // printf("Found entry: %s\n", entry->d_name); cacheEntries.push_back(multifileDirName + "/" + entry->d_name); } } else { @@ -458,6 +465,8 @@ TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) { // Set one entry mMBC->set("abcd", 4, "efgh", 4); + uint32_t initialCacheVersion = mMBC->getCurrentCacheVersion(); + // Close the cache so everything writes out mMBC->finish(); mMBC.reset(); @@ -466,7 +475,7 @@ TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) { ASSERT_EQ(getCacheEntries().size(), 1); // Set a debug cacheVersion - std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1); + std::string newCacheVersion = std::to_string(initialCacheVersion + 1); ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str())); ASSERT_TRUE( base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str())); @@ -503,4 +512,404 @@ TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) { ASSERT_EQ(getCacheEntries().size(), 0); } +// Ensure cache is correct when a key is reused +TEST_F(MultifileBlobCacheTest, SameKeyDifferentValues) { + if (!flags::multifile_blobcache_advanced_usage()) { + GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag"; + } + + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + + size_t startingSize = mMBC->getTotalSize(); + + // New cache should be empty + ASSERT_EQ(startingSize, 0); + + // Set an initial value + mMBC->set("ab", 2, "cdef", 4); + + // Grab the new size + size_t firstSize = mMBC->getTotalSize(); + + // Ensure the size went up + // Note: Checking for an exact size is challenging, as the + // file size can differ between platforms. + ASSERT_GT(firstSize, startingSize); + + // Verify the cache is correct + ASSERT_EQ(size_t(4), mMBC->get("ab", 2, buf, 4)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ('e', buf[2]); + ASSERT_EQ('f', buf[3]); + + // Now reuse the key with a smaller value + mMBC->set("ab", 2, "gh", 2); + + // Grab the new size + size_t secondSize = mMBC->getTotalSize(); + + // Ensure it decreased in size + ASSERT_LT(secondSize, firstSize); + + // Verify the cache is correct + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); + + // Now put back the original value + mMBC->set("ab", 2, "cdef", 4); + + // And we should get back a stable size + size_t finalSize = mMBC->getTotalSize(); + ASSERT_EQ(firstSize, finalSize); +} + +// Ensure cache is correct when a key is reused with large value size +TEST_F(MultifileBlobCacheTest, SameKeyLargeValues) { + if (!flags::multifile_blobcache_advanced_usage()) { + GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag"; + } + + // Create the cache with larger limits to stress test reuse + constexpr uint32_t kLocalMaxKeySize = 1 * 1024 * 1024; + constexpr uint32_t kLocalMaxValueSize = 4 * 1024 * 1024; + constexpr uint32_t kLocalMaxTotalSize = 32 * 1024 * 1024; + mMBC.reset(new MultifileBlobCache(kLocalMaxKeySize, kLocalMaxValueSize, kLocalMaxTotalSize, + kMaxTotalEntries, &mTempFile->path[0])); + + constexpr uint32_t kLargeValueCount = 8; + constexpr uint32_t kLargeValueSize = 64 * 1024; + + // Create a several really large values + unsigned char largeValue[kLargeValueCount][kLargeValueSize]; + for (int i = 0; i < kLargeValueCount; i++) { + for (int j = 0; j < kLargeValueSize; j++) { + // Fill the value with the index for uniqueness + largeValue[i][j] = i; + } + } + + size_t startingSize = mMBC->getTotalSize(); + + // New cache should be empty + ASSERT_EQ(startingSize, 0); + + // Cycle through the values and set them all in sequence + for (int i = 0; i < kLargeValueCount; i++) { + mMBC->set("abcd", 4, largeValue[i], kLargeValueSize); + } + + // Ensure we get the last one back + unsigned char outBuf[kLargeValueSize]; + mMBC->get("abcd", 4, outBuf, kLargeValueSize); + + for (int i = 0; i < kLargeValueSize; i++) { + // Buffer should contain highest index value + ASSERT_EQ(kLargeValueCount - 1, outBuf[i]); + } +} + +// Ensure cache eviction is LRU +TEST_F(MultifileBlobCacheTest, CacheEvictionIsLRU) { + if (!flags::multifile_blobcache_advanced_usage()) { + GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag"; + } + + // Fill the cache with exactly how much it can hold + int entry = 0; + for (entry = 0; entry < kMaxTotalEntries; entry++) { + // Use the index as the key and value + mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry)); + + int result = 0; + ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result))); + ASSERT_EQ(entry, result); + } + + // Ensure the cache is full + ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries); + + // Add one more entry to trigger eviction + size_t overflowEntry = kMaxTotalEntries; + mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry)); + + // Verify it contains the right amount, which will be one more than reduced size + // because we evict the cache before adding a new entry + size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor(); + ASSERT_EQ(mMBC->getTotalEntries(), evictionLimit + 1); + + // Ensure cache is as expected, with old entries removed, newer entries remaining + for (entry = 0; entry < kMaxTotalEntries; entry++) { + int result = 0; + mMBC->get(&entry, sizeof(entry), &result, sizeof(result)); + + if (entry < evictionLimit) { + // We should get no hits on evicted entries, i.e. the first added + ASSERT_EQ(result, 0); + } else { + // Above the limit should still be present + ASSERT_EQ(result, entry); + } + } +} + +// Ensure calling GET on an entry updates its access time, even if already in hotcache +TEST_F(MultifileBlobCacheTest, GetUpdatesAccessTime) { + if (!flags::multifile_blobcache_advanced_usage()) { + GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag"; + } + + // Fill the cache with exactly how much it can hold + int entry = 0; + int result = 0; + for (entry = 0; entry < kMaxTotalEntries; entry++) { + // Use the index as the key and value + mMBC->set(&entry, sizeof(entry), &entry, sizeof(entry)); + ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result))); + ASSERT_EQ(entry, result); + } + + // Ensure the cache is full + ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries); + + // GET the first few entries to update their access time + std::vector<int> accessedEntries = {1, 2, 3}; + for (int i = 0; i < accessedEntries.size(); i++) { + entry = accessedEntries[i]; + ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result))); + } + + // Add one more entry to trigger eviction + size_t overflowEntry = kMaxTotalEntries; + mMBC->set(&overflowEntry, sizeof(overflowEntry), &overflowEntry, sizeof(overflowEntry)); + + size_t evictionLimit = kMaxTotalEntries / mMBC->getTotalCacheSizeDivisor(); + + // Ensure cache is as expected, with old entries removed, newer entries remaining + for (entry = 0; entry < kMaxTotalEntries; entry++) { + int result = 0; + mMBC->get(&entry, sizeof(entry), &result, sizeof(result)); + + if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) != + accessedEntries.end()) { + // If this is one of the handful we accessed after filling the cache, + // they should still be in the cache because LRU + ASSERT_EQ(result, entry); + } else if (entry >= (evictionLimit + accessedEntries.size())) { + // If they were above the eviction limit (plus three for our updated entries), + // they should still be present + ASSERT_EQ(result, entry); + } else { + // Otherwise, they shold be evicted and no longer present + ASSERT_EQ(result, 0); + } + } + + // 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])); + + // Check the cache again, ensuring the updated access time made it to disk + for (entry = 0; entry < kMaxTotalEntries; entry++) { + int result = 0; + mMBC->get(&entry, sizeof(entry), &result, sizeof(result)); + if (std::find(accessedEntries.begin(), accessedEntries.end(), entry) != + accessedEntries.end()) { + ASSERT_EQ(result, entry); + } else if (entry >= (evictionLimit + accessedEntries.size())) { + ASSERT_EQ(result, entry); + } else { + ASSERT_EQ(result, 0); + } + } +} + +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); +} + +// Remove from cache when size is zero +TEST_F(MultifileBlobCacheTest, ZeroSizeRemovesEntry) { + if (!flags::multifile_blobcache_advanced_usage()) { + GTEST_SKIP() << "Skipping test that requires multifile_blobcache_advanced_usage flag"; + } + + // Put some entries in + int entry = 0; + int result = 0; + + uint32_t kEntryCount = 20; + + // 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); + } + + // Send some of them again with size zero + std::vector<int> removedEntries = {5, 10, 18}; + for (int i = 0; i < removedEntries.size(); i++) { + entry = removedEntries[i]; + mMBC->set(&entry, sizeof(entry), nullptr, 0); + } + + // Ensure they do not get a hit + for (int i = 0; i < removedEntries.size(); i++) { + entry = removedEntries[i]; + ASSERT_EQ(size_t(0), mMBC->get(&entry, sizeof(entry), &result, sizeof(result))); + } + + // And have been removed from disk + std::vector<std::string> diskEntries = getCacheEntries(); + ASSERT_EQ(diskEntries.size(), kEntryCount - removedEntries.size()); + for (int i = 0; i < removedEntries.size(); i++) { + entry = removedEntries[i]; + // Generate a hash for our removed entries and ensure they are not contained + // Note our entry and key and the same here, so we're hashing the key just like + // the multifile blobcache does. + uint32_t entryHash = + android::JenkinsHashMixBytes(0, reinterpret_cast<uint8_t*>(&entry), sizeof(entry)); + ASSERT_EQ(std::find(diskEntries.begin(), diskEntries.end(), std::to_string(entryHash)), + diskEntries.end()); + } + + // Ensure the others are still present + for (entry = 0; entry < kEntryCount; entry++) { + if (std::find(removedEntries.begin(), removedEntries.end(), entry) == + removedEntries.end()) { + ASSERT_EQ(sizeof(entry), mMBC->get(&entry, sizeof(entry), &result, sizeof(result))); + ASSERT_EQ(result, entry); + } + } +} + } // namespace android diff --git a/opengl/libs/EGL/egl_flags.aconfig b/opengl/libs/EGL/egl_flags.aconfig new file mode 100644 index 0000000000..115797071c --- /dev/null +++ b/opengl/libs/EGL/egl_flags.aconfig @@ -0,0 +1,13 @@ +package: "com.android.graphics.egl.flags" +container: "system" + +flag { + name: "multifile_blobcache_advanced_usage" + namespace: "gpu" + description: "This flag controls new behaviors to address bugs found via advanced usage" + bug: "380483358" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp index 4947e5ff6c..fe5f2a6de6 100644 --- a/opengl/libs/EGL/fuzzer/Android.bp +++ b/opengl/libs/EGL/fuzzer/Android.bp @@ -37,6 +37,7 @@ cc_fuzz { ], shared_libs: [ + "libegl_flags", "libz", ], |