blob: 917671d9abc5872d7fb8cfc83968f49640241f56 [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
Cody Northrop027f2422023-11-12 22:51:01 -070021#include <android-base/properties.h>
Cody Northrop6cca6c22023-02-08 20:23:13 -070022#include <dirent.h>
23#include <fcntl.h>
24#include <inttypes.h>
25#include <log/log.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <sys/mman.h>
29#include <sys/stat.h>
30#include <time.h>
31#include <unistd.h>
32#include <utime.h>
33
34#include <algorithm>
35#include <chrono>
36#include <limits>
37#include <locale>
38
39#include <utils/JenkinsHash.h>
40
Cody Northrop4ee63862024-11-19 22:41:23 -070041#include <com_android_graphics_egl_flags.h>
42
43using namespace com::android::graphics::egl;
44
Cody Northrop6cca6c22023-02-08 20:23:13 -070045using namespace std::literals;
46
Cody Northrop999db232023-02-27 17:02:50 -070047constexpr uint32_t kMultifileMagic = 'MFB$';
48constexpr uint32_t kCrcPlaceholder = 0;
49
Cody Northrop6cca6c22023-02-08 20:23:13 -070050namespace {
51
Cody Northrop6cca6c22023-02-08 20:23:13 -070052// Helper function to close entries or free them
53void freeHotCacheEntry(android::MultifileHotCache& entry) {
54 if (entry.entryFd != -1) {
55 // If we have an fd, then this entry was added to hot cache via INIT or GET
Cody Northrop5f8117a2023-09-26 20:48:59 -060056 // We need to unmap the entry
Cody Northrop6cca6c22023-02-08 20:23:13 -070057 munmap(entry.entryBuffer, entry.entrySize);
Cody Northrop6cca6c22023-02-08 20:23:13 -070058 } else {
59 // Otherwise, this was added to hot cache during SET, so it was never mapped
60 // and fd was only on the deferred thread.
61 delete[] entry.entryBuffer;
62 }
63}
64
65} // namespace
66
67namespace android {
68
Cody Northrop5dbcfa72023-03-24 15:34:09 -060069MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
Cody Northropb5267032023-10-24 10:11:21 -060070 size_t maxTotalEntries, const std::string& baseDir)
Cody Northrop6cca6c22023-02-08 20:23:13 -070071 : mInitialized(false),
Cody Northrop027f2422023-11-12 22:51:01 -070072 mCacheVersion(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060073 mMaxKeySize(maxKeySize),
74 mMaxValueSize(maxValueSize),
Cody Northrop6cca6c22023-02-08 20:23:13 -070075 mMaxTotalSize(maxTotalSize),
Cody Northropb5267032023-10-24 10:11:21 -060076 mMaxTotalEntries(maxTotalEntries),
Cody Northrop6cca6c22023-02-08 20:23:13 -070077 mTotalCacheSize(0),
Cody Northropb5267032023-10-24 10:11:21 -060078 mTotalCacheEntries(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060079 mHotCacheLimit(0),
Cody Northrop6cca6c22023-02-08 20:23:13 -070080 mHotCacheSize(0),
81 mWorkerThreadIdle(true) {
82 if (baseDir.empty()) {
83 ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
84 return;
85 }
86
Cody Northrop4ee63862024-11-19 22:41:23 -070087 // Set the cache version
Cody Northrop027f2422023-11-12 22:51:01 -070088 mCacheVersion = kMultifileBlobCacheVersion;
Cody Northrop4ee63862024-11-19 22:41:23 -070089 // Bump the version if we're using flagged features
90 if (flags::multifile_blobcache_advanced_usage()) {
91 mCacheVersion++;
92 }
93 // Override if debug value set
Cody Northrop027f2422023-11-12 22:51:01 -070094 int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
95 if (debugCacheVersion >= 0) {
96 ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
97 mCacheVersion = debugCacheVersion;
98 }
99
100 // Set the platform build ID, override if debug value set
101 mBuildId = base::GetProperty("ro.build.id", "");
102 std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
103 if (!debugBuildId.empty()) {
104 ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
105 if (debugBuildId.length() > PROP_VALUE_MAX) {
106 ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
107 PROP_VALUE_MAX);
108 }
109 mBuildId = debugBuildId;
110 }
111
Cody Northrop6cca6c22023-02-08 20:23:13 -0700112 // Establish the name of our multifile directory
113 mMultifileDirName = baseDir + ".multifile";
114
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600115 // Set the hotcache limit to be large enough to contain one max entry
116 // This ensure the hot cache is always large enough for single entry
117 mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700118
119 ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
120 mMaxKeySize, mMaxValueSize);
121
122 // Initialize our cache with the contents of the directory
123 mTotalCacheSize = 0;
124
125 // Create the worker thread
126 mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
127
128 // See if the dir exists, and initialize using its contents
Cody Northrop027f2422023-11-12 22:51:01 -0700129 bool statusGood = false;
130
131 // Check that our cacheVersion and buildId match
Cody Northrop6cca6c22023-02-08 20:23:13 -0700132 struct stat st;
133 if (stat(mMultifileDirName.c_str(), &st) == 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700134 if (checkStatus(mMultifileDirName.c_str())) {
135 statusGood = true;
136 } else {
137 ALOGV("INIT: Cache status has changed, clearing the cache");
138 if (!clearCache()) {
139 ALOGE("INIT: Unable to clear cache");
140 return;
141 }
142 }
143 }
144
145 if (statusGood) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700146 // Read all the files and gather details, then preload their contents
147 DIR* dir;
148 struct dirent* entry;
149 if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
150 while ((entry = readdir(dir)) != nullptr) {
Cody Northrop027f2422023-11-12 22:51:01 -0700151 if (entry->d_name == "."s || entry->d_name == ".."s ||
152 strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700153 continue;
154 }
155
156 std::string entryName = entry->d_name;
157 std::string fullPath = mMultifileDirName + "/" + entryName;
158
159 // The filename is the same as the entryHash
160 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
161
162 ALOGV("INIT: Checking entry %u", entryHash);
163
164 // Look up the details of the file
165 struct stat st;
166 if (stat(fullPath.c_str(), &st) != 0) {
167 ALOGE("Failed to stat %s", fullPath.c_str());
168 return;
169 }
170
Cody Northrop999db232023-02-27 17:02:50 -0700171 // If the cache entry is damaged or no good, remove it
172 if (st.st_size <= 0 || st.st_atime <= 0) {
173 ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
174 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700175 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
176 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700177 }
178 continue;
179 }
180
Cody Northrop6cca6c22023-02-08 20:23:13 -0700181 // Open the file so we can read its header
182 int fd = open(fullPath.c_str(), O_RDONLY);
183 if (fd == -1) {
184 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
185 std::strerror(errno));
186 return;
187 }
188
Cody Northrop999db232023-02-27 17:02:50 -0700189 // Read the beginning of the file to get header
190 MultifileHeader header;
191 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
192 if (result != sizeof(MultifileHeader)) {
Cody Northrop027f2422023-11-12 22:51:01 -0700193 ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
Cody Northrop999db232023-02-27 17:02:50 -0700194 fullPath.c_str(), std::strerror(errno));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600195 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700196 return;
197 }
198
199 // Verify header magic
200 if (header.magic != kMultifileMagic) {
201 ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
202 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700203 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
204 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700205 }
Cody Northrop5f8117a2023-09-26 20:48:59 -0600206 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700207 continue;
208 }
209
210 // Note: Converting from off_t (signed) to size_t (unsigned)
211 size_t fileSize = static_cast<size_t>(st.st_size);
212
213 // Memory map the file
214 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
215 mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600216
217 // We can close the file now and the mmap will remain
218 close(fd);
219
Cody Northrop999db232023-02-27 17:02:50 -0700220 if (mappedEntry == MAP_FAILED) {
221 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
222 return;
223 }
224
225 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900226 if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
227 fileSize - sizeof(MultifileHeader))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700228 ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
Cody Northrop999db232023-02-27 17:02:50 -0700229 if (remove(fullPath.c_str()) != 0) {
230 ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
231 }
232 continue;
233 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700234
235 // If the cache entry is damaged or no good, remove it
Cody Northrop999db232023-02-27 17:02:50 -0700236 if (header.keySize <= 0 || header.valueSize <= 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700237 ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
Cody Northrop999db232023-02-27 17:02:50 -0700238 "removing.",
239 entryHash, header.keySize, header.valueSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700240 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700241 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
242 std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700243 }
244 continue;
245 }
246
247 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
248
Cody Northrop6aebcf22024-11-08 15:55:30 -0700249 // Track details for rapid lookup later and update total size
Cody Northrop999db232023-02-27 17:02:50 -0700250 trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700251
Cody Northrop6cca6c22023-02-08 20:23:13 -0700252 // Preload the entry for fast retrieval
253 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700254 ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
255 "entryHash %u",
256 fd, mappedEntry, entryHash);
257
258 // Track the details of the preload so they can be retrieved later
259 if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
260 ALOGE("INIT Failed to add %u to hot cache", entryHash);
261 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700262 return;
263 }
264 } else {
Cody Northrop999db232023-02-27 17:02:50 -0700265 // If we're not keeping it in hot cache, unmap it now
266 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700267 }
268 }
269 closedir(dir);
270 } else {
271 ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
272 }
273 } else {
274 // If the multifile directory does not exist, create it and start from scratch
275 if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
276 ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
Cody Northrop027f2422023-11-12 22:51:01 -0700277 return;
278 }
279
280 // Create new status file
281 if (!createStatus(mMultifileDirName.c_str())) {
282 ALOGE("INIT: Failed to create status file!");
283 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700284 }
285 }
286
Cody Northrop027f2422023-11-12 22:51:01 -0700287 ALOGV("INIT: Multifile BlobCache initialization succeeded");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700288 mInitialized = true;
289}
290
291MultifileBlobCache::~MultifileBlobCache() {
292 if (!mInitialized) {
293 return;
294 }
295
296 // Inform the worker thread we're done
297 ALOGV("DESCTRUCTOR: Shutting down worker thread");
298 DeferredTask task(TaskCommand::Exit);
299 queueTask(std::move(task));
300
301 // Wait for it to complete
302 ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
303 waitForWorkComplete();
304 if (mTaskThread.joinable()) {
305 mTaskThread.join();
306 }
307}
308
309// Set will add the entry to hot cache and start a deferred process to write it to disk
310void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
311 EGLsizeiANDROID valueSize) {
312 if (!mInitialized) {
313 return;
314 }
315
316 // Ensure key and value are under their limits
317 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600318 ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700319 valueSize, mMaxValueSize);
320 return;
321 }
322
323 // Generate a hash of the key and use it to track this entry
324 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
325
Cody Northrop6aebcf22024-11-08 15:55:30 -0700326 // See if we already have this file
327 if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) {
328 // Remove previous entry from hot cache
329 removeFromHotCache(entryHash);
330
331 // Remove previous entry and update the overall cache size
332 removeEntry(entryHash);
333 }
334
Cody Northrop6cca6c22023-02-08 20:23:13 -0700335 size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
336
337 // If we're going to be over the cache limit, kick off a trim to clear space
Cody Northropb5267032023-10-24 10:11:21 -0600338 if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700339 ALOGV("SET: Cache is full, calling trimCache to clear space");
Cody Northropf2588a82023-03-27 23:03:49 -0600340 trimCache();
Cody Northrop6cca6c22023-02-08 20:23:13 -0700341 }
342
343 ALOGV("SET: Add %u to cache", entryHash);
344
345 uint8_t* buffer = new uint8_t[fileSize];
346
Cody Northrop45ce6802023-03-23 11:07:09 -0600347 // Write placeholders for magic and CRC until deferred thread completes the write
Cody Northrop999db232023-02-27 17:02:50 -0700348 android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
Cody Northrop6cca6c22023-02-08 20:23:13 -0700349 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
350 sizeof(android::MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700351 // Write the key and value after the header
Cody Northrop6cca6c22023-02-08 20:23:13 -0700352 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
353 keySize);
354 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
355 static_cast<const void*>(value), valueSize);
356
357 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
358
Cody Northrop6aebcf22024-11-08 15:55:30 -0700359 // Track the size and access time for quick recall and update the overall cache size
Cody Northrop6cca6c22023-02-08 20:23:13 -0700360 trackEntry(entryHash, valueSize, fileSize, time(0));
361
Cody Northrop6cca6c22023-02-08 20:23:13 -0700362 // Keep the entry in hot cache for quick retrieval
363 ALOGV("SET: Adding %u to hot cache.", entryHash);
364
365 // Sending -1 as the fd indicates we don't have an fd for this
366 if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
Cody Northropbe163732023-03-22 10:14:26 -0600367 ALOGE("SET: Failed to add %u to hot cache", entryHash);
Cody Northrop45ce6802023-03-23 11:07:09 -0600368 delete[] buffer;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700369 return;
370 }
371
372 // Track that we're creating a pending write for this entry
373 // Include the buffer to handle the case when multiple writes are pending for an entry
Cody Northropbe163732023-03-22 10:14:26 -0600374 {
375 // Synchronize access to deferred write status
376 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
377 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
378 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700379
380 // Create deferred task to write to storage
381 ALOGV("SET: Adding task to queue.");
382 DeferredTask task(TaskCommand::WriteToDisk);
383 task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
384 queueTask(std::move(task));
385}
386
387// Get will check the hot cache, then load it from disk if needed
388EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
389 EGLsizeiANDROID valueSize) {
390 if (!mInitialized) {
391 return 0;
392 }
393
394 // Ensure key and value are under their limits
395 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600396 ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700397 valueSize, mMaxValueSize);
398 return 0;
399 }
400
401 // Generate a hash of the key and use it to track this entry
402 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
403
404 // See if we have this file
405 if (!contains(entryHash)) {
406 ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
407 return 0;
408 }
409
410 // Look up the data for this entry
411 MultifileEntryStats entryStats = getEntryStats(entryHash);
412
413 size_t cachedValueSize = entryStats.valueSize;
414 if (cachedValueSize > valueSize) {
415 ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
416 "size (%zu)",
417 valueSize, entryHash, cachedValueSize);
418 return cachedValueSize;
419 }
420
421 // We have the file and have enough room to write it out, return the entry
422 ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
423
424 // Look up the size of the file
425 size_t fileSize = entryStats.fileSize;
426 if (keySize > fileSize) {
427 ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
428 "file",
429 keySize, fileSize);
430 return 0;
431 }
432
433 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
434
435 // Open the hashed filename path
436 uint8_t* cacheEntry = 0;
437
438 // Check hot cache
439 if (mHotCache.find(entryHash) != mHotCache.end()) {
440 ALOGV("GET: HotCache HIT for entry %u", entryHash);
441 cacheEntry = mHotCache[entryHash].entryBuffer;
442 } else {
443 ALOGV("GET: HotCache MISS for entry: %u", entryHash);
444
Cody Northropbe163732023-03-22 10:14:26 -0600445 // Wait for writes to complete if there is an outstanding write for this entry
446 bool wait = false;
447 {
448 // Synchronize access to deferred write status
449 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
450 wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
451 }
452
453 if (wait) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700454 ALOGV("GET: Waiting for write to complete for %u", entryHash);
455 waitForWorkComplete();
456 }
457
458 // Open the entry file
459 int fd = open(fullPath.c_str(), O_RDONLY);
460 if (fd == -1) {
461 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
462 std::strerror(errno));
463 return 0;
464 }
465
466 // Memory map the file
467 cacheEntry =
468 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600469
470 // We can close the file now and the mmap will remain
471 close(fd);
472
Cody Northrop6cca6c22023-02-08 20:23:13 -0700473 if (cacheEntry == MAP_FAILED) {
474 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700475 return 0;
476 }
477
478 ALOGV("GET: Adding %u to hot cache", entryHash);
479 if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
480 ALOGE("GET: Failed to add %u to hot cache", entryHash);
481 return 0;
482 }
483
484 cacheEntry = mHotCache[entryHash].entryBuffer;
485 }
486
487 // Ensure the header matches
488 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
489 if (header->keySize != keySize || header->valueSize != valueSize) {
490 ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
491 "to cache header values for fullPath: %s",
492 keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
493 removeFromHotCache(entryHash);
494 return 0;
495 }
496
497 // Compare the incoming key with our stored version (the beginning of the entry)
498 uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
499 int compare = memcmp(cachedKey, key, keySize);
500 if (compare != 0) {
501 ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
502 removeFromHotCache(entryHash);
503 return 0;
504 }
505
506 // Remaining entry following the key is the value
507 uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
508 memcpy(value, cachedValue, cachedValueSize);
509
510 return cachedValueSize;
511}
512
513void MultifileBlobCache::finish() {
514 if (!mInitialized) {
515 return;
516 }
517
518 // Wait for all deferred writes to complete
519 ALOGV("FINISH: Waiting for work to complete.");
520 waitForWorkComplete();
521
522 // Close all entries in the hot cache
523 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
524 uint32_t entryHash = hotCacheIter->first;
525 MultifileHotCache entry = hotCacheIter->second;
526
527 ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
528 freeHotCacheEntry(entry);
529
530 mHotCache.erase(hotCacheIter++);
531 }
532}
533
Cody Northrop027f2422023-11-12 22:51:01 -0700534bool MultifileBlobCache::createStatus(const std::string& baseDir) {
535 // Populate the status struct
536 struct MultifileStatus status;
537 memset(&status, 0, sizeof(status));
538 status.magic = kMultifileMagic;
539 status.cacheVersion = mCacheVersion;
540
541 // Copy the buildId string in, up to our allocated space
542 strncpy(status.buildId, mBuildId.c_str(),
543 mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
544
545 // Finally update the crc, using cacheVersion and everything the follows
Jisun Lee88a1b762024-10-17 20:12:29 +0900546 status.crc = GenerateCRC32(
547 reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
548 sizeof(status) - offsetof(MultifileStatus, cacheVersion));
Cody Northrop027f2422023-11-12 22:51:01 -0700549
550 // Create the status file
551 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
552 int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
553 if (fd == -1) {
554 ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
555 std::strerror(errno));
556 return false;
557 }
558
559 // Write the buffer contents to disk
560 ssize_t result = write(fd, &status, sizeof(status));
561 close(fd);
562 if (result != sizeof(status)) {
563 ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
564 std::strerror(errno));
565 return false;
566 }
567
568 ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
569 return true;
570}
571
572bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
573 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
574
575 // Does status exist
576 struct stat st;
577 if (stat(cacheStatus.c_str(), &st) != 0) {
578 ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
579 return false;
580 }
581
582 // If the status entry is damaged or no good, remove it
583 if (st.st_size <= 0 || st.st_atime <= 0) {
584 ALOGE("STATUS(CHECK): Cache status has invalid stats!");
585 return false;
586 }
587
588 // Open the file so we can read its header
589 int fd = open(cacheStatus.c_str(), O_RDONLY);
590 if (fd == -1) {
591 ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
592 cacheStatus.c_str(), std::strerror(errno));
593 return false;
594 }
595
596 // Read in the status header
597 MultifileStatus status;
598 size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
599 close(fd);
600 if (result != sizeof(MultifileStatus)) {
601 ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
602 std::strerror(errno));
603 return false;
604 }
605
606 // Verify header magic
607 if (status.magic != kMultifileMagic) {
608 ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
609 return false;
610 }
611
612 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900613 if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
614 offsetof(MultifileStatus, cacheVersion),
615 sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700616 ALOGE("STATUS(CHECK): Cache status failed CRC check!");
617 return false;
618 }
619
620 // Check cacheVersion
621 if (status.cacheVersion != mCacheVersion) {
622 ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
623 mCacheVersion);
624 return false;
625 }
626
627 // Check buildId
628 if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
629 ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
630 mBuildId.c_str());
631 return false;
632 }
633
634 // All checks passed!
635 ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
636 status.cacheVersion, status.buildId, cacheStatus.c_str());
637 return true;
638}
639
Cody Northrop6cca6c22023-02-08 20:23:13 -0700640void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
641 time_t accessTime) {
642 mEntries.insert(entryHash);
643 mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
Cody Northrop6aebcf22024-11-08 15:55:30 -0700644
645 increaseTotalCacheSize(fileSize);
646}
647
648bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
649 auto entryIter = mEntries.find(entryHash);
650 if (entryIter == mEntries.end()) {
651 return false;
652 }
653
654 auto entryStatsIter = mEntryStats.find(entryHash);
655 if (entryStatsIter == mEntryStats.end()) {
656 ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
657 return false;
658 }
659 decreaseTotalCacheSize(entryStatsIter->second.fileSize);
660
661 mEntryStats.erase(entryStatsIter);
662 mEntries.erase(entryIter);
663
664 return true;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700665}
666
667bool MultifileBlobCache::contains(uint32_t hashEntry) const {
668 return mEntries.find(hashEntry) != mEntries.end();
669}
670
671MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
672 return mEntryStats[entryHash];
673}
674
675void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
676 mTotalCacheSize += fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600677 mTotalCacheEntries++;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700678}
679
680void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
681 mTotalCacheSize -= fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600682 mTotalCacheEntries--;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700683}
684
685bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
686 size_t newEntrySize) {
687 ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
688
689 // Clear space if we need to
690 if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
691 ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
692 "mHotCacheLimit "
693 "(%zu), freeing up space for %u",
694 mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
695
696 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600697 ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700698 waitForWorkComplete();
699
700 // Free up old entries until under the limit
701 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
702 uint32_t oldEntryHash = hotCacheIter->first;
703 MultifileHotCache oldEntry = hotCacheIter->second;
704
705 // Move our iterator before deleting the entry
706 hotCacheIter++;
707 if (!removeFromHotCache(oldEntryHash)) {
708 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
709 return false;
710 }
711
712 // Clear at least half the hot cache
713 if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
714 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
715 break;
716 }
717 }
718 }
719
720 // Track it
721 mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
722 mHotCacheSize += newEntrySize;
723
724 ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
725
726 return true;
727}
728
729bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
730 if (mHotCache.find(entryHash) != mHotCache.end()) {
731 ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
732
733 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600734 ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700735 waitForWorkComplete();
736
737 ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
738 MultifileHotCache entry = mHotCache[entryHash];
739 freeHotCacheEntry(entry);
740
741 // Delete the entry from our tracking
742 mHotCacheSize -= entry.entrySize;
743 mHotCache.erase(entryHash);
744
745 return true;
746 }
747
748 return false;
749}
750
Cody Northropb5267032023-10-24 10:11:21 -0600751bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700752 // Walk through our map of sorted last access times and remove files until under the limit
753 for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
754 uint32_t entryHash = cacheEntryIter->first;
Cody Northrop6aebcf22024-11-08 15:55:30 -0700755 const MultifileEntryStats& entryStats = cacheEntryIter->second;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700756
757 ALOGV("LRU: Removing entryHash %u", entryHash);
758
Cody Northrop6cca6c22023-02-08 20:23:13 -0700759 // Remove it from hot cache if present
760 removeFromHotCache(entryHash);
761
762 // Remove it from the system
763 std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
764 if (remove(entryPath.c_str()) != 0) {
765 ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
766 return false;
767 }
768
769 // Increment the iterator before clearing the entry
770 cacheEntryIter++;
771
Cody Northrop6aebcf22024-11-08 15:55:30 -0700772 // Delete the entry from our tracking and update the overall cache size
773 if (!removeEntry(entryHash)) {
774 ALOGE("LRU: Failed to remove entryHash %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700775 return false;
776 }
777
778 // See if it has been reduced enough
779 size_t totalCacheSize = getTotalSize();
Cody Northropb5267032023-10-24 10:11:21 -0600780 size_t totalCacheEntries = getTotalEntries();
781 if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700782 // Success
Cody Northropb5267032023-10-24 10:11:21 -0600783 ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700784 return true;
785 }
786 }
787
Cody Northropf2588a82023-03-27 23:03:49 -0600788 ALOGV("LRU: Cache is empty");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700789 return false;
790}
791
Cody Northrop027f2422023-11-12 22:51:01 -0700792// Clear the cache by removing all entries and deleting the directory
793bool MultifileBlobCache::clearCache() {
794 DIR* dir;
795 struct dirent* entry;
796 dir = opendir(mMultifileDirName.c_str());
797 if (dir == nullptr) {
798 ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
799 return false;
800 }
801
802 // Delete all entries and the status file
803 while ((entry = readdir(dir)) != nullptr) {
804 if (entry->d_name == "."s || entry->d_name == ".."s) {
805 continue;
806 }
807
808 std::string entryName = entry->d_name;
809 std::string fullPath = mMultifileDirName + "/" + entryName;
810 if (remove(fullPath.c_str()) != 0) {
811 ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
812 return false;
813 }
814 }
815
816 // Delete the directory
817 if (remove(mMultifileDirName.c_str()) != 0) {
818 ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
819 return false;
820 }
821
822 ALOGV("CLEAR: Cleared the multifile blobcache");
823 return true;
824}
825
Cody Northrop6cca6c22023-02-08 20:23:13 -0700826// When removing files, what fraction of the overall limit should be reached when removing files
827// A divisor of two will decrease the cache to 50%, four to 25% and so on
Cody Northropb5267032023-10-24 10:11:21 -0600828// We use the same limit to manage size and entry count
Cody Northrop6cca6c22023-02-08 20:23:13 -0700829constexpr uint32_t kCacheLimitDivisor = 2;
830
831// Calculate the cache size and remove old entries until under the limit
Cody Northropf2588a82023-03-27 23:03:49 -0600832void MultifileBlobCache::trimCache() {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700833 // Wait for all deferred writes to complete
Cody Northropbe163732023-03-22 10:14:26 -0600834 ALOGV("TRIM: Waiting for work to complete.");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700835 waitForWorkComplete();
836
Cody Northropb5267032023-10-24 10:11:21 -0600837 ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
838 mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
839 if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
Cody Northropf2588a82023-03-27 23:03:49 -0600840 ALOGE("Error when clearing multifile shader cache");
841 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700842 }
843}
844
845// This function performs a task. It only knows how to write files to disk,
846// but it could be expanded if needed.
847void MultifileBlobCache::processTask(DeferredTask& task) {
848 switch (task.getTaskCommand()) {
849 case TaskCommand::Exit: {
850 ALOGV("DEFERRED: Shutting down");
851 return;
852 }
853 case TaskCommand::WriteToDisk: {
854 uint32_t entryHash = task.getEntryHash();
855 std::string& fullPath = task.getFullPath();
856 uint8_t* buffer = task.getBuffer();
857 size_t bufferSize = task.getBufferSize();
858
859 // Create the file or reset it if already present, read+write for user only
860 int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
861 if (fd == -1) {
862 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
863 fullPath.c_str(), std::strerror(errno));
864 return;
865 }
866
867 ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
868
Cody Northrop999db232023-02-27 17:02:50 -0700869 // Add CRC check to the header (always do this last!)
870 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
Jisun Lee88a1b762024-10-17 20:12:29 +0900871 header->crc = GenerateCRC32(buffer + sizeof(MultifileHeader),
872 bufferSize - sizeof(MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700873
Cody Northrop6cca6c22023-02-08 20:23:13 -0700874 ssize_t result = write(fd, buffer, bufferSize);
875 if (result != bufferSize) {
876 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
877 std::strerror(errno));
878 return;
879 }
880
881 ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
882 close(fd);
883
884 // Erase the entry from mDeferredWrites
885 // Since there could be multiple outstanding writes for an entry, find the matching one
Cody Northropbe163732023-03-22 10:14:26 -0600886 {
887 // Synchronize access to deferred write status
888 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
889 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
890 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
891 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
892 if (it->second == buffer) {
893 ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
894 it->second);
895 mDeferredWrites.erase(it);
896 break;
897 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700898 }
899 }
900
901 return;
902 }
903 default: {
904 ALOGE("DEFERRED: Unhandled task type");
905 return;
906 }
907 }
908}
909
910// This function will wait until tasks arrive, then execute them
911// If the exit command is submitted, the loop will terminate
912void MultifileBlobCache::processTasksImpl(bool* exitThread) {
913 while (true) {
914 std::unique_lock<std::mutex> lock(mWorkerMutex);
915 if (mTasks.empty()) {
916 ALOGV("WORKER: No tasks available, waiting");
917 mWorkerThreadIdle = true;
918 mWorkerIdleCondition.notify_all();
919 // Only wake if notified and command queue is not empty
920 mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
921 }
922
923 ALOGV("WORKER: Task available, waking up.");
924 mWorkerThreadIdle = false;
925 DeferredTask task = std::move(mTasks.front());
926 mTasks.pop();
927
928 if (task.getTaskCommand() == TaskCommand::Exit) {
929 ALOGV("WORKER: Exiting work loop.");
930 *exitThread = true;
931 mWorkerThreadIdle = true;
932 mWorkerIdleCondition.notify_one();
933 return;
934 }
935
936 lock.unlock();
937 processTask(task);
938 }
939}
940
941// Process tasks until the exit task is submitted
942void MultifileBlobCache::processTasks() {
943 while (true) {
944 bool exitThread = false;
945 processTasksImpl(&exitThread);
946 if (exitThread) {
947 break;
948 }
949 }
950}
951
952// Add a task to the queue to be processed by the worker thread
953void MultifileBlobCache::queueTask(DeferredTask&& task) {
954 std::lock_guard<std::mutex> queueLock(mWorkerMutex);
955 mTasks.emplace(std::move(task));
956 mWorkAvailableCondition.notify_one();
957}
958
959// Wait until all tasks have been completed
960void MultifileBlobCache::waitForWorkComplete() {
961 std::unique_lock<std::mutex> lock(mWorkerMutex);
962 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
963}
964
Cody Northrop999db232023-02-27 17:02:50 -0700965}; // namespace android