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