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