Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f2588a81 authored by Cody Northrop's avatar Cody Northrop
Browse files

EGL BlobCache: Trim immediately on multifile overflow

While adding a fuzzer for this feature, discovered that
cache trimming wasn't happening right when the overflow
happened, but on the next iteration.

This CL updates the code to make it trim right away, since
we've already determined it needs to happen.

Also add a test to ensure trimming happens right away.

Test: /data/nativetest64/EGL_test/EGL_test
Bug: b/266725576
Change-Id: Ica1e9ca688268e7e746750c27acdfced04e74b06
parent be16373f
Loading
Loading
Loading
Loading
+7 −16
Original line number Diff line number Diff line
@@ -267,7 +267,7 @@ void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const voi
    // 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);
        trimCache();
    }

    ALOGV("SET: Add %u to cache", entryHash);
@@ -589,7 +589,7 @@ bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
        }
    }

    ALOGV("LRU: Cache is emptry");
    ALOGV("LRU: Cache is empty");
    return false;
}

@@ -598,26 +598,17 @@ bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
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;

void MultifileBlobCache::trimCache() {
    // Wait for all deferred writes to complete
    ALOGV("TRIM: Waiting for work 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)) {
    ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
        ALOGE("Error when clearing multifile shader cache");
        return;
    }
}
}

// This function performs a task.  It only knows how to write files to disk,
// but it could be expanded if needed.
+1 −1
Original line number Diff line number Diff line
@@ -119,7 +119,7 @@ private:
    bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
    bool removeFromHotCache(uint32_t entryHash);

    void trimCache(size_t cacheByteLimit);
    void trimCache();
    bool applyLRU(size_t cacheLimit);

    bool mInitialized;
+58 −1
Original line number Diff line number Diff line
@@ -265,11 +265,68 @@ TEST_P(EGLCacheTest, TerminatedCacheBelowCacheLimit) {
    // Cache should contain both the key and the value
    // So 8 bytes per entry, at least 24 bytes
    ASSERT_GE(mCache->getCacheSize(), 24);
    mCache->setCacheLimit(4);

    // Set the new limit and initialize cache
    mCache->terminate();
    mCache->setCacheLimit(4);
    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));

    // Ensure the new limit is respected
    ASSERT_LE(mCache->getCacheSize(), 4);
}

TEST_P(EGLCacheTest, TrimCacheOnOverflow) {
    // Skip if not in multifile mode
    if (mCacheMode == egl_cache_t::EGLCacheMode::Monolithic) {
        GTEST_SKIP() << "Skipping test designed for multifile";
    }

    uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));

    // Set one value in the cache
    mCache->setBlob("abcd", 4, "efgh", 4);
    ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
    ASSERT_EQ('e', buf[0]);
    ASSERT_EQ('f', buf[1]);
    ASSERT_EQ('g', buf[2]);
    ASSERT_EQ('h', buf[3]);

    // Get the size of cache with a single entry
    size_t cacheEntrySize = mCache->getCacheSize();

    // Now reinitialize the cache, using max size equal to a single entry
    mCache->terminate();
    mCache->setCacheLimit(cacheEntrySize);
    mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));

    // Ensure our cache still has original value
    ASSERT_EQ(4, mCache->getBlob("abcd", 4, buf, 4));
    ASSERT_EQ('e', buf[0]);
    ASSERT_EQ('f', buf[1]);
    ASSERT_EQ('g', buf[2]);
    ASSERT_EQ('h', buf[3]);

    // Set another value, which should overflow the cache and trim
    mCache->setBlob("ijkl", 4, "mnop", 4);
    ASSERT_EQ(4, mCache->getBlob("ijkl", 4, buf, 4));
    ASSERT_EQ('m', buf[0]);
    ASSERT_EQ('n', buf[1]);
    ASSERT_EQ('o', buf[2]);
    ASSERT_EQ('p', buf[3]);

    // The cache should still be under the limit
    ASSERT_TRUE(mCache->getCacheSize() == cacheEntrySize);

    // And no cache hit on trimmed entry
    uint8_t buf2[4] = { 0xee, 0xee, 0xee, 0xee };
    mCache->getBlob("abcd", 4, buf2, 4);
    ASSERT_EQ(0xee, buf2[0]);
    ASSERT_EQ(0xee, buf2[1]);
    ASSERT_EQ(0xee, buf2[2]);
    ASSERT_EQ(0xee, buf2[3]);
}

INSTANTIATE_TEST_CASE_P(MonolithicCacheTests,
        EGLCacheTest, ::testing::Values(egl_cache_t::EGLCacheMode::Monolithic));
INSTANTIATE_TEST_CASE_P(MultifileCacheTests,