blob: ed3c616b92eac8f2c78dde869d39d915f1f2cbd2 [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
Cody Northrop5f8117a2023-09-26 20:48:59 -060051 // We need to unmap the entry
Cody Northrop6cca6c22023-02-08 20:23:13 -070052 munmap(entry.entryBuffer, entry.entrySize);
Cody Northrop6cca6c22023-02-08 20:23:13 -070053 } 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
62namespace android {
63
Cody Northrop5dbcfa72023-03-24 15:34:09 -060064MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
Cody Northrop6cca6c22023-02-08 20:23:13 -070065 const std::string& baseDir)
66 : mInitialized(false),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060067 mMaxKeySize(maxKeySize),
68 mMaxValueSize(maxValueSize),
Cody Northrop6cca6c22023-02-08 20:23:13 -070069 mMaxTotalSize(maxTotalSize),
70 mTotalCacheSize(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060071 mHotCacheLimit(0),
Cody Northrop6cca6c22023-02-08 20:23:13 -070072 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
Cody Northrop5dbcfa72023-03-24 15:34:09 -060082 // 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);
Cody Northrop6cca6c22023-02-08 20:23:13 -070085
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
Cody Northrop999db232023-02-27 17:02:50 -0700122 // 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
Cody Northrop6cca6c22023-02-08 20:23:13 -0700131 // 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
Cody Northrop999db232023-02-27 17:02:50 -0700139 // 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));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600145 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700146 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 }
Cody Northrop5f8117a2023-09-26 20:48:59 -0600155 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700156 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));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600165
166 // We can close the file now and the mmap will remain
167 close(fd);
168
Cody Northrop999db232023-02-27 17:02:50 -0700169 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 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700184
185 // If the cache entry is damaged or no good, remove it
Cody Northrop999db232023-02-27 17:02:50 -0700186 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);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700190 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
Cody Northrop6cca6c22023-02-08 20:23:13 -0700198 // Track details for rapid lookup later
Cody Northrop999db232023-02-27 17:02:50 -0700199 trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700200
201 // Track the total size
202 increaseTotalCacheSize(fileSize);
203
204 // Preload the entry for fast retrieval
205 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700206 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);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700214 return;
215 }
216 } else {
Cody Northrop999db232023-02-27 17:02:50 -0700217 // If we're not keeping it in hot cache, unmap it now
218 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700219 }
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
235MultifileBlobCache::~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
254void 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) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600262 ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700263 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");
Cody Northropf2588a82023-03-27 23:03:49 -0600275 trimCache();
Cody Northrop6cca6c22023-02-08 20:23:13 -0700276 }
277
278 ALOGV("SET: Add %u to cache", entryHash);
279
280 uint8_t* buffer = new uint8_t[fileSize];
281
Cody Northrop45ce6802023-03-23 11:07:09 -0600282 // Write placeholders for magic and CRC until deferred thread completes the write
Cody Northrop999db232023-02-27 17:02:50 -0700283 android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
Cody Northrop6cca6c22023-02-08 20:23:13 -0700284 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
285 sizeof(android::MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700286 // Write the key and value after the header
Cody Northrop6cca6c22023-02-08 20:23:13 -0700287 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)) {
Cody Northropbe163732023-03-22 10:14:26 -0600305 ALOGE("SET: Failed to add %u to hot cache", entryHash);
Cody Northrop45ce6802023-03-23 11:07:09 -0600306 delete[] buffer;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700307 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
Cody Northropbe163732023-03-22 10:14:26 -0600312 {
313 // Synchronize access to deferred write status
314 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
315 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
316 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700317
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
326EGLsizeiANDROID 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) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600334 ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700335 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
Cody Northropbe163732023-03-22 10:14:26 -0600383 // 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) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700392 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));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600407
408 // We can close the file now and the mmap will remain
409 close(fd);
410
Cody Northrop6cca6c22023-02-08 20:23:13 -0700411 if (cacheEntry == MAP_FAILED) {
412 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700413 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
451void 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
472void 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
478bool MultifileBlobCache::contains(uint32_t hashEntry) const {
479 return mEntries.find(hashEntry) != mEntries.end();
480}
481
482MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
483 return mEntryStats[entryHash];
484}
485
486void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
487 mTotalCacheSize += fileSize;
488}
489
490void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
491 mTotalCacheSize -= fileSize;
492}
493
494bool 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
Cody Northropbe163732023-03-22 10:14:26 -0600506 ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700507 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
538bool 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
Cody Northropbe163732023-03-22 10:14:26 -0600543 ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700544 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
560bool 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
Cody Northropf2588a82023-03-27 23:03:49 -0600600 ALOGV("LRU: Cache is empty");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700601 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
606constexpr uint32_t kCacheLimitDivisor = 2;
607
608// Calculate the cache size and remove old entries until under the limit
Cody Northropf2588a82023-03-27 23:03:49 -0600609void MultifileBlobCache::trimCache() {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700610 // Wait for all deferred writes to complete
Cody Northropbe163732023-03-22 10:14:26 -0600611 ALOGV("TRIM: Waiting for work to complete.");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700612 waitForWorkComplete();
613
Cody Northropf2588a82023-03-27 23:03:49 -0600614 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;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700618 }
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.
623void 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
Cody Northrop999db232023-02-27 17:02:50 -0700645 // 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
Cody Northrop6cca6c22023-02-08 20:23:13 -0700650 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
Cody Northropbe163732023-03-22 10:14:26 -0600662 {
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 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700674 }
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
688void 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
718void 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
729void 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
736void MultifileBlobCache::waitForWorkComplete() {
737 std::unique_lock<std::mutex> lock(mWorkerMutex);
738 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
739}
740
Cody Northrop999db232023-02-27 17:02:50 -0700741}; // namespace android