• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  ** Copyright 2022, The Android Open Source Project
3  **
4  ** Licensed under the Apache License, Version 2.0 (the "License");
5  ** you may not use this file except in compliance with the License.
6  ** You may obtain a copy of the License at
7  **
8  **     http://www.apache.org/licenses/LICENSE-2.0
9  **
10  ** Unless required by applicable law or agreed to in writing, software
11  ** distributed under the License is distributed on an "AS IS" BASIS,
12  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  ** See the License for the specific language governing permissions and
14  ** limitations under the License.
15  */
16 
17 // #define LOG_NDEBUG 0
18 
19 #include "MultifileBlobCache.h"
20 
21 #include <dirent.h>
22 #include <fcntl.h>
23 #include <inttypes.h>
24 #include <log/log.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/mman.h>
28 #include <sys/stat.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <utime.h>
32 
33 #include <algorithm>
34 #include <chrono>
35 #include <limits>
36 #include <locale>
37 
38 #include <utils/JenkinsHash.h>
39 
40 using namespace std::literals;
41 
42 constexpr uint32_t kMultifileMagic = 'MFB$';
43 constexpr uint32_t kCrcPlaceholder = 0;
44 
45 namespace {
46 
47 // Helper function to close entries or free them
freeHotCacheEntry(android::MultifileHotCache & entry)48 void freeHotCacheEntry(android::MultifileHotCache& entry) {
49     if (entry.entryFd != -1) {
50         // If we have an fd, then this entry was added to hot cache via INIT or GET
51         // We need to unmap the entry
52         munmap(entry.entryBuffer, entry.entrySize);
53     } else {
54         // Otherwise, this was added to hot cache during SET, so it was never mapped
55         // and fd was only on the deferred thread.
56         delete[] entry.entryBuffer;
57     }
58 }
59 
60 } // namespace
61 
62 namespace android {
63 
MultifileBlobCache(size_t maxKeySize,size_t maxValueSize,size_t maxTotalSize,const std::string & baseDir)64 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
65                                        const std::string& baseDir)
66       : mInitialized(false),
67         mMaxKeySize(maxKeySize),
68         mMaxValueSize(maxValueSize),
69         mMaxTotalSize(maxTotalSize),
70         mTotalCacheSize(0),
71         mHotCacheLimit(0),
72         mHotCacheSize(0),
73         mWorkerThreadIdle(true) {
74     if (baseDir.empty()) {
75         ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
76         return;
77     }
78 
79     // Establish the name of our multifile directory
80     mMultifileDirName = baseDir + ".multifile";
81 
82     // Set the hotcache limit to be large enough to contain one max entry
83     // This ensure the hot cache is always large enough for single entry
84     mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
85 
86     ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
87           mMaxKeySize, mMaxValueSize);
88 
89     // Initialize our cache with the contents of the directory
90     mTotalCacheSize = 0;
91 
92     // Create the worker thread
93     mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
94 
95     // See if the dir exists, and initialize using its contents
96     struct stat st;
97     if (stat(mMultifileDirName.c_str(), &st) == 0) {
98         // Read all the files and gather details, then preload their contents
99         DIR* dir;
100         struct dirent* entry;
101         if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
102             while ((entry = readdir(dir)) != nullptr) {
103                 if (entry->d_name == "."s || entry->d_name == ".."s) {
104                     continue;
105                 }
106 
107                 std::string entryName = entry->d_name;
108                 std::string fullPath = mMultifileDirName + "/" + entryName;
109 
110                 // The filename is the same as the entryHash
111                 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
112 
113                 ALOGV("INIT: Checking entry %u", entryHash);
114 
115                 // Look up the details of the file
116                 struct stat st;
117                 if (stat(fullPath.c_str(), &st) != 0) {
118                     ALOGE("Failed to stat %s", fullPath.c_str());
119                     return;
120                 }
121 
122                 // If the cache entry is damaged or no good, remove it
123                 if (st.st_size <= 0 || st.st_atime <= 0) {
124                     ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
125                     if (remove(fullPath.c_str()) != 0) {
126                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
127                     }
128                     continue;
129                 }
130 
131                 // Open the file so we can read its header
132                 int fd = open(fullPath.c_str(), O_RDONLY);
133                 if (fd == -1) {
134                     ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
135                           std::strerror(errno));
136                     return;
137                 }
138 
139                 // Read the beginning of the file to get header
140                 MultifileHeader header;
141                 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
142                 if (result != sizeof(MultifileHeader)) {
143                     ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
144                           fullPath.c_str(), std::strerror(errno));
145                     close(fd);
146                     return;
147                 }
148 
149                 // Verify header magic
150                 if (header.magic != kMultifileMagic) {
151                     ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
152                     if (remove(fullPath.c_str()) != 0) {
153                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
154                     }
155                     close(fd);
156                     continue;
157                 }
158 
159                 // Note: Converting from off_t (signed) to size_t (unsigned)
160                 size_t fileSize = static_cast<size_t>(st.st_size);
161 
162                 // Memory map the file
163                 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
164                         mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
165 
166                 // We can close the file now and the mmap will remain
167                 close(fd);
168 
169                 if (mappedEntry == MAP_FAILED) {
170                     ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
171                     return;
172                 }
173 
174                 // Ensure we have a good CRC
175                 if (header.crc !=
176                     crc32c(mappedEntry + sizeof(MultifileHeader),
177                            fileSize - sizeof(MultifileHeader))) {
178                     ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
179                     if (remove(fullPath.c_str()) != 0) {
180                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
181                     }
182                     continue;
183                 }
184 
185                 // If the cache entry is damaged or no good, remove it
186                 if (header.keySize <= 0 || header.valueSize <= 0) {
187                     ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
188                           "removing.",
189                           entryHash, header.keySize, header.valueSize);
190                     if (remove(fullPath.c_str()) != 0) {
191                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
192                     }
193                     continue;
194                 }
195 
196                 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
197 
198                 // Track details for rapid lookup later
199                 trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
200 
201                 // Track the total size
202                 increaseTotalCacheSize(fileSize);
203 
204                 // Preload the entry for fast retrieval
205                 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
206                     ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
207                           "entryHash %u",
208                           fd, mappedEntry, entryHash);
209 
210                     // Track the details of the preload so they can be retrieved later
211                     if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
212                         ALOGE("INIT Failed to add %u to hot cache", entryHash);
213                         munmap(mappedEntry, fileSize);
214                         return;
215                     }
216                 } else {
217                     // If we're not keeping it in hot cache, unmap it now
218                     munmap(mappedEntry, fileSize);
219                 }
220             }
221             closedir(dir);
222         } else {
223             ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
224         }
225     } else {
226         // If the multifile directory does not exist, create it and start from scratch
227         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
228             ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
229         }
230     }
231 
232     mInitialized = true;
233 }
234 
~MultifileBlobCache()235 MultifileBlobCache::~MultifileBlobCache() {
236     if (!mInitialized) {
237         return;
238     }
239 
240     // Inform the worker thread we're done
241     ALOGV("DESCTRUCTOR: Shutting down worker thread");
242     DeferredTask task(TaskCommand::Exit);
243     queueTask(std::move(task));
244 
245     // Wait for it to complete
246     ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
247     waitForWorkComplete();
248     if (mTaskThread.joinable()) {
249         mTaskThread.join();
250     }
251 }
252 
253 // Set will add the entry to hot cache and start a deferred process to write it to disk
set(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)254 void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
255                              EGLsizeiANDROID valueSize) {
256     if (!mInitialized) {
257         return;
258     }
259 
260     // Ensure key and value are under their limits
261     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
262         ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
263               valueSize, mMaxValueSize);
264         return;
265     }
266 
267     // Generate a hash of the key and use it to track this entry
268     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
269 
270     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
271 
272     // If we're going to be over the cache limit, kick off a trim to clear space
273     if (getTotalSize() + fileSize > mMaxTotalSize) {
274         ALOGV("SET: Cache is full, calling trimCache to clear space");
275         trimCache();
276     }
277 
278     ALOGV("SET: Add %u to cache", entryHash);
279 
280     uint8_t* buffer = new uint8_t[fileSize];
281 
282     // Write placeholders for magic and CRC until deferred thread completes the write
283     android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
284     memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
285            sizeof(android::MultifileHeader));
286     // Write the key and value after the header
287     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
288            keySize);
289     memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
290            static_cast<const void*>(value), valueSize);
291 
292     std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
293 
294     // Track the size and access time for quick recall
295     trackEntry(entryHash, valueSize, fileSize, time(0));
296 
297     // Update the overall cache size
298     increaseTotalCacheSize(fileSize);
299 
300     // Keep the entry in hot cache for quick retrieval
301     ALOGV("SET: Adding %u to hot cache.", entryHash);
302 
303     // Sending -1 as the fd indicates we don't have an fd for this
304     if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
305         ALOGE("SET: Failed to add %u to hot cache", entryHash);
306         delete[] buffer;
307         return;
308     }
309 
310     // Track that we're creating a pending write for this entry
311     // Include the buffer to handle the case when multiple writes are pending for an entry
312     {
313         // Synchronize access to deferred write status
314         std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
315         mDeferredWrites.insert(std::make_pair(entryHash, buffer));
316     }
317 
318     // Create deferred task to write to storage
319     ALOGV("SET: Adding task to queue.");
320     DeferredTask task(TaskCommand::WriteToDisk);
321     task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
322     queueTask(std::move(task));
323 }
324 
325 // Get will check the hot cache, then load it from disk if needed
get(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)326 EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
327                                         EGLsizeiANDROID valueSize) {
328     if (!mInitialized) {
329         return 0;
330     }
331 
332     // Ensure key and value are under their limits
333     if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
334         ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
335               valueSize, mMaxValueSize);
336         return 0;
337     }
338 
339     // Generate a hash of the key and use it to track this entry
340     uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
341 
342     // See if we have this file
343     if (!contains(entryHash)) {
344         ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
345         return 0;
346     }
347 
348     // Look up the data for this entry
349     MultifileEntryStats entryStats = getEntryStats(entryHash);
350 
351     size_t cachedValueSize = entryStats.valueSize;
352     if (cachedValueSize > valueSize) {
353         ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
354               "size (%zu)",
355               valueSize, entryHash, cachedValueSize);
356         return cachedValueSize;
357     }
358 
359     // We have the file and have enough room to write it out, return the entry
360     ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
361 
362     // Look up the size of the file
363     size_t fileSize = entryStats.fileSize;
364     if (keySize > fileSize) {
365         ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
366               "file",
367               keySize, fileSize);
368         return 0;
369     }
370 
371     std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
372 
373     // Open the hashed filename path
374     uint8_t* cacheEntry = 0;
375 
376     // Check hot cache
377     if (mHotCache.find(entryHash) != mHotCache.end()) {
378         ALOGV("GET: HotCache HIT for entry %u", entryHash);
379         cacheEntry = mHotCache[entryHash].entryBuffer;
380     } else {
381         ALOGV("GET: HotCache MISS for entry: %u", entryHash);
382 
383         // Wait for writes to complete if there is an outstanding write for this entry
384         bool wait = false;
385         {
386             // Synchronize access to deferred write status
387             std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
388             wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
389         }
390 
391         if (wait) {
392             ALOGV("GET: Waiting for write to complete for %u", entryHash);
393             waitForWorkComplete();
394         }
395 
396         // Open the entry file
397         int fd = open(fullPath.c_str(), O_RDONLY);
398         if (fd == -1) {
399             ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
400                   std::strerror(errno));
401             return 0;
402         }
403 
404         // Memory map the file
405         cacheEntry =
406                 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
407 
408         // We can close the file now and the mmap will remain
409         close(fd);
410 
411         if (cacheEntry == MAP_FAILED) {
412             ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
413             return 0;
414         }
415 
416         ALOGV("GET: Adding %u to hot cache", entryHash);
417         if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
418             ALOGE("GET: Failed to add %u to hot cache", entryHash);
419             return 0;
420         }
421 
422         cacheEntry = mHotCache[entryHash].entryBuffer;
423     }
424 
425     // Ensure the header matches
426     MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
427     if (header->keySize != keySize || header->valueSize != valueSize) {
428         ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
429               "to cache header values for fullPath: %s",
430               keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
431         removeFromHotCache(entryHash);
432         return 0;
433     }
434 
435     // Compare the incoming key with our stored version (the beginning of the entry)
436     uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
437     int compare = memcmp(cachedKey, key, keySize);
438     if (compare != 0) {
439         ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
440         removeFromHotCache(entryHash);
441         return 0;
442     }
443 
444     // Remaining entry following the key is the value
445     uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
446     memcpy(value, cachedValue, cachedValueSize);
447 
448     return cachedValueSize;
449 }
450 
finish()451 void MultifileBlobCache::finish() {
452     if (!mInitialized) {
453         return;
454     }
455 
456     // Wait for all deferred writes to complete
457     ALOGV("FINISH: Waiting for work to complete.");
458     waitForWorkComplete();
459 
460     // Close all entries in the hot cache
461     for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
462         uint32_t entryHash = hotCacheIter->first;
463         MultifileHotCache entry = hotCacheIter->second;
464 
465         ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
466         freeHotCacheEntry(entry);
467 
468         mHotCache.erase(hotCacheIter++);
469     }
470 }
471 
trackEntry(uint32_t entryHash,EGLsizeiANDROID valueSize,size_t fileSize,time_t accessTime)472 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
473                                     time_t accessTime) {
474     mEntries.insert(entryHash);
475     mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
476 }
477 
contains(uint32_t hashEntry) const478 bool MultifileBlobCache::contains(uint32_t hashEntry) const {
479     return mEntries.find(hashEntry) != mEntries.end();
480 }
481 
getEntryStats(uint32_t entryHash)482 MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
483     return mEntryStats[entryHash];
484 }
485 
increaseTotalCacheSize(size_t fileSize)486 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
487     mTotalCacheSize += fileSize;
488 }
489 
decreaseTotalCacheSize(size_t fileSize)490 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
491     mTotalCacheSize -= fileSize;
492 }
493 
addToHotCache(uint32_t newEntryHash,int newFd,uint8_t * newEntryBuffer,size_t newEntrySize)494 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
495                                        size_t newEntrySize) {
496     ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
497 
498     // Clear space if we need to
499     if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
500         ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
501               "mHotCacheLimit "
502               "(%zu), freeing up space for %u",
503               mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
504 
505         // Wait for all the files to complete writing so our hot cache is accurate
506         ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
507         waitForWorkComplete();
508 
509         // Free up old entries until under the limit
510         for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
511             uint32_t oldEntryHash = hotCacheIter->first;
512             MultifileHotCache oldEntry = hotCacheIter->second;
513 
514             // Move our iterator before deleting the entry
515             hotCacheIter++;
516             if (!removeFromHotCache(oldEntryHash)) {
517                 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
518                 return false;
519             }
520 
521             // Clear at least half the hot cache
522             if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
523                 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
524                 break;
525             }
526         }
527     }
528 
529     // Track it
530     mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
531     mHotCacheSize += newEntrySize;
532 
533     ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
534 
535     return true;
536 }
537 
removeFromHotCache(uint32_t entryHash)538 bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
539     if (mHotCache.find(entryHash) != mHotCache.end()) {
540         ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
541 
542         // Wait for all the files to complete writing so our hot cache is accurate
543         ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
544         waitForWorkComplete();
545 
546         ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
547         MultifileHotCache entry = mHotCache[entryHash];
548         freeHotCacheEntry(entry);
549 
550         // Delete the entry from our tracking
551         mHotCacheSize -= entry.entrySize;
552         mHotCache.erase(entryHash);
553 
554         return true;
555     }
556 
557     return false;
558 }
559 
applyLRU(size_t cacheLimit)560 bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
561     // Walk through our map of sorted last access times and remove files until under the limit
562     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
563         uint32_t entryHash = cacheEntryIter->first;
564 
565         ALOGV("LRU: Removing entryHash %u", entryHash);
566 
567         // Track the overall size
568         MultifileEntryStats entryStats = getEntryStats(entryHash);
569         decreaseTotalCacheSize(entryStats.fileSize);
570 
571         // Remove it from hot cache if present
572         removeFromHotCache(entryHash);
573 
574         // Remove it from the system
575         std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
576         if (remove(entryPath.c_str()) != 0) {
577             ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
578             return false;
579         }
580 
581         // Increment the iterator before clearing the entry
582         cacheEntryIter++;
583 
584         // Delete the entry from our tracking
585         size_t count = mEntryStats.erase(entryHash);
586         if (count != 1) {
587             ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
588             return false;
589         }
590 
591         // See if it has been reduced enough
592         size_t totalCacheSize = getTotalSize();
593         if (totalCacheSize <= cacheLimit) {
594             // Success
595             ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
596             return true;
597         }
598     }
599 
600     ALOGV("LRU: Cache is empty");
601     return false;
602 }
603 
604 // When removing files, what fraction of the overall limit should be reached when removing files
605 // A divisor of two will decrease the cache to 50%, four to 25% and so on
606 constexpr uint32_t kCacheLimitDivisor = 2;
607 
608 // Calculate the cache size and remove old entries until under the limit
trimCache()609 void MultifileBlobCache::trimCache() {
610     // Wait for all deferred writes to complete
611     ALOGV("TRIM: Waiting for work to complete.");
612     waitForWorkComplete();
613 
614     ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
615     if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
616         ALOGE("Error when clearing multifile shader cache");
617         return;
618     }
619 }
620 
621 // This function performs a task.  It only knows how to write files to disk,
622 // but it could be expanded if needed.
processTask(DeferredTask & task)623 void MultifileBlobCache::processTask(DeferredTask& task) {
624     switch (task.getTaskCommand()) {
625         case TaskCommand::Exit: {
626             ALOGV("DEFERRED: Shutting down");
627             return;
628         }
629         case TaskCommand::WriteToDisk: {
630             uint32_t entryHash = task.getEntryHash();
631             std::string& fullPath = task.getFullPath();
632             uint8_t* buffer = task.getBuffer();
633             size_t bufferSize = task.getBufferSize();
634 
635             // Create the file or reset it if already present, read+write for user only
636             int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
637             if (fd == -1) {
638                 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
639                       fullPath.c_str(), std::strerror(errno));
640                 return;
641             }
642 
643             ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
644 
645             // Add CRC check to the header (always do this last!)
646             MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
647             header->crc =
648                     crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader));
649 
650             ssize_t result = write(fd, buffer, bufferSize);
651             if (result != bufferSize) {
652                 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
653                       std::strerror(errno));
654                 return;
655             }
656 
657             ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
658             close(fd);
659 
660             // Erase the entry from mDeferredWrites
661             // Since there could be multiple outstanding writes for an entry, find the matching one
662             {
663                 // Synchronize access to deferred write status
664                 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
665                 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
666                 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
667                 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
668                     if (it->second == buffer) {
669                         ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
670                               it->second);
671                         mDeferredWrites.erase(it);
672                         break;
673                     }
674                 }
675             }
676 
677             return;
678         }
679         default: {
680             ALOGE("DEFERRED: Unhandled task type");
681             return;
682         }
683     }
684 }
685 
686 // This function will wait until tasks arrive, then execute them
687 // If the exit command is submitted, the loop will terminate
processTasksImpl(bool * exitThread)688 void MultifileBlobCache::processTasksImpl(bool* exitThread) {
689     while (true) {
690         std::unique_lock<std::mutex> lock(mWorkerMutex);
691         if (mTasks.empty()) {
692             ALOGV("WORKER: No tasks available, waiting");
693             mWorkerThreadIdle = true;
694             mWorkerIdleCondition.notify_all();
695             // Only wake if notified and command queue is not empty
696             mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
697         }
698 
699         ALOGV("WORKER: Task available, waking up.");
700         mWorkerThreadIdle = false;
701         DeferredTask task = std::move(mTasks.front());
702         mTasks.pop();
703 
704         if (task.getTaskCommand() == TaskCommand::Exit) {
705             ALOGV("WORKER: Exiting work loop.");
706             *exitThread = true;
707             mWorkerThreadIdle = true;
708             mWorkerIdleCondition.notify_one();
709             return;
710         }
711 
712         lock.unlock();
713         processTask(task);
714     }
715 }
716 
717 // Process tasks until the exit task is submitted
processTasks()718 void MultifileBlobCache::processTasks() {
719     while (true) {
720         bool exitThread = false;
721         processTasksImpl(&exitThread);
722         if (exitThread) {
723             break;
724         }
725     }
726 }
727 
728 // Add a task to the queue to be processed by the worker thread
queueTask(DeferredTask && task)729 void MultifileBlobCache::queueTask(DeferredTask&& task) {
730     std::lock_guard<std::mutex> queueLock(mWorkerMutex);
731     mTasks.emplace(std::move(task));
732     mWorkAvailableCondition.notify_one();
733 }
734 
735 // Wait until all tasks have been completed
waitForWorkComplete()736 void MultifileBlobCache::waitForWorkComplete() {
737     std::unique_lock<std::mutex> lock(mWorkerMutex);
738     mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
739 }
740 
741 }; // namespace android
742