Revert "EGL: Refactor multifile blobcache"
Revert submission 21108066-blobcache_multifile_20230125
Reason for revert: b/267777424
Reverted changes: /q/submissionid:21108066-blobcache_multifile_20230125
Change-Id: Ieb94d531a12b29b61958d3d5c601be544e429d79
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
deleted file mode 100644
index 48b184b..0000000
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ /dev/null
@@ -1,668 +0,0 @@
-/*
- ** 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 <android-base/properties.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;
-
-namespace {
-
-// Open the file and determine the size of the value it contains
-size_t getValueSizeFromFile(int fd, const std::string& entryPath) {
- // Read the beginning of the file to get header
- android::MultifileHeader header;
- size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader));
- if (result != sizeof(android::MultifileHeader)) {
- ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(),
- std::strerror(errno));
- return 0;
- }
-
- return header.valueSize;
-}
-
-// 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 maxTotalSize, size_t maxHotCacheSize,
- const std::string& baseDir)
- : mInitialized(false),
- mMaxTotalSize(maxTotalSize),
- mTotalCacheSize(0),
- mHotCacheLimit(maxHotCacheSize),
- mHotCacheSize(0),
- mWorkerThreadIdle(true) {
- if (baseDir.empty()) {
- return;
- }
-
- // Establish the name of our multifile directory
- mMultifileDirName = baseDir + ".multifile";
-
- // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache
- mMaxKeySize = mHotCacheLimit / 4;
- mMaxValueSize = mHotCacheLimit / 2;
-
- // Initialize our cache with the contents of the directory
- mTotalCacheSize = 0;
-
- // 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));
-
- // 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;
- }
-
- // 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;
- }
-
- // Look up the details we track about each file
- size_t valueSize = getValueSizeFromFile(fd, fullPath);
- size_t fileSize = st.st_size;
- time_t accessTime = st.st_atime;
-
- // If the cache entry is damaged or no good, remove it
- // TODO: Perform any other checks
- if (valueSize <= 0 || fileSize <= 0 || accessTime <= 0) {
- if (remove(fullPath.c_str()) != 0) {
- ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
- }
- continue;
- }
-
- // Track details for rapid lookup later
- trackEntry(entryHash, valueSize, fileSize, accessTime);
-
- // Track the total size
- increaseTotalCacheSize(fileSize);
-
- // Preload the entry for fast retrieval
- if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
- // 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));
- }
-
- 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 {
- 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);
- }
- }
-
- mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
-
- mInitialized = true;
-}
-
-MultifileBlobCache::~MultifileBlobCache() {
- // 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();
- 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) {
- ALOGV("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(mMaxTotalSize);
- }
-
- ALOGV("SET: Add %u to cache", entryHash);
-
- uint8_t* buffer = new uint8_t[fileSize];
-
- // Write the key and value after the header
- android::MultifileHeader header = {keySize, valueSize};
- memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
- sizeof(android::MultifileHeader));
- 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("GET: Failed to add %u to hot cache", entryHash);
- 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
- 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(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) {
- ALOGV("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);
-
- if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) {
- // Wait for writes to complete if there is an outstanding write for this entry
- 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() {
- // 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
- 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
- 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;
- size_t count = 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 emptry");
- 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(size_t cacheByteLimit) {
- // Start with the value provided by egl_cache
- size_t limit = cacheByteLimit;
-
- // Wait for all deferred writes to complete
- waitForWorkComplete();
-
- size_t size = getTotalSize();
-
- // If size is larger than the threshold, remove files using LRU
- if (size > limit) {
- ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries",
- cacheByteLimit);
- if (!applyLRU(limit / 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());
-
- 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
- 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
\ No newline at end of file
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
deleted file mode 100644
index dcdfe47..0000000
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- ** 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 <future>
-#include <map>
-#include <queue>
-#include <string>
-#include <thread>
-#include <unordered_map>
-#include <unordered_set>
-
-namespace android {
-
-struct MultifileHeader {
- 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) {}
-
- TaskCommand getTaskCommand() { return mCommand; }
-
- void initWriteToDisk(std::string fullPath, uint8_t* buffer, size_t bufferSize) {
- mCommand = TaskCommand::WriteToDisk;
- mFullPath = 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 maxTotalSize, size_t maxHotCacheSize, 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; }
- void trimCache(size_t cacheByteLimit);
-
-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);
-
- 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 to allow a deferred write
-
- // Track whether we have pending writes for an entry
- std::multimap<uint32_t, uint8_t*> mDeferredWrites;
-
- // 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
deleted file mode 100644
index 1a55a4f..0000000
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- ** 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 kMaxTotalSize = 32 * 1024;
-constexpr size_t kMaxPreloadSize = 8 * 1024;
-
-constexpr size_t kMaxKeySize = kMaxPreloadSize / 4;
-constexpr size_t kMaxValueSize = kMaxPreloadSize / 2;
-
-class MultifileBlobCacheTest : public ::testing::Test {
-protected:
- virtual void SetUp() {
- mTempFile.reset(new TemporaryFile());
- mMBC.reset(new MultifileBlobCache(kMaxTotalSize, kMaxPreloadSize, &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, 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_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index b00ee33..1e8a348 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -14,8 +14,6 @@
** limitations under the License.
*/
-// #define LOG_NDEBUG 0
-
#include "egl_cache.h"
#include <android-base/properties.h>
@@ -27,19 +25,22 @@
#include <thread>
#include "../egl_impl.h"
+#include "egl_cache_multifile.h"
#include "egl_display.h"
// 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;
+static const size_t maxKeySize = 12 * 1024;
+static const size_t maxValueSize = 64 * 1024;
+static const size_t maxTotalSize = 32 * 1024 * 1024;
// The time in seconds to wait before saving newly inserted monolithic cache entries.
-static const unsigned int kDeferredMonolithicSaveDelay = 4;
+static const unsigned int deferredSaveDelay = 4;
-// Multifile cache size limits
-constexpr uint32_t kMultifileHotCacheLimit = 8 * 1024 * 1024;
-constexpr uint32_t kMultifileCacheByteLimit = 32 * 1024 * 1024;
+// Multifile cache size limit
+constexpr size_t kMultifileCacheByteLimit = 64 * 1024 * 1024;
+
+// Delay before cleaning up multifile cache entries
+static const unsigned int deferredMultifileCleanupDelaySeconds = 1;
namespace android {
@@ -67,7 +68,10 @@
// egl_cache_t definition
//
egl_cache_t::egl_cache_t()
- : mInitialized(false), mMultifileMode(false), mCacheByteLimit(kMaxMonolithicTotalSize) {}
+ : mInitialized(false),
+ mMultifileMode(false),
+ mCacheByteLimit(maxTotalSize),
+ mMultifileCleanupPending(false) {}
egl_cache_t::~egl_cache_t() {}
@@ -81,7 +85,7 @@
std::lock_guard<std::mutex> lock(mMutex);
egl_connection_t* const cnx = &gEGLImpl;
- if (display && cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
+ if (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,36 +114,14 @@
}
}
- // 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());
+ // Allow forcing monolithic cache for debug purposes
+ if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") {
+ ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\"");
mMultifileMode = false;
}
if (mMultifileMode) {
- mCacheByteLimit = static_cast<size_t>(
- base::GetUintProperty<uint32_t>("ro.egl.blobcache.multifile_limit",
- kMultifileCacheByteLimit));
-
- // 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);
+ mCacheByteLimit = kMultifileCacheByteLimit;
}
mInitialized = true;
@@ -151,10 +133,10 @@
mBlobCache->writeToFile();
}
mBlobCache = nullptr;
- if (mMultifileBlobCache) {
- mMultifileBlobCache->finish();
+ if (mMultifileMode) {
+ checkMultifileCacheSize(mCacheByteLimit);
}
- mMultifileBlobCache = nullptr;
+ mMultifileMode = false;
mInitialized = false;
}
@@ -169,8 +151,20 @@
if (mInitialized) {
if (mMultifileMode) {
- MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
- mbc->set(key, keySize, value, valueSize);
+ setBlobMultifile(key, keySize, value, valueSize, mFilename);
+
+ if (!mMultifileCleanupPending) {
+ mMultifileCleanupPending = true;
+ // Kick off a thread to cull cache files below limit
+ std::thread deferredMultifileCleanupThread([this]() {
+ sleep(deferredMultifileCleanupDelaySeconds);
+ std::lock_guard<std::mutex> lock(mMutex);
+ // Check the size of cache and remove entries to stay under limit
+ checkMultifileCacheSize(mCacheByteLimit);
+ mMultifileCleanupPending = false;
+ });
+ deferredMultifileCleanupThread.detach();
+ }
} else {
BlobCache* bc = getBlobCacheLocked();
bc->set(key, keySize, value, valueSize);
@@ -178,7 +172,7 @@
if (!mSavePending) {
mSavePending = true;
std::thread deferredSaveThread([this]() {
- sleep(kDeferredMonolithicSaveDelay);
+ sleep(deferredSaveDelay);
std::lock_guard<std::mutex> lock(mMutex);
if (mInitialized && mBlobCache) {
mBlobCache->writeToFile();
@@ -202,21 +196,15 @@
if (mInitialized) {
if (mMultifileMode) {
- MultifileBlobCache* mbc = getMultifileBlobCacheLocked();
- return mbc->get(key, keySize, value, valueSize);
+ return getBlobMultifile(key, keySize, value, valueSize, mFilename);
} 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;
@@ -228,7 +216,7 @@
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) {
+ if (cacheByteLimit > maxTotalSize) {
return;
}
}
@@ -238,8 +226,8 @@
size_t egl_cache_t::getCacheSize() {
std::lock_guard<std::mutex> lock(mMutex);
- if (mMultifileBlobCache) {
- return mMultifileBlobCache->getTotalSize();
+ if (mMultifileMode) {
+ return getMultifileCacheSize();
}
if (mBlobCache) {
return mBlobCache->getSize();
@@ -249,18 +237,9 @@
BlobCache* egl_cache_t::getBlobCacheLocked() {
if (mBlobCache == nullptr) {
- mBlobCache.reset(new FileBlobCache(kMaxMonolithicKeySize, kMaxMonolithicValueSize,
- mCacheByteLimit, mFilename));
+ mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, mCacheByteLimit, mFilename));
}
return mBlobCache.get();
}
-MultifileBlobCache* egl_cache_t::getMultifileBlobCacheLocked() {
- if (mMultifileBlobCache == nullptr) {
- mMultifileBlobCache.reset(
- new MultifileBlobCache(mCacheByteLimit, kMultifileHotCacheLimit, mFilename));
- }
- return mMultifileBlobCache.get();
-}
-
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index 1399368..2dcd803 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -25,7 +25,6 @@
#include <string>
#include "FileBlobCache.h"
-#include "MultifileBlobCache.h"
namespace android {
@@ -33,11 +32,6 @@
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();
@@ -70,9 +64,6 @@
// 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);
@@ -94,9 +85,6 @@
// 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
@@ -110,9 +98,6 @@
// 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
@@ -138,7 +123,11 @@
bool mMultifileMode;
// Cache limit
- size_t mCacheByteLimit;
+ int64_t mCacheByteLimit;
+
+ // Whether we've kicked off a side thread that will check the multifile
+ // cache size and remove entries if needed.
+ bool mMultifileCleanupPending;
};
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache_multifile.cpp b/opengl/libs/EGL/egl_cache_multifile.cpp
new file mode 100644
index 0000000..48e557f
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache_multifile.cpp
@@ -0,0 +1,343 @@
+/*
+ ** 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 "egl_cache_multifile.h"
+
+#include <android-base/properties.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <fstream>
+#include <limits>
+#include <locale>
+#include <map>
+#include <sstream>
+#include <unordered_map>
+
+#include <utils/JenkinsHash.h>
+
+static std::string multifileDirName = "";
+
+using namespace std::literals;
+
+namespace {
+
+// Create a directory for tracking multiple files
+void setupMultifile(const std::string& baseDir) {
+ // If we've already set up the multifile dir in this base directory, we're done
+ if (!multifileDirName.empty() && multifileDirName.find(baseDir) != std::string::npos) {
+ return;
+ }
+
+ // Otherwise, create it
+ multifileDirName = baseDir + ".multifile";
+ if (mkdir(multifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
+ ALOGW("Unable to create directory (%s), errno (%i)", multifileDirName.c_str(), errno);
+ }
+}
+
+// Create a filename that is based on the hash of the key
+std::string getCacheEntryFilename(const void* key, EGLsizeiANDROID keySize,
+ const std::string& baseDir) {
+ // Hash the key into a string
+ std::stringstream keyName;
+ keyName << android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
+
+ // Build a filename using dir and hash
+ return baseDir + "/" + keyName.str();
+}
+
+// Determine file age based on stat modification time
+// Newer files have a higher age (time since epoch)
+time_t getFileAge(const std::string& filePath) {
+ struct stat st;
+ if (stat(filePath.c_str(), &st) == 0) {
+ ALOGD("getFileAge returning %" PRId64 " for file age", static_cast<uint64_t>(st.st_mtime));
+ return st.st_mtime;
+ } else {
+ ALOGW("Failed to stat %s", filePath.c_str());
+ return 0;
+ }
+}
+
+size_t getFileSize(const std::string& filePath) {
+ struct stat st;
+ if (stat(filePath.c_str(), &st) != 0) {
+ ALOGE("Unable to stat %s", filePath.c_str());
+ return 0;
+ }
+ return st.st_size;
+}
+
+// Walk through directory entries and track age and size
+// Then iterate through the entries, oldest first, and remove them until under the limit.
+// This will need to be updated if we move to a multilevel cache dir.
+bool applyLRU(size_t cacheLimit) {
+ // Build a multimap of files indexed by age.
+ // They will be automatically sorted smallest (oldest) to largest (newest)
+ std::multimap<time_t, std::string> agesToFiles;
+
+ // Map files to sizes
+ std::unordered_map<std::string, size_t> filesToSizes;
+
+ size_t totalCacheSize = 0;
+
+ DIR* dir;
+ struct dirent* entry;
+ if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ // Look up each file age
+ std::string fullPath = multifileDirName + "/" + entry->d_name;
+ time_t fileAge = getFileAge(fullPath);
+
+ // Track the files, sorted by age
+ agesToFiles.insert(std::make_pair(fileAge, fullPath));
+
+ // Also track the size so we know how much room we have freed
+ size_t fileSize = getFileSize(fullPath);
+ filesToSizes[fullPath] = fileSize;
+ totalCacheSize += fileSize;
+ }
+ closedir(dir);
+ } else {
+ ALOGE("Unable to open filename: %s", multifileDirName.c_str());
+ return false;
+ }
+
+ if (totalCacheSize <= cacheLimit) {
+ // If LRU was called on a sufficiently small cache, no need to remove anything
+ return true;
+ }
+
+ // Walk through the map of files until we're under the cache size
+ for (const auto& cacheEntryIter : agesToFiles) {
+ time_t entryAge = cacheEntryIter.first;
+ const std::string entryPath = cacheEntryIter.second;
+
+ ALOGD("Removing %s with age %ld", entryPath.c_str(), entryAge);
+ if (std::remove(entryPath.c_str()) != 0) {
+ ALOGE("Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ totalCacheSize -= filesToSizes[entryPath];
+ if (totalCacheSize <= cacheLimit) {
+ // Success
+ ALOGV("Reduced cache to %zu", totalCacheSize);
+ return true;
+ } else {
+ ALOGD("Cache size is still too large (%zu), removing more files", totalCacheSize);
+ }
+ }
+
+ // Should never reach this return
+ return false;
+}
+
+} // namespace
+
+namespace android {
+
+void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir) {
+ if (baseDir.empty()) {
+ return;
+ }
+
+ setupMultifile(baseDir);
+ std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
+
+ ALOGD("Attempting to open filename for set: %s", filename.c_str());
+ std::ofstream outfile(filename, std::ofstream::binary);
+ if (outfile.fail()) {
+ ALOGW("Unable to open filename: %s", filename.c_str());
+ return;
+ }
+
+ // First write the key
+ outfile.write(static_cast<const char*>(key), keySize);
+ if (outfile.bad()) {
+ ALOGW("Unable to write key to filename: %s", filename.c_str());
+ outfile.close();
+ return;
+ }
+ ALOGD("Wrote %i bytes to out file for key", static_cast<int>(outfile.tellp()));
+
+ // Then write the value
+ outfile.write(static_cast<const char*>(value), valueSize);
+ if (outfile.bad()) {
+ ALOGW("Unable to write value to filename: %s", filename.c_str());
+ outfile.close();
+ return;
+ }
+ ALOGD("Wrote %i bytes to out file for full entry", static_cast<int>(outfile.tellp()));
+
+ outfile.close();
+}
+
+EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir) {
+ if (baseDir.empty()) {
+ return 0;
+ }
+
+ setupMultifile(baseDir);
+ std::string filename = getCacheEntryFilename(key, keySize, multifileDirName);
+
+ // Open the hashed filename path
+ ALOGD("Attempting to open filename for get: %s", filename.c_str());
+ int fd = open(filename.c_str(), O_RDONLY);
+
+ // File doesn't exist, this is a MISS, return zero bytes read
+ if (fd == -1) {
+ ALOGD("Cache MISS - failed to open filename: %s, error: %s", filename.c_str(),
+ std::strerror(errno));
+ return 0;
+ }
+
+ ALOGD("Cache HIT - opened filename: %s", filename.c_str());
+
+ // Get the size of the file
+ size_t entrySize = getFileSize(filename);
+ if (keySize > entrySize) {
+ ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
+ "file",
+ keySize, entrySize);
+ close(fd);
+ return 0;
+ }
+
+ // Memory map the file
+ uint8_t* cacheEntry =
+ reinterpret_cast<uint8_t*>(mmap(nullptr, entrySize, PROT_READ, MAP_PRIVATE, fd, 0));
+ if (cacheEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ close(fd);
+ return 0;
+ }
+
+ // Compare the incoming key with our stored version (the beginning of the entry)
+ int compare = memcmp(cacheEntry, key, keySize);
+ if (compare != 0) {
+ ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
+ munmap(cacheEntry, entrySize);
+ close(fd);
+ return 0;
+ }
+
+ // Keys matched, so remaining cache is value size
+ size_t cachedValueSize = entrySize - keySize;
+
+ // Return actual value size if valueSize is not large enough
+ if (cachedValueSize > valueSize) {
+ ALOGD("Skipping file read, not enough room provided (valueSize): %lu, "
+ "returning required space as %zu",
+ valueSize, cachedValueSize);
+ munmap(cacheEntry, entrySize);
+ close(fd);
+ return cachedValueSize;
+ }
+
+ // Remaining entry following the key is the value
+ uint8_t* cachedValue = cacheEntry + keySize;
+ memcpy(value, cachedValue, cachedValueSize);
+ munmap(cacheEntry, entrySize);
+ close(fd);
+
+ ALOGD("Read %zu bytes from %s", cachedValueSize, filename.c_str());
+ return cachedValueSize;
+}
+
+// Walk through the files in our flat directory, checking the size of each one.
+// Return the total size of normal files in the directory.
+// This will need to be updated if we move to a multilevel cache dir.
+size_t getMultifileCacheSize() {
+ if (multifileDirName.empty()) {
+ return 0;
+ }
+
+ DIR* dir;
+ struct dirent* entry;
+ size_t size = 0;
+
+ ALOGD("Using %s as the multifile cache dir ", multifileDirName.c_str());
+
+ if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+ while ((entry = readdir(dir)) != nullptr) {
+ if (entry->d_name == "."s || entry->d_name == ".."s) {
+ continue;
+ }
+
+ // Add up the size of all files in the dir
+ std::string fullPath = multifileDirName + "/" + entry->d_name;
+ size += getFileSize(fullPath);
+ }
+ closedir(dir);
+ } else {
+ ALOGW("Unable to open filename: %s", multifileDirName.c_str());
+ return 0;
+ }
+
+ return size;
+}
+
+// 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 checkMultifileCacheSize(size_t cacheByteLimit) {
+ // Start with the value provided by egl_cache
+ size_t limit = cacheByteLimit;
+
+ // Check for a debug value
+ int debugCacheSize = base::GetIntProperty("debug.egl.blobcache.bytelimit", -1);
+ if (debugCacheSize >= 0) {
+ ALOGV("Overriding cache limit %zu with %i from debug.egl.blobcache.bytelimit", limit,
+ debugCacheSize);
+ limit = debugCacheSize;
+ }
+
+ // Tally up the initial amount of cache in use
+ size_t size = getMultifileCacheSize();
+ ALOGD("Multifile cache dir size: %zu", size);
+
+ // If size is larger than the threshold, remove files using LRU
+ if (size > limit) {
+ ALOGV("Multifile cache size is larger than %zu, removing old entries", cacheByteLimit);
+ if (!applyLRU(limit / kCacheLimitDivisor)) {
+ ALOGE("Error when clearing multifile shader cache");
+ return;
+ }
+ }
+ ALOGD("Multifile cache size after reduction: %zu", getMultifileCacheSize());
+}
+
+}; // namespace android
\ No newline at end of file
diff --git a/opengl/libs/EGL/egl_cache_multifile.h b/opengl/libs/EGL/egl_cache_multifile.h
new file mode 100644
index 0000000..ee5fe81
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache_multifile.h
@@ -0,0 +1,36 @@
+/*
+ ** 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_EGL_CACHE_MULTIFILE_H
+#define ANDROID_EGL_CACHE_MULTIFILE_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <string>
+
+namespace android {
+
+void setBlobMultifile(const void* key, EGLsizeiANDROID keySize, const void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir);
+EGLsizeiANDROID getBlobMultifile(const void* key, EGLsizeiANDROID keySize, void* value,
+ EGLsizeiANDROID valueSize, const std::string& baseDir);
+size_t getMultifileCacheSize();
+void checkMultifileCacheSize(size_t cacheByteLimit);
+
+}; // namespace android
+
+#endif // ANDROID_EGL_CACHE_MULTIFILE_H