diff options
Diffstat (limited to 'opengl/libs')
-rw-r--r-- | opengl/libs/Android.bp | 7 | ||||
-rw-r--r-- | opengl/libs/EGL/BlobCache.cpp | 6 | ||||
-rw-r--r-- | opengl/libs/EGL/FileBlobCache.cpp | 15 | ||||
-rw-r--r-- | opengl/libs/EGL/FileBlobCache.h | 5 | ||||
-rw-r--r-- | opengl/libs/EGL/Loader.cpp | 25 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.cpp | 735 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache.h | 174 | ||||
-rw-r--r-- | opengl/libs/EGL/MultifileBlobCache_test.cpp | 219 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_angle_platform.cpp | 13 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_cache.cpp | 166 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_cache.h | 30 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_display.cpp | 5 | ||||
-rw-r--r-- | opengl/libs/EGL/egl_platform_entries.cpp | 11 | ||||
-rw-r--r-- | opengl/libs/EGL/fuzzer/Android.bp | 42 | ||||
-rw-r--r-- | opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp | 158 |
15 files changed, 1564 insertions, 47 deletions
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp index 62cf2555ca..16de3908f8 100644 --- a/opengl/libs/Android.bp +++ b/opengl/libs/Android.bp @@ -144,6 +144,7 @@ cc_library_static { srcs: [ "EGL/BlobCache.cpp", "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", ], export_include_dirs: ["EGL"], } @@ -204,6 +205,12 @@ cc_test { srcs: [ "EGL/BlobCache.cpp", "EGL/BlobCache_test.cpp", + "EGL/FileBlobCache.cpp", + "EGL/MultifileBlobCache.cpp", + "EGL/MultifileBlobCache_test.cpp", + ], + shared_libs: [ + "libutils", ], } diff --git a/opengl/libs/EGL/BlobCache.cpp b/opengl/libs/EGL/BlobCache.cpp index aecfc6b077..b3a4bc19fd 100644 --- a/opengl/libs/EGL/BlobCache.cpp +++ b/opengl/libs/EGL/BlobCache.cpp @@ -15,6 +15,7 @@ */ //#define LOG_NDEBUG 0 +#define ATRACE_TAG ATRACE_TAG_GRAPHICS #include "BlobCache.h" @@ -22,6 +23,7 @@ #include <errno.h> #include <inttypes.h> #include <log/log.h> +#include <utils/Trace.h> #include <chrono> @@ -230,6 +232,8 @@ int BlobCache::flatten(void* buffer, size_t size) const { } int BlobCache::unflatten(void const* buffer, size_t size) { + ATRACE_NAME("BlobCache::unflatten"); + // All errors should result in the BlobCache being in an empty state. clear(); @@ -293,6 +297,8 @@ long int BlobCache::blob_random() { } void BlobCache::clean() { + ATRACE_NAME("BlobCache::clean"); + // Remove a random cache entry until the total cache size gets below half // the maximum total cache size. while (mTotalSize > mMaxTotalSize / 2) { diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp index 751f3be81a..4a0fac4ce5 100644 --- a/opengl/libs/EGL/FileBlobCache.cpp +++ b/opengl/libs/EGL/FileBlobCache.cpp @@ -14,6 +14,8 @@ ** limitations under the License. */ +#define ATRACE_TAG ATRACE_TAG_GRAPHICS + #include "FileBlobCache.h" #include <errno.h> @@ -24,6 +26,7 @@ #include <unistd.h> #include <log/log.h> +#include <utils/Trace.h> // Cache file header static const char* cacheFileMagic = "EGL$"; @@ -31,7 +34,7 @@ static const size_t cacheFileHeaderSize = 8; namespace android { -static uint32_t crc32c(const uint8_t* buf, size_t len) { +uint32_t crc32c(const uint8_t* buf, size_t len) { const uint32_t polyBits = 0x82F63B78; uint32_t r = 0; for (size_t i = 0; i < len; i++) { @@ -51,6 +54,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT const std::string& filename) : BlobCache(maxKeySize, maxValueSize, maxTotalSize) , mFilename(filename) { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t headerSize = cacheFileHeaderSize; @@ -117,6 +122,8 @@ FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxT } void FileBlobCache::writeToFile() { + ATRACE_CALL(); + if (mFilename.length() > 0) { size_t cacheSize = getFlattenedSize(); size_t headerSize = cacheFileHeaderSize; @@ -185,4 +192,10 @@ void FileBlobCache::writeToFile() { } } +size_t FileBlobCache::getSize() { + if (mFilename.length() > 0) { + return getFlattenedSize() + cacheFileHeaderSize; + } + return 0; +} } diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h index 393703f234..f083b0d6ca 100644 --- a/opengl/libs/EGL/FileBlobCache.h +++ b/opengl/libs/EGL/FileBlobCache.h @@ -22,6 +22,8 @@ namespace android { +uint32_t crc32c(const uint8_t* buf, size_t len); + class FileBlobCache : public BlobCache { public: // FileBlobCache attempts to load the saved cache contents from disk into @@ -33,6 +35,9 @@ public: // disk. void writeToFile(); + // Return the total size of the cache + size_t getSize(); + private: // mFilename is the name of the file for storing cache contents. std::string mFilename; diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp index 6ea400721d..2c3ce16f66 100644 --- a/opengl/libs/EGL/Loader.cpp +++ b/opengl/libs/EGL/Loader.cpp @@ -139,9 +139,10 @@ static void* load_wrapper(const char* path) { static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl"; -static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = { - DRIVER_SUFFIX_PROPERTY, - "ro.board.platform", +static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = { + "persist.graphics.egl", + DRIVER_SUFFIX_PROPERTY, + "ro.board.platform", }; static bool should_unload_system_driver(egl_connection_t* cnx) { @@ -208,8 +209,7 @@ void* Loader::open(egl_connection_t* cnx) ATRACE_CALL(); const nsecs_t openTime = systemTime(); - if (!android::GraphicsEnv::getInstance().angleIsSystemDriver() && - should_unload_system_driver(cnx)) { + if (should_unload_system_driver(cnx)) { unload_system_driver(cnx); } @@ -218,12 +218,8 @@ void* Loader::open(egl_connection_t* cnx) return cnx->dso; } - // Firstly, try to load ANGLE driver, unless we know that we shouldn't. - bool shouldForceLegacyDriver = android::GraphicsEnv::getInstance().shouldForceLegacyDriver(); - driver_t* hnd = nullptr; - if (!shouldForceLegacyDriver) { - hnd = attempt_to_load_angle(cnx); - } + // Firstly, try to load ANGLE driver. + driver_t* hnd = attempt_to_load_angle(cnx); if (!hnd) { // Secondly, try to load from driver apk. @@ -285,8 +281,10 @@ void* Loader::open(egl_connection_t* cnx) } LOG_ALWAYS_FATAL_IF(!hnd, - "couldn't find an OpenGL ES implementation, make sure you set %s or %s", - HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1]); + "couldn't find an OpenGL ES implementation, make sure one of %s, %s and %s " + "is set", + HAL_SUBNAME_KEY_PROPERTIES[0], HAL_SUBNAME_KEY_PROPERTIES[1], + HAL_SUBNAME_KEY_PROPERTIES[2]); if (!cnx->libEgl) { cnx->libEgl = load_wrapper(EGL_WRAPPER_DIR "/libEGL.so"); @@ -499,7 +497,6 @@ static void* load_angle(const char* kind, android_namespace_t* ns) { void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo); if (so) { - ALOGD("dlopen_ext from APK (%s) success at %p", name.c_str(), so); return so; } else { ALOGE("dlopen_ext(\"%s\") failed: %s", name.c_str(), dlerror()); diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp new file mode 100644 index 0000000000..7ffdac7ea7 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.cpp @@ -0,0 +1,735 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +// #define LOG_NDEBUG 0 + +#include "MultifileBlobCache.h" + +#include <dirent.h> +#include <fcntl.h> +#include <inttypes.h> +#include <log/log.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <utime.h> + +#include <algorithm> +#include <chrono> +#include <limits> +#include <locale> + +#include <utils/JenkinsHash.h> + +using namespace std::literals; + +constexpr uint32_t kMultifileMagic = 'MFB$'; +constexpr uint32_t kCrcPlaceholder = 0; + +namespace { + +// Helper function to close entries or free them +void freeHotCacheEntry(android::MultifileHotCache& entry) { + if (entry.entryFd != -1) { + // If we have an fd, then this entry was added to hot cache via INIT or GET + // We need to unmap and close the entry + munmap(entry.entryBuffer, entry.entrySize); + close(entry.entryFd); + } else { + // Otherwise, this was added to hot cache during SET, so it was never mapped + // and fd was only on the deferred thread. + delete[] entry.entryBuffer; + } +} + +} // namespace + +namespace android { + +MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, + const std::string& baseDir) + : mInitialized(false), + mMaxKeySize(maxKeySize), + mMaxValueSize(maxValueSize), + mMaxTotalSize(maxTotalSize), + mTotalCacheSize(0), + mHotCacheLimit(0), + mHotCacheSize(0), + mWorkerThreadIdle(true) { + if (baseDir.empty()) { + ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early."); + return; + } + + // Establish the name of our multifile directory + mMultifileDirName = baseDir + ".multifile"; + + // Set the hotcache limit to be large enough to contain one max entry + // This ensure the hot cache is always large enough for single entry + mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader); + + ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu", + mMaxKeySize, mMaxValueSize); + + // Initialize our cache with the contents of the directory + mTotalCacheSize = 0; + + // Create the worker thread + mTaskThread = std::thread(&MultifileBlobCache::processTasks, this); + + // See if the dir exists, and initialize using its contents + struct stat st; + if (stat(mMultifileDirName.c_str(), &st) == 0) { + // Read all the files and gather details, then preload their contents + DIR* dir; + struct dirent* entry; + if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) { + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name == "."s || entry->d_name == ".."s) { + continue; + } + + std::string entryName = entry->d_name; + std::string fullPath = mMultifileDirName + "/" + entryName; + + // The filename is the same as the entryHash + uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10)); + + ALOGV("INIT: Checking entry %u", entryHash); + + // Look up the details of the file + struct stat st; + if (stat(fullPath.c_str(), &st) != 0) { + ALOGE("Failed to stat %s", fullPath.c_str()); + return; + } + + // If the cache entry is damaged or no good, remove it + if (st.st_size <= 0 || st.st_atime <= 0) { + ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Open the file so we can read its header + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + // Read the beginning of the file to get header + MultifileHeader header; + size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader)); + if (result != sizeof(MultifileHeader)) { + ALOGE("Error reading MultifileHeader from cache entry (%s): %s", + fullPath.c_str(), std::strerror(errno)); + return; + } + + // Verify header magic + if (header.magic != kMultifileMagic) { + ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // Note: Converting from off_t (signed) to size_t (unsigned) + size_t fileSize = static_cast<size_t>(st.st_size); + + // Memory map the file + uint8_t* mappedEntry = reinterpret_cast<uint8_t*>( + mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (mappedEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + return; + } + + // Ensure we have a good CRC + if (header.crc != + crc32c(mappedEntry + sizeof(MultifileHeader), + fileSize - sizeof(MultifileHeader))) { + ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + // If the cache entry is damaged or no good, remove it + if (header.keySize <= 0 || header.valueSize <= 0) { + ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), " + "removing.", + entryHash, header.keySize, header.valueSize); + if (remove(fullPath.c_str()) != 0) { + ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno)); + } + continue; + } + + 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); + + // Preload the entry for fast retrieval + if ((mHotCacheSize + fileSize) < mHotCacheLimit) { + ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for " + "entryHash %u", + fd, mappedEntry, entryHash); + + // Track the details of the preload so they can be retrieved later + if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) { + ALOGE("INIT Failed to add %u to hot cache", entryHash); + munmap(mappedEntry, fileSize); + close(fd); + return; + } + } else { + // If we're not keeping it in hot cache, unmap it now + munmap(mappedEntry, fileSize); + close(fd); + } + } + closedir(dir); + } else { + ALOGE("Unable to open filename: %s", mMultifileDirName.c_str()); + } + } else { + // If the multifile directory does not exist, create it and start from scratch + if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) { + ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno); + } + } + + mInitialized = true; +} + +MultifileBlobCache::~MultifileBlobCache() { + if (!mInitialized) { + return; + } + + // Inform the worker thread we're done + ALOGV("DESCTRUCTOR: Shutting down worker thread"); + DeferredTask task(TaskCommand::Exit); + queueTask(std::move(task)); + + // Wait for it to complete + ALOGV("DESCTRUCTOR: Waiting for worker thread to complete"); + waitForWorkComplete(); + if (mTaskThread.joinable()) { + mTaskThread.join(); + } +} + +// Set will add the entry to hot cache and start a deferred process to write it to disk +void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return; + } + + // 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); + + size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize; + + // If we're going to be over the cache limit, kick off a trim to clear space + if (getTotalSize() + fileSize > mMaxTotalSize) { + ALOGV("SET: Cache is full, calling trimCache to clear space"); + trimCache(); + } + + ALOGV("SET: Add %u to cache", entryHash); + + uint8_t* buffer = new uint8_t[fileSize]; + + // Write placeholders for magic and CRC until deferred thread completes the write + android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize}; + memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header), + sizeof(android::MultifileHeader)); + // Write the key and value after the header + memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key), + keySize); + 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); + + // Keep the entry in hot cache for quick retrieval + ALOGV("SET: Adding %u to hot cache.", entryHash); + + // Sending -1 as the fd indicates we don't have an fd for this + if (!addToHotCache(entryHash, -1, buffer, fileSize)) { + ALOGE("SET: Failed to add %u to hot cache", entryHash); + delete[] buffer; + return; + } + + // Track that we're creating a pending write for this entry + // Include the buffer to handle the case when multiple writes are pending for an entry + { + // Synchronize access to deferred write status + std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex); + mDeferredWrites.insert(std::make_pair(entryHash, buffer)); + } + + // Create deferred task to write to storage + ALOGV("SET: Adding task to queue."); + DeferredTask task(TaskCommand::WriteToDisk); + task.initWriteToDisk(entryHash, fullPath, buffer, fileSize); + queueTask(std::move(task)); +} + +// Get will check the hot cache, then load it from disk if needed +EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize) { + if (!mInitialized) { + return 0; + } + + // Ensure key and value are under their limits + if (keySize > mMaxKeySize || valueSize > mMaxValueSize) { + ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize, + valueSize, mMaxValueSize); + return 0; + } + + // 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); + + // See if we have this file + if (!contains(entryHash)) { + ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash); + return 0; + } + + // Look up the data for this entry + MultifileEntryStats entryStats = getEntryStats(entryHash); + + size_t cachedValueSize = entryStats.valueSize; + if (cachedValueSize > valueSize) { + ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required" + "size (%zu)", + valueSize, entryHash, cachedValueSize); + return cachedValueSize; + } + + // We have the file and have enough room to write it out, return the entry + ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash); + + // Look up the size of the file + size_t fileSize = entryStats.fileSize; + if (keySize > fileSize) { + ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified " + "file", + keySize, fileSize); + return 0; + } + + std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash); + + // Open the hashed filename path + uint8_t* cacheEntry = 0; + + // Check hot cache + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("GET: HotCache HIT for entry %u", entryHash); + cacheEntry = mHotCache[entryHash].entryBuffer; + } else { + ALOGV("GET: HotCache MISS for entry: %u", entryHash); + + // Wait for writes to complete if there is an outstanding write for this entry + bool wait = false; + { + // Synchronize access to deferred write status + std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex); + wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end(); + } + + if (wait) { + ALOGV("GET: Waiting for write to complete for %u", entryHash); + waitForWorkComplete(); + } + + // Open the entry file + int fd = open(fullPath.c_str(), O_RDONLY); + if (fd == -1) { + ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(), + std::strerror(errno)); + return 0; + } + + // Memory map the file + cacheEntry = + reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0)); + if (cacheEntry == MAP_FAILED) { + ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno)); + close(fd); + return 0; + } + + ALOGV("GET: Adding %u to hot cache", entryHash); + if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) { + ALOGE("GET: Failed to add %u to hot cache", entryHash); + return 0; + } + + cacheEntry = mHotCache[entryHash].entryBuffer; + } + + // Ensure the header matches + MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry); + if (header->keySize != keySize || header->valueSize != valueSize) { + ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared " + "to cache header values for fullPath: %s", + keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str()); + removeFromHotCache(entryHash); + return 0; + } + + // Compare the incoming key with our stored version (the beginning of the entry) + uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader); + int compare = memcmp(cachedKey, key, keySize); + if (compare != 0) { + ALOGW("Cached key and new key do not match! This is a hash collision or modified file"); + removeFromHotCache(entryHash); + return 0; + } + + // Remaining entry following the key is the value + uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader)); + memcpy(value, cachedValue, cachedValueSize); + + return cachedValueSize; +} + +void MultifileBlobCache::finish() { + if (!mInitialized) { + return; + } + + // Wait for all deferred writes to complete + ALOGV("FINISH: Waiting for work to complete."); + waitForWorkComplete(); + + // Close all entries in the hot cache + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t entryHash = hotCacheIter->first; + MultifileHotCache entry = hotCacheIter->second; + + ALOGV("FINISH: Closing hot cache entry for %u", entryHash); + freeHotCacheEntry(entry); + + mHotCache.erase(hotCacheIter++); + } +} + +void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime) { + mEntries.insert(entryHash); + mEntryStats[entryHash] = {valueSize, fileSize, accessTime}; +} + +bool MultifileBlobCache::contains(uint32_t hashEntry) const { + return mEntries.find(hashEntry) != mEntries.end(); +} + +MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) { + return mEntryStats[entryHash]; +} + +void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize += fileSize; +} + +void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) { + mTotalCacheSize -= fileSize; +} + +bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer, + size_t newEntrySize) { + ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash); + + // Clear space if we need to + if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) { + ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for " + "mHotCacheLimit " + "(%zu), freeing up space for %u", + mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash); + waitForWorkComplete(); + + // Free up old entries until under the limit + for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) { + uint32_t oldEntryHash = hotCacheIter->first; + MultifileHotCache oldEntry = hotCacheIter->second; + + // Move our iterator before deleting the entry + hotCacheIter++; + if (!removeFromHotCache(oldEntryHash)) { + ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash); + return false; + } + + // Clear at least half the hot cache + if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) { + ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize); + break; + } + } + } + + // Track it + mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize}; + mHotCacheSize += newEntrySize; + + ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize); + + return true; +} + +bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) { + if (mHotCache.find(entryHash) != mHotCache.end()) { + ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash); + + // Wait for all the files to complete writing so our hot cache is accurate + ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash); + waitForWorkComplete(); + + ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash); + MultifileHotCache entry = mHotCache[entryHash]; + freeHotCacheEntry(entry); + + // Delete the entry from our tracking + mHotCacheSize -= entry.entrySize; + mHotCache.erase(entryHash); + + return true; + } + + return false; +} + +bool MultifileBlobCache::applyLRU(size_t cacheLimit) { + // Walk through our map of sorted last access times and remove files until under the limit + for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) { + uint32_t entryHash = cacheEntryIter->first; + + 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; + } + + // 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); + return false; + } + + // See if it has been reduced enough + size_t totalCacheSize = getTotalSize(); + if (totalCacheSize <= cacheLimit) { + // Success + ALOGV("LRU: Reduced cache to %zu", totalCacheSize); + return true; + } + } + + ALOGV("LRU: Cache is empty"); + return false; +} + +// 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 +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 + ALOGV("TRIM: Waiting for work to complete."); + waitForWorkComplete(); + + ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor); + if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) { + ALOGE("Error when clearing multifile shader cache"); + return; + } +} + +// This function performs a task. It only knows how to write files to disk, +// but it could be expanded if needed. +void MultifileBlobCache::processTask(DeferredTask& task) { + switch (task.getTaskCommand()) { + case TaskCommand::Exit: { + ALOGV("DEFERRED: Shutting down"); + return; + } + case TaskCommand::WriteToDisk: { + uint32_t entryHash = task.getEntryHash(); + std::string& fullPath = task.getFullPath(); + uint8_t* buffer = task.getBuffer(); + size_t bufferSize = task.getBufferSize(); + + // 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; + } + + ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str()); + + // Add CRC check to the header (always do this last!) + MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer); + header->crc = + crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader)); + + ssize_t result = write(fd, buffer, bufferSize); + if (result != bufferSize) { + ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(), + std::strerror(errno)); + return; + } + + ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str()); + close(fd); + + // Erase the entry from mDeferredWrites + // Since there could be multiple outstanding writes for an entry, find the matching one + { + // Synchronize access to deferred write status + std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex); + typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter; + std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash); + for (entryIter it = iterPair.first; it != iterPair.second; ++it) { + if (it->second == buffer) { + ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, + it->second); + mDeferredWrites.erase(it); + break; + } + } + } + + return; + } + default: { + ALOGE("DEFERRED: Unhandled task type"); + return; + } + } +} + +// This function will wait until tasks arrive, then execute them +// If the exit command is submitted, the loop will terminate +void MultifileBlobCache::processTasksImpl(bool* exitThread) { + while (true) { + std::unique_lock<std::mutex> lock(mWorkerMutex); + if (mTasks.empty()) { + ALOGV("WORKER: No tasks available, waiting"); + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_all(); + // Only wake if notified and command queue is not empty + mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); }); + } + + ALOGV("WORKER: Task available, waking up."); + mWorkerThreadIdle = false; + DeferredTask task = std::move(mTasks.front()); + mTasks.pop(); + + if (task.getTaskCommand() == TaskCommand::Exit) { + ALOGV("WORKER: Exiting work loop."); + *exitThread = true; + mWorkerThreadIdle = true; + mWorkerIdleCondition.notify_one(); + return; + } + + lock.unlock(); + processTask(task); + } +} + +// Process tasks until the exit task is submitted +void MultifileBlobCache::processTasks() { + while (true) { + bool exitThread = false; + processTasksImpl(&exitThread); + if (exitThread) { + break; + } + } +} + +// Add a task to the queue to be processed by the worker thread +void MultifileBlobCache::queueTask(DeferredTask&& task) { + std::lock_guard<std::mutex> queueLock(mWorkerMutex); + mTasks.emplace(std::move(task)); + mWorkAvailableCondition.notify_one(); +} + +// Wait until all tasks have been completed +void MultifileBlobCache::waitForWorkComplete() { + std::unique_lock<std::mutex> lock(mWorkerMutex); + mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); }); +} + +}; // namespace android diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h new file mode 100644 index 0000000000..5e527dcf35 --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache.h @@ -0,0 +1,174 @@ +/* + ** Copyright 2022, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#ifndef ANDROID_MULTIFILE_BLOB_CACHE_H +#define ANDROID_MULTIFILE_BLOB_CACHE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <android-base/thread_annotations.h> +#include <future> +#include <map> +#include <queue> +#include <string> +#include <thread> +#include <unordered_map> +#include <unordered_set> + +#include "FileBlobCache.h" + +namespace android { + +struct MultifileHeader { + uint32_t magic; + uint32_t crc; + EGLsizeiANDROID keySize; + EGLsizeiANDROID valueSize; +}; + +struct MultifileEntryStats { + EGLsizeiANDROID valueSize; + size_t fileSize; + time_t accessTime; +}; + +struct MultifileHotCache { + int entryFd; + uint8_t* entryBuffer; + size_t entrySize; +}; + +enum class TaskCommand { + Invalid = 0, + WriteToDisk, + Exit, +}; + +class DeferredTask { +public: + DeferredTask(TaskCommand command) + : mCommand(command), mEntryHash(0), mBuffer(nullptr), mBufferSize(0) {} + + TaskCommand getTaskCommand() { return mCommand; } + + void initWriteToDisk(uint32_t entryHash, std::string fullPath, uint8_t* buffer, + size_t bufferSize) { + mCommand = TaskCommand::WriteToDisk; + mEntryHash = entryHash; + mFullPath = std::move(fullPath); + mBuffer = buffer; + mBufferSize = bufferSize; + } + + uint32_t getEntryHash() { return mEntryHash; } + std::string& getFullPath() { return mFullPath; } + uint8_t* getBuffer() { return mBuffer; } + size_t getBufferSize() { return mBufferSize; }; + +private: + TaskCommand mCommand; + + // Parameters for WriteToDisk + uint32_t mEntryHash; + std::string mFullPath; + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class MultifileBlobCache { +public: + MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize, + const std::string& baseDir); + ~MultifileBlobCache(); + + void set(const void* key, EGLsizeiANDROID keySize, const void* value, + EGLsizeiANDROID valueSize); + EGLsizeiANDROID get(const void* key, EGLsizeiANDROID keySize, void* value, + EGLsizeiANDROID valueSize); + + void finish(); + + size_t getTotalSize() const { return mTotalCacheSize; } + +private: + void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize, + time_t accessTime); + bool contains(uint32_t entryHash) const; + bool removeEntry(uint32_t entryHash); + MultifileEntryStats getEntryStats(uint32_t entryHash); + + size_t getFileSize(uint32_t entryHash); + size_t getValueSize(uint32_t entryHash); + + void increaseTotalCacheSize(size_t fileSize); + void decreaseTotalCacheSize(size_t fileSize); + + bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize); + bool removeFromHotCache(uint32_t entryHash); + + void trimCache(); + bool applyLRU(size_t cacheLimit); + + bool mInitialized; + std::string mMultifileDirName; + + std::unordered_set<uint32_t> mEntries; + std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats; + std::unordered_map<uint32_t, MultifileHotCache> mHotCache; + + size_t mMaxKeySize; + size_t mMaxValueSize; + size_t mMaxTotalSize; + size_t mTotalCacheSize; + size_t mHotCacheLimit; + size_t mHotCacheEntryLimit; + size_t mHotCacheSize; + + // Below are the components used for deferred writes + + // Track whether we have pending writes for an entry + std::mutex mDeferredWriteStatusMutex; + std::multimap<uint32_t, uint8_t*> mDeferredWrites GUARDED_BY(mDeferredWriteStatusMutex); + + // Functions to work through tasks in the queue + void processTasks(); + void processTasksImpl(bool* exitThread); + void processTask(DeferredTask& task); + + // Used by main thread to create work for the worker thread + void queueTask(DeferredTask&& task); + + // Used by main thread to wait for worker thread to complete all outstanding work. + void waitForWorkComplete(); + + std::thread mTaskThread; + std::queue<DeferredTask> mTasks; + std::mutex mWorkerMutex; + + // This condition will block the worker thread until a task is queued + std::condition_variable mWorkAvailableCondition; + + // This condition will block the main thread while the worker thread still has tasks + std::condition_variable mWorkerIdleCondition; + + // This bool will track whether all tasks have been completed + bool mWorkerThreadIdle; +}; + +}; // namespace android + +#endif // ANDROID_MULTIFILE_BLOB_CACHE_H diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp new file mode 100644 index 0000000000..dbee13bb2e --- /dev/null +++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp @@ -0,0 +1,219 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include <android-base/test_utils.h> +#include <fcntl.h> +#include <gtest/gtest.h> +#include <stdio.h> + +#include <memory> + +namespace android { + +template <typename T> +using sp = std::shared_ptr<T>; + +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; +constexpr size_t kMaxTotalSize = 32 * 1024; + +class MultifileBlobCacheTest : public ::testing::Test { +protected: + virtual void SetUp() { + mTempFile.reset(new TemporaryFile()); + mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, + &mTempFile->path[0])); + } + + virtual void TearDown() { mMBC.reset(); } + + std::unique_ptr<TemporaryFile> mTempFile; + std::unique_ptr<MultifileBlobCache> mMBC; +}; + +TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheTwoValuesSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + mMBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mMBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetSetTwiceSucceeds) { + unsigned char buf[2] = {0xee, 0xee}; + mMBC->set("ab", 2, "cd", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + // Use the same key, but different value + mMBC->set("ab", 2, "ef", 2); + ASSERT_EQ(size_t(2), mMBC->get("ab", 2, buf, 2)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesInsideBounds) { + unsigned char buf[6] = {0xee, 0xee, 0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf + 1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(MultifileBlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + unsigned char buf[3] = {0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(MultifileBlobCacheTest, GetDoesntAccessNullBuffer) { + mMBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, nullptr, 0)); +} + +TEST_F(MultifileBlobCacheTest, MultipleSetsCacheLatestValue) { + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + unsigned char buf[kMaxValueSize + 1] = {0xee, 0xee, 0xee, 0xee}; + mMBC->set("abcd", 4, "efgh", 4); + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + ASSERT_EQ(size_t(4), mMBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[kMaxKeySize + 1]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize + 1; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize + 1, "bbbb", 4); + ASSERT_EQ(size_t(0), mMBC->get(key, kMaxKeySize + 1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(MultifileBlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[kMaxValueSize + 1]; + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize + 1); + for (int i = 0; i < kMaxValueSize + 1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mMBC->get("abcd", 4, buf, kMaxValueSize + 1)); + for (int i = 0; i < kMaxValueSize + 1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[kMaxKeySize]; + unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee}; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + mMBC->set(key, kMaxKeySize, "wxyz", 4); + ASSERT_EQ(size_t(4), mMBC->get(key, kMaxKeySize, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(MultifileBlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get("abcd", 4, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMaxKeyAndValueSizeSucceeds) { + char key[kMaxKeySize]; + for (int i = 0; i < kMaxKeySize; i++) { + key[i] = 'a'; + } + char buf[kMaxValueSize]; + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 'b'; + } + mMBC->set(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + buf[i] = 0xee; + } + mMBC->get(key, kMaxKeySize, buf, kMaxValueSize); + for (int i = 0; i < kMaxValueSize; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + unsigned char buf[1] = {0xee}; + mMBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mMBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +} // namespace android diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp index c0ead5096f..9a6bb7a61c 100644 --- a/opengl/libs/EGL/egl_angle_platform.cpp +++ b/opengl/libs/EGL/egl_angle_platform.cpp @@ -68,9 +68,9 @@ static void logInfo(PlatformMethods* /*platform*/, const char* infoMessage) { static TraceEventHandle addTraceEvent( PlatformMethods* /**platform*/, char phase, const unsigned char* /*category_group_enabled*/, - const char* name, unsigned long long /*id*/, double /*timestamp*/, int /*num_args*/, - const char** /*arg_names*/, const unsigned char* /*arg_types*/, - const unsigned long long* /*arg_values*/, unsigned char /*flags*/) { + const char* name, unsigned long long /*id*/, double /*timestamp*/, int num_args, + const char** arg_names, const unsigned char* /*arg_types*/, + const unsigned long long* arg_values, unsigned char /*flags*/) { switch (phase) { case 'B': { ATRACE_BEGIN(name); @@ -84,6 +84,13 @@ static TraceEventHandle addTraceEvent( ATRACE_NAME(name); break; } + case 'C': { + for(int i=0; i<num_args ; i++) + { + ATRACE_INT(arg_names[i],arg_values[i]); + } + break; + } default: // Could handle other event types here break; diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp index efa67dbc1c..1b68344cb1 100644 --- a/opengl/libs/EGL/egl_cache.cpp +++ b/opengl/libs/EGL/egl_cache.cpp @@ -14,8 +14,12 @@ ** limitations under the License. */ +// #define LOG_NDEBUG 0 + #include "egl_cache.h" +#include <android-base/properties.h> +#include <inttypes.h> #include <log/log.h> #include <private/EGL/cache.h> #include <unistd.h> @@ -25,13 +29,18 @@ #include "../egl_impl.h" #include "egl_display.h" -// Cache size limits. -static const size_t maxKeySize = 12 * 1024; -static const size_t maxValueSize = 64 * 1024; -static const size_t maxTotalSize = 2 * 1024 * 1024; +// Monolithic cache size limits. +static const size_t kMaxMonolithicKeySize = 12 * 1024; +static const size_t kMaxMonolithicValueSize = 64 * 1024; +static const size_t kMaxMonolithicTotalSize = 2 * 1024 * 1024; + +// The time in seconds to wait before saving newly inserted monolithic cache entries. +static const unsigned int kDeferredMonolithicSaveDelay = 4; -// The time in seconds to wait before saving newly inserted cache entries. -static const unsigned int deferredSaveDelay = 4; +// Multifile cache size limits +constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024; +constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024; +constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024; namespace android { @@ -58,7 +67,8 @@ static EGLsizeiANDROID getBlob(const void* key, EGLsizeiANDROID keySize, void* v // // egl_cache_t definition // -egl_cache_t::egl_cache_t() : mInitialized(false) {} +egl_cache_t::egl_cache_t() + : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {} egl_cache_t::~egl_cache_t() {} @@ -72,7 +82,7 @@ void egl_cache_t::initialize(egl_display_t* display) { std::lock_guard<std::mutex> lock(mMutex); egl_connection_t* const cnx = &gEGLImpl; - if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { + if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) { const char* exts = display->disp.queryString.extensions; size_t bcExtLen = strlen(BC_EXT_STR); size_t extsLen = strlen(exts); @@ -110,6 +120,11 @@ void egl_cache_t::terminate() { mBlobCache->writeToFile(); } mBlobCache = nullptr; + if (mMultifileBlobCache) { + mMultifileBlobCache->finish(); + } + mMultifileBlobCache = nullptr; + mInitialized = false; } void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* value, @@ -121,21 +136,28 @@ void egl_cache_t::setBlob(const void* key, EGLsizeiANDROID keySize, const void* return; } + updateMode(); + if (mInitialized) { - BlobCache* bc = getBlobCacheLocked(); - bc->set(key, keySize, value, valueSize); - - if (!mSavePending) { - mSavePending = true; - std::thread deferredSaveThread([this]() { - sleep(deferredSaveDelay); - std::lock_guard<std::mutex> lock(mMutex); - if (mInitialized && mBlobCache) { - mBlobCache->writeToFile(); - } - mSavePending = false; - }); - deferredSaveThread.detach(); + if (mMultifileMode) { + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + mbc->set(key, keySize, value, valueSize); + } else { + BlobCache* bc = getBlobCacheLocked(); + bc->set(key, keySize, value, valueSize); + + if (!mSavePending) { + mSavePending = true; + std::thread deferredSaveThread([this]() { + sleep(kDeferredMonolithicSaveDelay); + std::lock_guard<std::mutex> lock(mMutex); + if (mInitialized && mBlobCache) { + mBlobCache->writeToFile(); + } + mSavePending = false; + }); + deferredSaveThread.detach(); + } } } } @@ -145,27 +167,119 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v std::lock_guard<std::mutex> lock(mMutex); if (keySize < 0 || valueSize < 0) { - ALOGW("EGL_ANDROID_blob_cache set: negative sizes are not allowed"); + ALOGW("EGL_ANDROID_blob_cache get: negative sizes are not allowed"); return 0; } + updateMode(); + if (mInitialized) { - BlobCache* bc = getBlobCacheLocked(); - return bc->get(key, keySize, value, valueSize); + if (mMultifileMode) { + MultifileBlobCache* mbc = getMultifileBlobCacheLocked(); + return mbc->get(key, keySize, value, valueSize); + } else { + BlobCache* bc = getBlobCacheLocked(); + return bc->get(key, keySize, value, valueSize); + } } + return 0; } +void egl_cache_t::setCacheMode(EGLCacheMode cacheMode) { + mMultifileMode = (cacheMode == EGLCacheMode::Multifile); +} + void egl_cache_t::setCacheFilename(const char* filename) { std::lock_guard<std::mutex> lock(mMutex); mFilename = filename; } +void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) { + std::lock_guard<std::mutex> lock(mMutex); + + if (!mMultifileMode) { + // If we're not in multifile mode, ensure the cache limit is only being lowered, + // not increasing above the hard coded platform limit + if (cacheByteLimit > kMaxMonolithicTotalSize) { + return; + } + } + + mCacheByteLimit = cacheByteLimit; +} + +size_t egl_cache_t::getCacheSize() { + std::lock_guard<std::mutex> lock(mMutex); + if (mMultifileBlobCache) { + return mMultifileBlobCache->getTotalSize(); + } + if (mBlobCache) { + return mBlobCache->getSize(); + } + return 0; +} + +void egl_cache_t::updateMode() { + // We don't set the mode in the constructor because these checks have + // a non-trivial cost, and not all processes that instantiate egl_cache_t + // will use it. + + // If we've already set the mode, skip these checks + static bool checked = false; + if (checked) { + return; + } + checked = true; + + // Check the device config to decide whether multifile should be used + if (base::GetBoolProperty("ro.egl.blobcache.multifile", false)) { + mMultifileMode = true; + ALOGV("Using multifile EGL blobcache"); + } + + // Allow forcing the mode for debug purposes + std::string mode = base::GetProperty("debug.egl.blobcache.multifile", ""); + if (mode == "true") { + ALOGV("Forcing multifile cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = true; + } else if (mode == "false") { + ALOGV("Forcing monolithic cache due to debug.egl.blobcache.multifile == %s", mode.c_str()); + mMultifileMode = false; + } + + if (mMultifileMode) { + mCacheByteLimit = static_cast<size_t>( + base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit", + kMaxMultifileTotalSize)); + + // Check for a debug value + int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.multifile_limit", -1); + if (debugCacheSize >= 0) { + ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.multifile_limit", + mCacheByteLimit, debugCacheSize); + mCacheByteLimit = debugCacheSize; + } + + ALOGV("Using multifile EGL blobcache limit of %zu bytes", mCacheByteLimit); + } +} + BlobCache* egl_cache_t::getBlobCacheLocked() { if (mBlobCache == nullptr) { - mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); + mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize, + mCacheByteLimit, mFilename)); } return mBlobCache.get(); } +MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() { + if (mMultifileBlobCache == nullptr) { + mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize, + kMaxMultifileValueSize, mCacheByteLimit, + mFilename)); + } + return mMultifileBlobCache.get(); +} + }; // namespace android diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h index d10a6154b7..ae6d38146e 100644 --- a/opengl/libs/EGL/egl_cache.h +++ b/opengl/libs/EGL/egl_cache.h @@ -25,6 +25,7 @@ #include <string> #include "FileBlobCache.h" +#include "MultifileBlobCache.h" namespace android { @@ -32,6 +33,11 @@ class egl_display_t; class EGLAPI egl_cache_t { public: + enum class EGLCacheMode { + Monolithic, + Multifile, + }; + // get returns a pointer to the singleton egl_cache_t object. This // singleton object will never be destroyed. static egl_cache_t* get(); @@ -64,6 +70,15 @@ public: // cache contents from one program invocation to another. void setCacheFilename(const char* filename); + // Allow setting monolithic or multifile modes + void setCacheMode(EGLCacheMode cacheMode); + + // Allow the fixed cache limit to be overridden + void setCacheLimit(int64_t cacheByteLimit); + + // Return the byte total for cache file(s) + size_t getCacheSize(); + private: // Creation and (the lack of) destruction is handled internally. egl_cache_t(); @@ -73,12 +88,18 @@ private: egl_cache_t(const egl_cache_t&); // not implemented void operator=(const egl_cache_t&); // not implemented + // Check system properties to determine which blobcache mode should be used + void updateMode(); + // getBlobCacheLocked returns the BlobCache object being used to store the // key/value blob pairs. If the BlobCache object has not yet been created, // this will do so, loading the serialized cache contents from disk if // possible. BlobCache* getBlobCacheLocked(); + // Get or create the multifile blobcache + MultifileBlobCache* getMultifileBlobCacheLocked(); + // mInitialized indicates whether the egl_cache_t is in the initialized // state. It is initialized to false at construction time, and gets set to // true when initialize is called. It is set back to false when terminate @@ -92,6 +113,9 @@ private: // first time it's needed. std::unique_ptr<FileBlobCache> mBlobCache; + // The multifile version of blobcache allowing larger contents to be stored + std::unique_ptr<MultifileBlobCache> mMultifileBlobCache; + // mFilename is the name of the file for storing cache contents in between // program invocations. It is initialized to an empty string at // construction time, and can be set with the setCacheFilename method. An @@ -112,6 +136,12 @@ private: // sCache is the singleton egl_cache_t object. static egl_cache_t sCache; + + // Whether to use multiple files to store cache entries + bool mMultifileMode; + + // Cache limit + size_t mCacheByteLimit; }; }; // namespace android diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp index 6593c1bb16..525fed115d 100644 --- a/opengl/libs/EGL/egl_display.cpp +++ b/opengl/libs/EGL/egl_display.cpp @@ -353,8 +353,9 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) { // Typically that means there is an HDR capable display attached, but could be // support for attaching an HDR display. In either case, advertise support for // HDR color spaces. - mExtensionString.append( - "EGL_EXT_gl_colorspace_bt2020_linear EGL_EXT_gl_colorspace_bt2020_pq "); + mExtensionString.append("EGL_EXT_gl_colorspace_bt2020_hlg " + "EGL_EXT_gl_colorspace_bt2020_linear " + "EGL_EXT_gl_colorspace_bt2020_pq "); } char const* start = gExtensionString; diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp index 4b637dc60b..aefa1f0db5 100644 --- a/opengl/libs/EGL/egl_platform_entries.cpp +++ b/opengl/libs/EGL/egl_platform_entries.cpp @@ -18,6 +18,7 @@ #include "egl_platform_entries.h" +#include <aidl/android/hardware/graphics/common/PixelFormat.h> #include <android-base/properties.h> #include <android-base/strings.h> #include <android/hardware_buffer.h> @@ -29,7 +30,6 @@ #include <private/android/AHardwareBufferHelpers.h> #include <stdlib.h> #include <string.h> -#include <aidl/android/hardware/graphics/common/PixelFormat.h> #include <condition_variable> #include <deque> @@ -422,6 +422,8 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm return HAL_DATASPACE_V0_SCRGB; } else if (colorspace == EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT) { return HAL_DATASPACE_V0_SCRGB_LINEAR; + } else if (colorspace == EGL_GL_COLORSPACE_BT2020_HLG_EXT) { + return static_cast<android_dataspace>(HAL_DATASPACE_BT2020_HLG); } else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) { if (pixelFormat == PixelFormat::RGBA_FP16) { return static_cast<android_dataspace>(HAL_DATASPACE_STANDARD_BT2020 | @@ -433,6 +435,7 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelForm } else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) { return HAL_DATASPACE_BT2020_PQ; } + return HAL_DATASPACE_UNKNOWN; } @@ -459,6 +462,9 @@ static std::vector<EGLint> getDriverColorSpaces(egl_display_t* dp) { if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_scrgb_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT); } + if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_hlg")) { + colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_HLG_EXT); + } if (findExtension(dp->disp.queryString.extensions, "EGL_EXT_gl_colorspace_bt2020_linear")) { colorSpaces.push_back(EGL_GL_COLORSPACE_BT2020_LINEAR_EXT); } @@ -492,6 +498,7 @@ static EGLBoolean processAttributes(egl_display_t* dp, ANativeWindow* window, case EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT: case EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT: case EGL_GL_COLORSPACE_SCRGB_EXT: + case EGL_GL_COLORSPACE_BT2020_HLG_EXT: case EGL_GL_COLORSPACE_BT2020_LINEAR_EXT: case EGL_GL_COLORSPACE_BT2020_PQ_EXT: case EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT: @@ -943,6 +950,8 @@ EGLContext eglCreateContextImpl(EGLDisplay dpy, EGLConfig config, EGLContext sha android::GraphicsEnv::getInstance().setTargetStats( android::GpuStatsInfo::Stats::GLES_1_IN_USE); } + android::GraphicsEnv::getInstance().setTargetStats( + android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT); egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version); return c; } diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp new file mode 100644 index 0000000000..022a2a3f06 --- /dev/null +++ b/opengl/libs/EGL/fuzzer/Android.bp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_fuzz { + name: "MultifileBlobCache_fuzzer", + + fuzz_config: { + cc: ["cnorthrop@google.com"], + libfuzzer_options: ["len_control=0"], + }, + + static_libs: [ + "libbase", + "libEGL_blobCache", + "liblog", + "libutils", + ], + + srcs: [ + "MultifileBlobCache_fuzzer.cpp", + ], +} diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp new file mode 100644 index 0000000000..633cc9c51b --- /dev/null +++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp @@ -0,0 +1,158 @@ +/* + ** Copyright 2023, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "MultifileBlobCache.h" + +#include <android-base/test_utils.h> +#include <fcntl.h> +#include <fuzzer/FuzzedDataProvider.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> + +namespace android { + +constexpr size_t kMaxKeySize = 2 * 1024; +constexpr size_t kMaxValueSize = 6 * 1024; +constexpr size_t kMaxTotalSize = 32 * 1024; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + // To fuzz this, we're going to create a key/value pair from data + // and use them with MultifileBlobCache in a random order + // - Use the first entry in data to determine keySize + // - Use the second entry in data to determine valueSize + // - Mod each of them against half the remaining size, ensuring both fit + // - Create key and value using sizes from data + // - Use remaining data to switch between GET and SET while + // tweaking the keys slightly + // - Ensure two cache cleaning scenarios are hit at the end + + // Ensure we have enough data to create interesting key/value pairs + size_t kMinInputLength = 128; + if (size < kMinInputLength) { + return 0; + } + + // Need non-zero sizes for interesting results + if (data[0] == 0 || data[1] == 0) { + return 0; + } + + // We need to divide the data up into buffers and sizes + FuzzedDataProvider fdp(data, size); + + // Pull two values from data for key and value size + EGLsizeiANDROID keySize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>()); + EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>()); + size -= 2 * sizeof(uint8_t); + + // Ensure key and value fit in the remaining space (cap them at half data size) + keySize = keySize % (size >> 1); + valueSize = valueSize % (size >> 1); + + // If either size ended up zero, just move on to save time + if (keySize == 0 || valueSize == 0) { + return 0; + } + + // Create key and value from remaining data + std::vector<uint8_t> key; + std::vector<uint8_t> value; + key = fdp.ConsumeBytes<uint8_t>(keySize); + value = fdp.ConsumeBytes<uint8_t>(valueSize); + + // Create a tempfile and a cache + std::unique_ptr<TemporaryFile> tempFile; + std::unique_ptr<MultifileBlobCache> mbc; + + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + // With remaining data, select different paths below + int loopCount = 1; + uint8_t bumpCount = 0; + while (fdp.remaining_bytes() > 0) { + // Bounce back and forth between gets and sets + if (fdp.ConsumeBool()) { + mbc->set(key.data(), keySize, value.data(), valueSize); + } else { + uint8_t* buffer = new uint8_t[valueSize]; + mbc->get(key.data(), keySize, buffer, valueSize); + delete[] buffer; + } + + // Bump the key and values periodically, causing different hits/misses + if (fdp.ConsumeBool()) { + key[0]++; + value[0]++; + bumpCount++; + } + + // Reset the key and value periodically to hit old entries + if (fdp.ConsumeBool()) { + key[0] -= bumpCount; + value[0] -= bumpCount; + bumpCount = 0; + } + + loopCount++; + } + mbc->finish(); + + // Fill 2 keys and 2 values to max size with unique values + std::vector<uint8_t> maxKey1, maxKey2, maxValue1, maxValue2; + maxKey1.resize(kMaxKeySize, 0); + maxKey2.resize(kMaxKeySize, 0); + maxValue1.resize(kMaxValueSize, 0); + maxValue2.resize(kMaxValueSize, 0); + for (int i = 0; i < keySize && i < kMaxKeySize; ++i) { + maxKey1[i] = key[i]; + maxKey2[i] = key[i] - 1; + } + for (int i = 0; i < valueSize && i < kMaxValueSize; ++i) { + maxValue1[i] = value[i]; + maxValue2[i] = value[i] - 1; + } + + // Trigger hot cache trimming + // Place the maxKey/maxValue twice + // The first will fit, the second will trigger hot cache trimming + tempFile.reset(new TemporaryFile()); + mbc.reset( + new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0])); + uint8_t* buffer = new uint8_t[kMaxValueSize]; + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + // Trigger cold cache trimming + // Create a total size small enough only one entry fits + // Since the cache will add a header, 2 * key + value will only hold one value, the second will + // overflow + tempFile.reset(new TemporaryFile()); + mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize), + &tempFile->path[0])); + mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize); + mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize); + mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize); + mbc->finish(); + + delete[] buffer; + return 0; +} + +} // namespace android |