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