blob: f7e33b383fb3b62e254c8d56ccd7e6a8aa3d2a19 [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
41using namespace std::literals;
42
Cody Northrop999db232023-02-27 17:02:50 -070043constexpr uint32_t kMultifileMagic = 'MFB$';
44constexpr uint32_t kCrcPlaceholder = 0;
45
Cody Northrop6cca6c22023-02-08 20:23:13 -070046namespace {
47
Cody Northrop6cca6c22023-02-08 20:23:13 -070048// Helper function to close entries or free them
49void freeHotCacheEntry(android::MultifileHotCache& entry) {
50 if (entry.entryFd != -1) {
51 // If we have an fd, then this entry was added to hot cache via INIT or GET
Cody Northrop5f8117a2023-09-26 20:48:59 -060052 // We need to unmap the entry
Cody Northrop6cca6c22023-02-08 20:23:13 -070053 munmap(entry.entryBuffer, entry.entrySize);
Cody Northrop6cca6c22023-02-08 20:23:13 -070054 } else {
55 // Otherwise, this was added to hot cache during SET, so it was never mapped
56 // and fd was only on the deferred thread.
57 delete[] entry.entryBuffer;
58 }
59}
60
61} // namespace
62
63namespace android {
64
Cody Northrop5dbcfa72023-03-24 15:34:09 -060065MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
Cody Northropb5267032023-10-24 10:11:21 -060066 size_t maxTotalEntries, const std::string& baseDir)
Cody Northrop6cca6c22023-02-08 20:23:13 -070067 : mInitialized(false),
Cody Northrop027f2422023-11-12 22:51:01 -070068 mCacheVersion(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060069 mMaxKeySize(maxKeySize),
70 mMaxValueSize(maxValueSize),
Cody Northrop6cca6c22023-02-08 20:23:13 -070071 mMaxTotalSize(maxTotalSize),
Cody Northropb5267032023-10-24 10:11:21 -060072 mMaxTotalEntries(maxTotalEntries),
Cody Northrop6cca6c22023-02-08 20:23:13 -070073 mTotalCacheSize(0),
Cody Northropb5267032023-10-24 10:11:21 -060074 mTotalCacheEntries(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060075 mHotCacheLimit(0),
Cody Northrop6cca6c22023-02-08 20:23:13 -070076 mHotCacheSize(0),
77 mWorkerThreadIdle(true) {
78 if (baseDir.empty()) {
79 ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
80 return;
81 }
82
Cody Northrop027f2422023-11-12 22:51:01 -070083 // Set the cache version, override if debug value set
84 mCacheVersion = kMultifileBlobCacheVersion;
85 int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
86 if (debugCacheVersion >= 0) {
87 ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
88 mCacheVersion = debugCacheVersion;
89 }
90
91 // Set the platform build ID, override if debug value set
92 mBuildId = base::GetProperty("ro.build.id", "");
93 std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
94 if (!debugBuildId.empty()) {
95 ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
96 if (debugBuildId.length() > PROP_VALUE_MAX) {
97 ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
98 PROP_VALUE_MAX);
99 }
100 mBuildId = debugBuildId;
101 }
102
Cody Northrop6cca6c22023-02-08 20:23:13 -0700103 // Establish the name of our multifile directory
104 mMultifileDirName = baseDir + ".multifile";
105
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600106 // Set the hotcache limit to be large enough to contain one max entry
107 // This ensure the hot cache is always large enough for single entry
108 mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700109
110 ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
111 mMaxKeySize, mMaxValueSize);
112
113 // Initialize our cache with the contents of the directory
114 mTotalCacheSize = 0;
115
116 // Create the worker thread
117 mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
118
119 // See if the dir exists, and initialize using its contents
Cody Northrop027f2422023-11-12 22:51:01 -0700120 bool statusGood = false;
121
122 // Check that our cacheVersion and buildId match
Cody Northrop6cca6c22023-02-08 20:23:13 -0700123 struct stat st;
124 if (stat(mMultifileDirName.c_str(), &st) == 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700125 if (checkStatus(mMultifileDirName.c_str())) {
126 statusGood = true;
127 } else {
128 ALOGV("INIT: Cache status has changed, clearing the cache");
129 if (!clearCache()) {
130 ALOGE("INIT: Unable to clear cache");
131 return;
132 }
133 }
134 }
135
136 if (statusGood) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700137 // Read all the files and gather details, then preload their contents
138 DIR* dir;
139 struct dirent* entry;
140 if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
141 while ((entry = readdir(dir)) != nullptr) {
Cody Northrop027f2422023-11-12 22:51:01 -0700142 if (entry->d_name == "."s || entry->d_name == ".."s ||
143 strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700144 continue;
145 }
146
147 std::string entryName = entry->d_name;
148 std::string fullPath = mMultifileDirName + "/" + entryName;
149
150 // The filename is the same as the entryHash
151 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
152
153 ALOGV("INIT: Checking entry %u", entryHash);
154
155 // Look up the details of the file
156 struct stat st;
157 if (stat(fullPath.c_str(), &st) != 0) {
158 ALOGE("Failed to stat %s", fullPath.c_str());
159 return;
160 }
161
Cody Northrop999db232023-02-27 17:02:50 -0700162 // If the cache entry is damaged or no good, remove it
163 if (st.st_size <= 0 || st.st_atime <= 0) {
164 ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
165 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700166 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
167 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700168 }
169 continue;
170 }
171
Cody Northrop6cca6c22023-02-08 20:23:13 -0700172 // Open the file so we can read its header
173 int fd = open(fullPath.c_str(), O_RDONLY);
174 if (fd == -1) {
175 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
176 std::strerror(errno));
177 return;
178 }
179
Cody Northrop999db232023-02-27 17:02:50 -0700180 // Read the beginning of the file to get header
181 MultifileHeader header;
182 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
183 if (result != sizeof(MultifileHeader)) {
Cody Northrop027f2422023-11-12 22:51:01 -0700184 ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
Cody Northrop999db232023-02-27 17:02:50 -0700185 fullPath.c_str(), std::strerror(errno));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600186 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700187 return;
188 }
189
190 // Verify header magic
191 if (header.magic != kMultifileMagic) {
192 ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
193 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700194 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
195 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700196 }
Cody Northrop5f8117a2023-09-26 20:48:59 -0600197 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700198 continue;
199 }
200
201 // Note: Converting from off_t (signed) to size_t (unsigned)
202 size_t fileSize = static_cast<size_t>(st.st_size);
203
204 // Memory map the file
205 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
206 mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600207
208 // We can close the file now and the mmap will remain
209 close(fd);
210
Cody Northrop999db232023-02-27 17:02:50 -0700211 if (mappedEntry == MAP_FAILED) {
212 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
213 return;
214 }
215
216 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900217 if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
218 fileSize - sizeof(MultifileHeader))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700219 ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
Cody Northrop999db232023-02-27 17:02:50 -0700220 if (remove(fullPath.c_str()) != 0) {
221 ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
222 }
223 continue;
224 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700225
226 // If the cache entry is damaged or no good, remove it
Cody Northrop999db232023-02-27 17:02:50 -0700227 if (header.keySize <= 0 || header.valueSize <= 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700228 ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
Cody Northrop999db232023-02-27 17:02:50 -0700229 "removing.",
230 entryHash, header.keySize, header.valueSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700231 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700232 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
233 std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700234 }
235 continue;
236 }
237
238 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
239
Cody Northrop6cca6c22023-02-08 20:23:13 -0700240 // Track details for rapid lookup later
Cody Northrop999db232023-02-27 17:02:50 -0700241 trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700242
243 // Track the total size
244 increaseTotalCacheSize(fileSize);
245
246 // Preload the entry for fast retrieval
247 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700248 ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
249 "entryHash %u",
250 fd, mappedEntry, entryHash);
251
252 // Track the details of the preload so they can be retrieved later
253 if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
254 ALOGE("INIT Failed to add %u to hot cache", entryHash);
255 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700256 return;
257 }
258 } else {
Cody Northrop999db232023-02-27 17:02:50 -0700259 // If we're not keeping it in hot cache, unmap it now
260 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700261 }
262 }
263 closedir(dir);
264 } else {
265 ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
266 }
267 } else {
268 // If the multifile directory does not exist, create it and start from scratch
269 if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
270 ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
Cody Northrop027f2422023-11-12 22:51:01 -0700271 return;
272 }
273
274 // Create new status file
275 if (!createStatus(mMultifileDirName.c_str())) {
276 ALOGE("INIT: Failed to create status file!");
277 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700278 }
279 }
280
Cody Northrop027f2422023-11-12 22:51:01 -0700281 ALOGV("INIT: Multifile BlobCache initialization succeeded");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700282 mInitialized = true;
283}
284
285MultifileBlobCache::~MultifileBlobCache() {
286 if (!mInitialized) {
287 return;
288 }
289
290 // Inform the worker thread we're done
291 ALOGV("DESCTRUCTOR: Shutting down worker thread");
292 DeferredTask task(TaskCommand::Exit);
293 queueTask(std::move(task));
294
295 // Wait for it to complete
296 ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
297 waitForWorkComplete();
298 if (mTaskThread.joinable()) {
299 mTaskThread.join();
300 }
301}
302
303// Set will add the entry to hot cache and start a deferred process to write it to disk
304void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
305 EGLsizeiANDROID valueSize) {
306 if (!mInitialized) {
307 return;
308 }
309
310 // Ensure key and value are under their limits
311 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600312 ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700313 valueSize, mMaxValueSize);
314 return;
315 }
316
317 // Generate a hash of the key and use it to track this entry
318 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
319
320 size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
321
322 // If we're going to be over the cache limit, kick off a trim to clear space
Cody Northropb5267032023-10-24 10:11:21 -0600323 if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700324 ALOGV("SET: Cache is full, calling trimCache to clear space");
Cody Northropf2588a82023-03-27 23:03:49 -0600325 trimCache();
Cody Northrop6cca6c22023-02-08 20:23:13 -0700326 }
327
328 ALOGV("SET: Add %u to cache", entryHash);
329
330 uint8_t* buffer = new uint8_t[fileSize];
331
Cody Northrop45ce6802023-03-23 11:07:09 -0600332 // Write placeholders for magic and CRC until deferred thread completes the write
Cody Northrop999db232023-02-27 17:02:50 -0700333 android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
Cody Northrop6cca6c22023-02-08 20:23:13 -0700334 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
335 sizeof(android::MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700336 // Write the key and value after the header
Cody Northrop6cca6c22023-02-08 20:23:13 -0700337 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
338 keySize);
339 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
340 static_cast<const void*>(value), valueSize);
341
342 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
343
344 // Track the size and access time for quick recall
345 trackEntry(entryHash, valueSize, fileSize, time(0));
346
347 // Update the overall cache size
348 increaseTotalCacheSize(fileSize);
349
350 // Keep the entry in hot cache for quick retrieval
351 ALOGV("SET: Adding %u to hot cache.", entryHash);
352
353 // Sending -1 as the fd indicates we don't have an fd for this
354 if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
Cody Northropbe163732023-03-22 10:14:26 -0600355 ALOGE("SET: Failed to add %u to hot cache", entryHash);
Cody Northrop45ce6802023-03-23 11:07:09 -0600356 delete[] buffer;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700357 return;
358 }
359
360 // Track that we're creating a pending write for this entry
361 // Include the buffer to handle the case when multiple writes are pending for an entry
Cody Northropbe163732023-03-22 10:14:26 -0600362 {
363 // Synchronize access to deferred write status
364 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
365 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
366 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700367
368 // Create deferred task to write to storage
369 ALOGV("SET: Adding task to queue.");
370 DeferredTask task(TaskCommand::WriteToDisk);
371 task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
372 queueTask(std::move(task));
373}
374
375// Get will check the hot cache, then load it from disk if needed
376EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
377 EGLsizeiANDROID valueSize) {
378 if (!mInitialized) {
379 return 0;
380 }
381
382 // Ensure key and value are under their limits
383 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600384 ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700385 valueSize, mMaxValueSize);
386 return 0;
387 }
388
389 // Generate a hash of the key and use it to track this entry
390 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
391
392 // See if we have this file
393 if (!contains(entryHash)) {
394 ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
395 return 0;
396 }
397
398 // Look up the data for this entry
399 MultifileEntryStats entryStats = getEntryStats(entryHash);
400
401 size_t cachedValueSize = entryStats.valueSize;
402 if (cachedValueSize > valueSize) {
403 ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
404 "size (%zu)",
405 valueSize, entryHash, cachedValueSize);
406 return cachedValueSize;
407 }
408
409 // We have the file and have enough room to write it out, return the entry
410 ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
411
412 // Look up the size of the file
413 size_t fileSize = entryStats.fileSize;
414 if (keySize > fileSize) {
415 ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
416 "file",
417 keySize, fileSize);
418 return 0;
419 }
420
421 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
422
423 // Open the hashed filename path
424 uint8_t* cacheEntry = 0;
425
426 // Check hot cache
427 if (mHotCache.find(entryHash) != mHotCache.end()) {
428 ALOGV("GET: HotCache HIT for entry %u", entryHash);
429 cacheEntry = mHotCache[entryHash].entryBuffer;
430 } else {
431 ALOGV("GET: HotCache MISS for entry: %u", entryHash);
432
Cody Northropbe163732023-03-22 10:14:26 -0600433 // Wait for writes to complete if there is an outstanding write for this entry
434 bool wait = false;
435 {
436 // Synchronize access to deferred write status
437 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
438 wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
439 }
440
441 if (wait) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700442 ALOGV("GET: Waiting for write to complete for %u", entryHash);
443 waitForWorkComplete();
444 }
445
446 // Open the entry file
447 int fd = open(fullPath.c_str(), O_RDONLY);
448 if (fd == -1) {
449 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
450 std::strerror(errno));
451 return 0;
452 }
453
454 // Memory map the file
455 cacheEntry =
456 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600457
458 // We can close the file now and the mmap will remain
459 close(fd);
460
Cody Northrop6cca6c22023-02-08 20:23:13 -0700461 if (cacheEntry == MAP_FAILED) {
462 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700463 return 0;
464 }
465
466 ALOGV("GET: Adding %u to hot cache", entryHash);
467 if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
468 ALOGE("GET: Failed to add %u to hot cache", entryHash);
469 return 0;
470 }
471
472 cacheEntry = mHotCache[entryHash].entryBuffer;
473 }
474
475 // Ensure the header matches
476 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
477 if (header->keySize != keySize || header->valueSize != valueSize) {
478 ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
479 "to cache header values for fullPath: %s",
480 keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
481 removeFromHotCache(entryHash);
482 return 0;
483 }
484
485 // Compare the incoming key with our stored version (the beginning of the entry)
486 uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
487 int compare = memcmp(cachedKey, key, keySize);
488 if (compare != 0) {
489 ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
490 removeFromHotCache(entryHash);
491 return 0;
492 }
493
494 // Remaining entry following the key is the value
495 uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
496 memcpy(value, cachedValue, cachedValueSize);
497
498 return cachedValueSize;
499}
500
501void MultifileBlobCache::finish() {
502 if (!mInitialized) {
503 return;
504 }
505
506 // Wait for all deferred writes to complete
507 ALOGV("FINISH: Waiting for work to complete.");
508 waitForWorkComplete();
509
510 // Close all entries in the hot cache
511 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
512 uint32_t entryHash = hotCacheIter->first;
513 MultifileHotCache entry = hotCacheIter->second;
514
515 ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
516 freeHotCacheEntry(entry);
517
518 mHotCache.erase(hotCacheIter++);
519 }
520}
521
Cody Northrop027f2422023-11-12 22:51:01 -0700522bool MultifileBlobCache::createStatus(const std::string& baseDir) {
523 // Populate the status struct
524 struct MultifileStatus status;
525 memset(&status, 0, sizeof(status));
526 status.magic = kMultifileMagic;
527 status.cacheVersion = mCacheVersion;
528
529 // Copy the buildId string in, up to our allocated space
530 strncpy(status.buildId, mBuildId.c_str(),
531 mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
532
533 // Finally update the crc, using cacheVersion and everything the follows
Jisun Lee88a1b762024-10-17 20:12:29 +0900534 status.crc = GenerateCRC32(
535 reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
536 sizeof(status) - offsetof(MultifileStatus, cacheVersion));
Cody Northrop027f2422023-11-12 22:51:01 -0700537
538 // Create the status file
539 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
540 int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
541 if (fd == -1) {
542 ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
543 std::strerror(errno));
544 return false;
545 }
546
547 // Write the buffer contents to disk
548 ssize_t result = write(fd, &status, sizeof(status));
549 close(fd);
550 if (result != sizeof(status)) {
551 ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
552 std::strerror(errno));
553 return false;
554 }
555
556 ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
557 return true;
558}
559
560bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
561 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
562
563 // Does status exist
564 struct stat st;
565 if (stat(cacheStatus.c_str(), &st) != 0) {
566 ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
567 return false;
568 }
569
570 // If the status entry is damaged or no good, remove it
571 if (st.st_size <= 0 || st.st_atime <= 0) {
572 ALOGE("STATUS(CHECK): Cache status has invalid stats!");
573 return false;
574 }
575
576 // Open the file so we can read its header
577 int fd = open(cacheStatus.c_str(), O_RDONLY);
578 if (fd == -1) {
579 ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
580 cacheStatus.c_str(), std::strerror(errno));
581 return false;
582 }
583
584 // Read in the status header
585 MultifileStatus status;
586 size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
587 close(fd);
588 if (result != sizeof(MultifileStatus)) {
589 ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
590 std::strerror(errno));
591 return false;
592 }
593
594 // Verify header magic
595 if (status.magic != kMultifileMagic) {
596 ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
597 return false;
598 }
599
600 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900601 if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
602 offsetof(MultifileStatus, cacheVersion),
603 sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700604 ALOGE("STATUS(CHECK): Cache status failed CRC check!");
605 return false;
606 }
607
608 // Check cacheVersion
609 if (status.cacheVersion != mCacheVersion) {
610 ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
611 mCacheVersion);
612 return false;
613 }
614
615 // Check buildId
616 if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
617 ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
618 mBuildId.c_str());
619 return false;
620 }
621
622 // All checks passed!
623 ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
624 status.cacheVersion, status.buildId, cacheStatus.c_str());
625 return true;
626}
627
Cody Northrop6cca6c22023-02-08 20:23:13 -0700628void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
629 time_t accessTime) {
630 mEntries.insert(entryHash);
631 mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
632}
633
634bool MultifileBlobCache::contains(uint32_t hashEntry) const {
635 return mEntries.find(hashEntry) != mEntries.end();
636}
637
638MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
639 return mEntryStats[entryHash];
640}
641
642void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
643 mTotalCacheSize += fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600644 mTotalCacheEntries++;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700645}
646
647void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
648 mTotalCacheSize -= fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600649 mTotalCacheEntries--;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700650}
651
652bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
653 size_t newEntrySize) {
654 ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
655
656 // Clear space if we need to
657 if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
658 ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
659 "mHotCacheLimit "
660 "(%zu), freeing up space for %u",
661 mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
662
663 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600664 ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700665 waitForWorkComplete();
666
667 // Free up old entries until under the limit
668 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
669 uint32_t oldEntryHash = hotCacheIter->first;
670 MultifileHotCache oldEntry = hotCacheIter->second;
671
672 // Move our iterator before deleting the entry
673 hotCacheIter++;
674 if (!removeFromHotCache(oldEntryHash)) {
675 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
676 return false;
677 }
678
679 // Clear at least half the hot cache
680 if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
681 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
682 break;
683 }
684 }
685 }
686
687 // Track it
688 mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
689 mHotCacheSize += newEntrySize;
690
691 ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
692
693 return true;
694}
695
696bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
697 if (mHotCache.find(entryHash) != mHotCache.end()) {
698 ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
699
700 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600701 ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700702 waitForWorkComplete();
703
704 ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
705 MultifileHotCache entry = mHotCache[entryHash];
706 freeHotCacheEntry(entry);
707
708 // Delete the entry from our tracking
709 mHotCacheSize -= entry.entrySize;
710 mHotCache.erase(entryHash);
711
712 return true;
713 }
714
715 return false;
716}
717
Cody Northropb5267032023-10-24 10:11:21 -0600718bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700719 // Walk through our map of sorted last access times and remove files until under the limit
720 for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
721 uint32_t entryHash = cacheEntryIter->first;
722
723 ALOGV("LRU: Removing entryHash %u", entryHash);
724
725 // Track the overall size
726 MultifileEntryStats entryStats = getEntryStats(entryHash);
727 decreaseTotalCacheSize(entryStats.fileSize);
728
729 // Remove it from hot cache if present
730 removeFromHotCache(entryHash);
731
732 // Remove it from the system
733 std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
734 if (remove(entryPath.c_str()) != 0) {
735 ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
736 return false;
737 }
738
739 // Increment the iterator before clearing the entry
740 cacheEntryIter++;
741
742 // Delete the entry from our tracking
743 size_t count = mEntryStats.erase(entryHash);
744 if (count != 1) {
745 ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
746 return false;
747 }
748
749 // See if it has been reduced enough
750 size_t totalCacheSize = getTotalSize();
Cody Northropb5267032023-10-24 10:11:21 -0600751 size_t totalCacheEntries = getTotalEntries();
752 if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700753 // Success
Cody Northropb5267032023-10-24 10:11:21 -0600754 ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700755 return true;
756 }
757 }
758
Cody Northropf2588a82023-03-27 23:03:49 -0600759 ALOGV("LRU: Cache is empty");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700760 return false;
761}
762
Cody Northrop027f2422023-11-12 22:51:01 -0700763// Clear the cache by removing all entries and deleting the directory
764bool MultifileBlobCache::clearCache() {
765 DIR* dir;
766 struct dirent* entry;
767 dir = opendir(mMultifileDirName.c_str());
768 if (dir == nullptr) {
769 ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
770 return false;
771 }
772
773 // Delete all entries and the status file
774 while ((entry = readdir(dir)) != nullptr) {
775 if (entry->d_name == "."s || entry->d_name == ".."s) {
776 continue;
777 }
778
779 std::string entryName = entry->d_name;
780 std::string fullPath = mMultifileDirName + "/" + entryName;
781 if (remove(fullPath.c_str()) != 0) {
782 ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
783 return false;
784 }
785 }
786
787 // Delete the directory
788 if (remove(mMultifileDirName.c_str()) != 0) {
789 ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
790 return false;
791 }
792
793 ALOGV("CLEAR: Cleared the multifile blobcache");
794 return true;
795}
796
Cody Northrop6cca6c22023-02-08 20:23:13 -0700797// When removing files, what fraction of the overall limit should be reached when removing files
798// A divisor of two will decrease the cache to 50%, four to 25% and so on
Cody Northropb5267032023-10-24 10:11:21 -0600799// We use the same limit to manage size and entry count
Cody Northrop6cca6c22023-02-08 20:23:13 -0700800constexpr uint32_t kCacheLimitDivisor = 2;
801
802// Calculate the cache size and remove old entries until under the limit
Cody Northropf2588a82023-03-27 23:03:49 -0600803void MultifileBlobCache::trimCache() {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700804 // Wait for all deferred writes to complete
Cody Northropbe163732023-03-22 10:14:26 -0600805 ALOGV("TRIM: Waiting for work to complete.");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700806 waitForWorkComplete();
807
Cody Northropb5267032023-10-24 10:11:21 -0600808 ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
809 mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
810 if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
Cody Northropf2588a82023-03-27 23:03:49 -0600811 ALOGE("Error when clearing multifile shader cache");
812 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700813 }
814}
815
816// This function performs a task. It only knows how to write files to disk,
817// but it could be expanded if needed.
818void MultifileBlobCache::processTask(DeferredTask& task) {
819 switch (task.getTaskCommand()) {
820 case TaskCommand::Exit: {
821 ALOGV("DEFERRED: Shutting down");
822 return;
823 }
824 case TaskCommand::WriteToDisk: {
825 uint32_t entryHash = task.getEntryHash();
826 std::string& fullPath = task.getFullPath();
827 uint8_t* buffer = task.getBuffer();
828 size_t bufferSize = task.getBufferSize();
829
830 // Create the file or reset it if already present, read+write for user only
831 int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
832 if (fd == -1) {
833 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
834 fullPath.c_str(), std::strerror(errno));
835 return;
836 }
837
838 ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
839
Cody Northrop999db232023-02-27 17:02:50 -0700840 // Add CRC check to the header (always do this last!)
841 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
Jisun Lee88a1b762024-10-17 20:12:29 +0900842 header->crc = GenerateCRC32(buffer + sizeof(MultifileHeader),
843 bufferSize - sizeof(MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700844
Cody Northrop6cca6c22023-02-08 20:23:13 -0700845 ssize_t result = write(fd, buffer, bufferSize);
846 if (result != bufferSize) {
847 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
848 std::strerror(errno));
849 return;
850 }
851
852 ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
853 close(fd);
854
855 // Erase the entry from mDeferredWrites
856 // Since there could be multiple outstanding writes for an entry, find the matching one
Cody Northropbe163732023-03-22 10:14:26 -0600857 {
858 // Synchronize access to deferred write status
859 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
860 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
861 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
862 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
863 if (it->second == buffer) {
864 ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
865 it->second);
866 mDeferredWrites.erase(it);
867 break;
868 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700869 }
870 }
871
872 return;
873 }
874 default: {
875 ALOGE("DEFERRED: Unhandled task type");
876 return;
877 }
878 }
879}
880
881// This function will wait until tasks arrive, then execute them
882// If the exit command is submitted, the loop will terminate
883void MultifileBlobCache::processTasksImpl(bool* exitThread) {
884 while (true) {
885 std::unique_lock<std::mutex> lock(mWorkerMutex);
886 if (mTasks.empty()) {
887 ALOGV("WORKER: No tasks available, waiting");
888 mWorkerThreadIdle = true;
889 mWorkerIdleCondition.notify_all();
890 // Only wake if notified and command queue is not empty
891 mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
892 }
893
894 ALOGV("WORKER: Task available, waking up.");
895 mWorkerThreadIdle = false;
896 DeferredTask task = std::move(mTasks.front());
897 mTasks.pop();
898
899 if (task.getTaskCommand() == TaskCommand::Exit) {
900 ALOGV("WORKER: Exiting work loop.");
901 *exitThread = true;
902 mWorkerThreadIdle = true;
903 mWorkerIdleCondition.notify_one();
904 return;
905 }
906
907 lock.unlock();
908 processTask(task);
909 }
910}
911
912// Process tasks until the exit task is submitted
913void MultifileBlobCache::processTasks() {
914 while (true) {
915 bool exitThread = false;
916 processTasksImpl(&exitThread);
917 if (exitThread) {
918 break;
919 }
920 }
921}
922
923// Add a task to the queue to be processed by the worker thread
924void MultifileBlobCache::queueTask(DeferredTask&& task) {
925 std::lock_guard<std::mutex> queueLock(mWorkerMutex);
926 mTasks.emplace(std::move(task));
927 mWorkAvailableCondition.notify_one();
928}
929
930// Wait until all tasks have been completed
931void MultifileBlobCache::waitForWorkComplete() {
932 std::unique_lock<std::mutex> lock(mWorkerMutex);
933 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
934}
935
Cody Northrop999db232023-02-27 17:02:50 -0700936}; // namespace android