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