blob: 53a08bb59d7b18205e666407723972008476fe85 [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 Northrop99e8f2c2024-11-21 15:32:32 -070050// When removing files, what fraction of the overall limit should be reached when removing files
51// A divisor of two will decrease the cache to 50%, four to 25% and so on
52// We use the same limit to manage size and entry count
53constexpr uint32_t kCacheLimitDivisor = 2;
54
Cody Northrop6cca6c22023-02-08 20:23:13 -070055namespace {
56
Cody Northrop6cca6c22023-02-08 20:23:13 -070057// Helper function to close entries or free them
58void freeHotCacheEntry(android::MultifileHotCache& entry) {
59 if (entry.entryFd != -1) {
60 // If we have an fd, then this entry was added to hot cache via INIT or GET
Cody Northrop5f8117a2023-09-26 20:48:59 -060061 // We need to unmap the entry
Cody Northrop6cca6c22023-02-08 20:23:13 -070062 munmap(entry.entryBuffer, entry.entrySize);
Cody Northrop6cca6c22023-02-08 20:23:13 -070063 } else {
64 // Otherwise, this was added to hot cache during SET, so it was never mapped
65 // and fd was only on the deferred thread.
66 delete[] entry.entryBuffer;
67 }
68}
69
70} // namespace
71
72namespace android {
73
Cody Northrop5dbcfa72023-03-24 15:34:09 -060074MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
Cody Northropb5267032023-10-24 10:11:21 -060075 size_t maxTotalEntries, const std::string& baseDir)
Cody Northrop6cca6c22023-02-08 20:23:13 -070076 : mInitialized(false),
Cody Northrop027f2422023-11-12 22:51:01 -070077 mCacheVersion(0),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060078 mMaxKeySize(maxKeySize),
79 mMaxValueSize(maxValueSize),
Cody Northrop6cca6c22023-02-08 20:23:13 -070080 mMaxTotalSize(maxTotalSize),
Cody Northropb5267032023-10-24 10:11:21 -060081 mMaxTotalEntries(maxTotalEntries),
Cody Northrop6cca6c22023-02-08 20:23:13 -070082 mTotalCacheSize(0),
Cody Northropb5267032023-10-24 10:11:21 -060083 mTotalCacheEntries(0),
Cody Northrop99e8f2c2024-11-21 15:32:32 -070084 mTotalCacheSizeDivisor(kCacheLimitDivisor),
Cody Northrop5dbcfa72023-03-24 15:34:09 -060085 mHotCacheLimit(0),
Cody Northrop6cca6c22023-02-08 20:23:13 -070086 mHotCacheSize(0),
87 mWorkerThreadIdle(true) {
88 if (baseDir.empty()) {
89 ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
90 return;
91 }
92
Cody Northrop4ee63862024-11-19 22:41:23 -070093 // Set the cache version
Cody Northrop027f2422023-11-12 22:51:01 -070094 mCacheVersion = kMultifileBlobCacheVersion;
Cody Northrop4ee63862024-11-19 22:41:23 -070095 // Bump the version if we're using flagged features
96 if (flags::multifile_blobcache_advanced_usage()) {
97 mCacheVersion++;
98 }
99 // Override if debug value set
Cody Northrop027f2422023-11-12 22:51:01 -0700100 int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
101 if (debugCacheVersion >= 0) {
102 ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
103 mCacheVersion = debugCacheVersion;
104 }
105
106 // Set the platform build ID, override if debug value set
107 mBuildId = base::GetProperty("ro.build.id", "");
108 std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
109 if (!debugBuildId.empty()) {
110 ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
111 if (debugBuildId.length() > PROP_VALUE_MAX) {
112 ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
113 PROP_VALUE_MAX);
114 }
115 mBuildId = debugBuildId;
116 }
117
Cody Northrop6cca6c22023-02-08 20:23:13 -0700118 // Establish the name of our multifile directory
119 mMultifileDirName = baseDir + ".multifile";
120
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600121 // Set the hotcache limit to be large enough to contain one max entry
122 // This ensure the hot cache is always large enough for single entry
123 mHotCacheLimit = mMaxKeySize + mMaxValueSize + sizeof(MultifileHeader);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700124
125 ALOGV("INIT: Initializing multifile blobcache with maxKeySize=%zu and maxValueSize=%zu",
126 mMaxKeySize, mMaxValueSize);
127
128 // Initialize our cache with the contents of the directory
129 mTotalCacheSize = 0;
130
131 // Create the worker thread
132 mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
133
134 // See if the dir exists, and initialize using its contents
Cody Northrop027f2422023-11-12 22:51:01 -0700135 bool statusGood = false;
136
137 // Check that our cacheVersion and buildId match
Cody Northrop6cca6c22023-02-08 20:23:13 -0700138 struct stat st;
139 if (stat(mMultifileDirName.c_str(), &st) == 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700140 if (checkStatus(mMultifileDirName.c_str())) {
141 statusGood = true;
142 } else {
143 ALOGV("INIT: Cache status has changed, clearing the cache");
144 if (!clearCache()) {
145 ALOGE("INIT: Unable to clear cache");
146 return;
147 }
148 }
149 }
150
151 if (statusGood) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700152 // Read all the files and gather details, then preload their contents
153 DIR* dir;
154 struct dirent* entry;
155 if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
156 while ((entry = readdir(dir)) != nullptr) {
Cody Northrop027f2422023-11-12 22:51:01 -0700157 if (entry->d_name == "."s || entry->d_name == ".."s ||
158 strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700159 continue;
160 }
161
162 std::string entryName = entry->d_name;
163 std::string fullPath = mMultifileDirName + "/" + entryName;
164
165 // The filename is the same as the entryHash
166 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
167
168 ALOGV("INIT: Checking entry %u", entryHash);
169
170 // Look up the details of the file
171 struct stat st;
172 if (stat(fullPath.c_str(), &st) != 0) {
173 ALOGE("Failed to stat %s", fullPath.c_str());
174 return;
175 }
176
Cody Northrop999db232023-02-27 17:02:50 -0700177 // If the cache entry is damaged or no good, remove it
178 if (st.st_size <= 0 || st.st_atime <= 0) {
179 ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
180 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700181 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
182 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700183 }
184 continue;
185 }
186
Cody Northrop6cca6c22023-02-08 20:23:13 -0700187 // Open the file so we can read its header
188 int fd = open(fullPath.c_str(), O_RDONLY);
189 if (fd == -1) {
190 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
191 std::strerror(errno));
192 return;
193 }
194
Cody Northrop999db232023-02-27 17:02:50 -0700195 // Read the beginning of the file to get header
196 MultifileHeader header;
197 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
198 if (result != sizeof(MultifileHeader)) {
Cody Northrop027f2422023-11-12 22:51:01 -0700199 ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
Cody Northrop999db232023-02-27 17:02:50 -0700200 fullPath.c_str(), std::strerror(errno));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600201 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700202 return;
203 }
204
205 // Verify header magic
206 if (header.magic != kMultifileMagic) {
207 ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
208 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700209 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
210 std::strerror(errno));
Cody Northrop999db232023-02-27 17:02:50 -0700211 }
Cody Northrop5f8117a2023-09-26 20:48:59 -0600212 close(fd);
Cody Northrop999db232023-02-27 17:02:50 -0700213 continue;
214 }
215
216 // Note: Converting from off_t (signed) to size_t (unsigned)
217 size_t fileSize = static_cast<size_t>(st.st_size);
218
219 // Memory map the file
220 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
221 mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600222
223 // We can close the file now and the mmap will remain
224 close(fd);
225
Cody Northrop999db232023-02-27 17:02:50 -0700226 if (mappedEntry == MAP_FAILED) {
227 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
228 return;
229 }
230
231 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900232 if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
233 fileSize - sizeof(MultifileHeader))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700234 ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
Cody Northrop999db232023-02-27 17:02:50 -0700235 if (remove(fullPath.c_str()) != 0) {
236 ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
237 }
238 continue;
239 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700240
241 // If the cache entry is damaged or no good, remove it
Cody Northrop999db232023-02-27 17:02:50 -0700242 if (header.keySize <= 0 || header.valueSize <= 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700243 ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
Cody Northrop999db232023-02-27 17:02:50 -0700244 "removing.",
245 entryHash, header.keySize, header.valueSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700246 if (remove(fullPath.c_str()) != 0) {
Cody Northrop027f2422023-11-12 22:51:01 -0700247 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
248 std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700249 }
250 continue;
251 }
252
253 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
254
Cody Northrop6aebcf22024-11-08 15:55:30 -0700255 // Track details for rapid lookup later and update total size
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700256 // Note access time is a full timespec instead of just seconds
257 trackEntry(entryHash, header.valueSize, fileSize, st.st_atim);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700258
Cody Northrop6cca6c22023-02-08 20:23:13 -0700259 // Preload the entry for fast retrieval
260 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700261 ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
262 "entryHash %u",
263 fd, mappedEntry, entryHash);
264
265 // Track the details of the preload so they can be retrieved later
266 if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
267 ALOGE("INIT Failed to add %u to hot cache", entryHash);
268 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700269 return;
270 }
271 } else {
Cody Northrop999db232023-02-27 17:02:50 -0700272 // If we're not keeping it in hot cache, unmap it now
273 munmap(mappedEntry, fileSize);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700274 }
275 }
276 closedir(dir);
277 } else {
278 ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
279 }
280 } else {
281 // If the multifile directory does not exist, create it and start from scratch
282 if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
283 ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
Cody Northrop027f2422023-11-12 22:51:01 -0700284 return;
285 }
286
287 // Create new status file
288 if (!createStatus(mMultifileDirName.c_str())) {
289 ALOGE("INIT: Failed to create status file!");
290 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700291 }
292 }
293
Cody Northrop027f2422023-11-12 22:51:01 -0700294 ALOGV("INIT: Multifile BlobCache initialization succeeded");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700295 mInitialized = true;
296}
297
298MultifileBlobCache::~MultifileBlobCache() {
299 if (!mInitialized) {
300 return;
301 }
302
303 // Inform the worker thread we're done
304 ALOGV("DESCTRUCTOR: Shutting down worker thread");
305 DeferredTask task(TaskCommand::Exit);
306 queueTask(std::move(task));
307
308 // Wait for it to complete
309 ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
310 waitForWorkComplete();
311 if (mTaskThread.joinable()) {
312 mTaskThread.join();
313 }
314}
315
316// Set will add the entry to hot cache and start a deferred process to write it to disk
317void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
318 EGLsizeiANDROID valueSize) {
319 if (!mInitialized) {
320 return;
321 }
322
323 // Ensure key and value are under their limits
324 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600325 ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700326 valueSize, mMaxValueSize);
327 return;
328 }
329
330 // Generate a hash of the key and use it to track this entry
331 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
332
Cody Northrop6aebcf22024-11-08 15:55:30 -0700333 // See if we already have this file
334 if (flags::multifile_blobcache_advanced_usage() && contains(entryHash)) {
335 // Remove previous entry from hot cache
336 removeFromHotCache(entryHash);
337
338 // Remove previous entry and update the overall cache size
339 removeEntry(entryHash);
340 }
341
Cody Northrop6cca6c22023-02-08 20:23:13 -0700342 size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
343
344 // If we're going to be over the cache limit, kick off a trim to clear space
Cody Northropb5267032023-10-24 10:11:21 -0600345 if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700346 ALOGV("SET: Cache is full, calling trimCache to clear space");
Cody Northropf2588a82023-03-27 23:03:49 -0600347 trimCache();
Cody Northrop6cca6c22023-02-08 20:23:13 -0700348 }
349
350 ALOGV("SET: Add %u to cache", entryHash);
351
352 uint8_t* buffer = new uint8_t[fileSize];
353
Cody Northrop45ce6802023-03-23 11:07:09 -0600354 // Write placeholders for magic and CRC until deferred thread completes the write
Cody Northrop999db232023-02-27 17:02:50 -0700355 android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
Cody Northrop6cca6c22023-02-08 20:23:13 -0700356 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
357 sizeof(android::MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700358 // Write the key and value after the header
Cody Northrop6cca6c22023-02-08 20:23:13 -0700359 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
360 keySize);
361 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
362 static_cast<const void*>(value), valueSize);
363
364 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
365
Cody Northrop6aebcf22024-11-08 15:55:30 -0700366 // Track the size and access time for quick recall and update the overall cache size
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700367 struct timespec time = {0, 0};
368 if (flags::multifile_blobcache_advanced_usage()) {
369 clock_gettime(CLOCK_REALTIME, &time);
370 }
371 trackEntry(entryHash, valueSize, fileSize, time);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700372
Cody Northrop6cca6c22023-02-08 20:23:13 -0700373 // Keep the entry in hot cache for quick retrieval
374 ALOGV("SET: Adding %u to hot cache.", entryHash);
375
376 // Sending -1 as the fd indicates we don't have an fd for this
377 if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
Cody Northropbe163732023-03-22 10:14:26 -0600378 ALOGE("SET: Failed to add %u to hot cache", entryHash);
Cody Northrop45ce6802023-03-23 11:07:09 -0600379 delete[] buffer;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700380 return;
381 }
382
383 // Track that we're creating a pending write for this entry
384 // Include the buffer to handle the case when multiple writes are pending for an entry
Cody Northropbe163732023-03-22 10:14:26 -0600385 {
386 // Synchronize access to deferred write status
387 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
388 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
389 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700390
391 // Create deferred task to write to storage
392 ALOGV("SET: Adding task to queue.");
393 DeferredTask task(TaskCommand::WriteToDisk);
394 task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
395 queueTask(std::move(task));
396}
397
398// Get will check the hot cache, then load it from disk if needed
399EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
400 EGLsizeiANDROID valueSize) {
401 if (!mInitialized) {
402 return 0;
403 }
404
405 // Ensure key and value are under their limits
406 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
Cody Northrop5dbcfa72023-03-24 15:34:09 -0600407 ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
Cody Northrop6cca6c22023-02-08 20:23:13 -0700408 valueSize, mMaxValueSize);
409 return 0;
410 }
411
412 // Generate a hash of the key and use it to track this entry
413 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
414
415 // See if we have this file
416 if (!contains(entryHash)) {
417 ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
418 return 0;
419 }
420
421 // Look up the data for this entry
422 MultifileEntryStats entryStats = getEntryStats(entryHash);
423
424 size_t cachedValueSize = entryStats.valueSize;
425 if (cachedValueSize > valueSize) {
426 ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
427 "size (%zu)",
428 valueSize, entryHash, cachedValueSize);
429 return cachedValueSize;
430 }
431
432 // We have the file and have enough room to write it out, return the entry
433 ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
434
435 // Look up the size of the file
436 size_t fileSize = entryStats.fileSize;
437 if (keySize > fileSize) {
438 ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
439 "file",
440 keySize, fileSize);
441 return 0;
442 }
443
444 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
445
446 // Open the hashed filename path
447 uint8_t* cacheEntry = 0;
448
449 // Check hot cache
450 if (mHotCache.find(entryHash) != mHotCache.end()) {
451 ALOGV("GET: HotCache HIT for entry %u", entryHash);
452 cacheEntry = mHotCache[entryHash].entryBuffer;
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700453
454 if (flags::multifile_blobcache_advanced_usage()) {
455 // Update last access time on disk
456 struct timespec times[2];
457 times[0].tv_nsec = UTIME_NOW;
458 times[1].tv_nsec = UTIME_OMIT;
459 utimensat(0, fullPath.c_str(), times, 0);
460 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700461 } else {
462 ALOGV("GET: HotCache MISS for entry: %u", entryHash);
463
Cody Northropbe163732023-03-22 10:14:26 -0600464 // Wait for writes to complete if there is an outstanding write for this entry
465 bool wait = false;
466 {
467 // Synchronize access to deferred write status
468 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
469 wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
470 }
471
472 if (wait) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700473 ALOGV("GET: Waiting for write to complete for %u", entryHash);
474 waitForWorkComplete();
475 }
476
477 // Open the entry file
478 int fd = open(fullPath.c_str(), O_RDONLY);
479 if (fd == -1) {
480 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
481 std::strerror(errno));
482 return 0;
483 }
484
485 // Memory map the file
486 cacheEntry =
487 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
Cody Northrop5f8117a2023-09-26 20:48:59 -0600488
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700489 if (flags::multifile_blobcache_advanced_usage()) {
490 // Update last access time and omit last modify time
491 struct timespec times[2];
492 times[0].tv_nsec = UTIME_NOW;
493 times[1].tv_nsec = UTIME_OMIT;
494 futimens(fd, times);
495 }
496
Cody Northrop5f8117a2023-09-26 20:48:59 -0600497 // We can close the file now and the mmap will remain
498 close(fd);
499
Cody Northrop6cca6c22023-02-08 20:23:13 -0700500 if (cacheEntry == MAP_FAILED) {
501 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
Cody Northrop6cca6c22023-02-08 20:23:13 -0700502 return 0;
503 }
504
505 ALOGV("GET: Adding %u to hot cache", entryHash);
506 if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
507 ALOGE("GET: Failed to add %u to hot cache", entryHash);
508 return 0;
509 }
510
511 cacheEntry = mHotCache[entryHash].entryBuffer;
512 }
513
514 // Ensure the header matches
515 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
516 if (header->keySize != keySize || header->valueSize != valueSize) {
517 ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
518 "to cache header values for fullPath: %s",
519 keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
520 removeFromHotCache(entryHash);
521 return 0;
522 }
523
524 // Compare the incoming key with our stored version (the beginning of the entry)
525 uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
526 int compare = memcmp(cachedKey, key, keySize);
527 if (compare != 0) {
528 ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
529 removeFromHotCache(entryHash);
530 return 0;
531 }
532
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700533 if (flags::multifile_blobcache_advanced_usage()) {
534 // Update the entry time for this hash, so it reflects LRU
535 struct timespec time;
536 clock_gettime(CLOCK_REALTIME, &time);
537 updateEntryTime(entryHash, time);
538 }
539
Cody Northrop6cca6c22023-02-08 20:23:13 -0700540 // Remaining entry following the key is the value
541 uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
542 memcpy(value, cachedValue, cachedValueSize);
543
544 return cachedValueSize;
545}
546
547void MultifileBlobCache::finish() {
548 if (!mInitialized) {
549 return;
550 }
551
552 // Wait for all deferred writes to complete
553 ALOGV("FINISH: Waiting for work to complete.");
554 waitForWorkComplete();
555
556 // Close all entries in the hot cache
557 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
558 uint32_t entryHash = hotCacheIter->first;
559 MultifileHotCache entry = hotCacheIter->second;
560
561 ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
562 freeHotCacheEntry(entry);
563
564 mHotCache.erase(hotCacheIter++);
565 }
566}
567
Cody Northrop027f2422023-11-12 22:51:01 -0700568bool MultifileBlobCache::createStatus(const std::string& baseDir) {
569 // Populate the status struct
570 struct MultifileStatus status;
571 memset(&status, 0, sizeof(status));
572 status.magic = kMultifileMagic;
573 status.cacheVersion = mCacheVersion;
574
575 // Copy the buildId string in, up to our allocated space
576 strncpy(status.buildId, mBuildId.c_str(),
577 mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
578
579 // Finally update the crc, using cacheVersion and everything the follows
Jisun Lee88a1b762024-10-17 20:12:29 +0900580 status.crc = GenerateCRC32(
581 reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
582 sizeof(status) - offsetof(MultifileStatus, cacheVersion));
Cody Northrop027f2422023-11-12 22:51:01 -0700583
584 // Create the status file
585 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
586 int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
587 if (fd == -1) {
588 ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
589 std::strerror(errno));
590 return false;
591 }
592
593 // Write the buffer contents to disk
594 ssize_t result = write(fd, &status, sizeof(status));
595 close(fd);
596 if (result != sizeof(status)) {
597 ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
598 std::strerror(errno));
599 return false;
600 }
601
602 ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
603 return true;
604}
605
606bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
607 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
608
609 // Does status exist
610 struct stat st;
611 if (stat(cacheStatus.c_str(), &st) != 0) {
612 ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
613 return false;
614 }
615
616 // If the status entry is damaged or no good, remove it
617 if (st.st_size <= 0 || st.st_atime <= 0) {
618 ALOGE("STATUS(CHECK): Cache status has invalid stats!");
619 return false;
620 }
621
622 // Open the file so we can read its header
623 int fd = open(cacheStatus.c_str(), O_RDONLY);
624 if (fd == -1) {
625 ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
626 cacheStatus.c_str(), std::strerror(errno));
627 return false;
628 }
629
630 // Read in the status header
631 MultifileStatus status;
632 size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
633 close(fd);
634 if (result != sizeof(MultifileStatus)) {
635 ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
636 std::strerror(errno));
637 return false;
638 }
639
640 // Verify header magic
641 if (status.magic != kMultifileMagic) {
642 ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
643 return false;
644 }
645
646 // Ensure we have a good CRC
Jisun Lee88a1b762024-10-17 20:12:29 +0900647 if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
648 offsetof(MultifileStatus, cacheVersion),
649 sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
Cody Northrop027f2422023-11-12 22:51:01 -0700650 ALOGE("STATUS(CHECK): Cache status failed CRC check!");
651 return false;
652 }
653
654 // Check cacheVersion
655 if (status.cacheVersion != mCacheVersion) {
656 ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
657 mCacheVersion);
658 return false;
659 }
660
661 // Check buildId
662 if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
663 ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
664 mBuildId.c_str());
665 return false;
666 }
667
668 // All checks passed!
669 ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
670 status.cacheVersion, status.buildId, cacheStatus.c_str());
671 return true;
672}
673
Cody Northrop6cca6c22023-02-08 20:23:13 -0700674void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700675 const timespec& accessTime) {
676#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
677 // When we add this entry to the map, it is sorted by accessTime
678 MultifileEntryStatsMapIter entryStatsIter =
679 mEntryStats.emplace(std::piecewise_construct, std::forward_as_tuple(accessTime),
680 std::forward_as_tuple(entryHash, valueSize, fileSize));
681
682 // Track all entries with quick access to its stats
683 mEntries.emplace(entryHash, entryStatsIter);
684#else
685 (void)accessTime;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700686 mEntries.insert(entryHash);
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700687 mEntryStats[entryHash] = {entryHash, valueSize, fileSize};
688#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
Cody Northrop6aebcf22024-11-08 15:55:30 -0700689
690 increaseTotalCacheSize(fileSize);
691}
692
693bool MultifileBlobCache::removeEntry(uint32_t entryHash) {
694 auto entryIter = mEntries.find(entryHash);
695 if (entryIter == mEntries.end()) {
696 return false;
697 }
698
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700699#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
700 MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
701 MultifileEntryStats entryStats = entryStatsIter->second;
702 decreaseTotalCacheSize(entryStats.fileSize);
703#else
Cody Northrop6aebcf22024-11-08 15:55:30 -0700704 auto entryStatsIter = mEntryStats.find(entryHash);
705 if (entryStatsIter == mEntryStats.end()) {
706 ALOGE("Failed to remove entryHash (%u) from mEntryStats", entryHash);
707 return false;
708 }
709 decreaseTotalCacheSize(entryStatsIter->second.fileSize);
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700710#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
Cody Northrop6aebcf22024-11-08 15:55:30 -0700711
712 mEntryStats.erase(entryStatsIter);
713 mEntries.erase(entryIter);
714
715 return true;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700716}
717
718bool MultifileBlobCache::contains(uint32_t hashEntry) const {
719 return mEntries.find(hashEntry) != mEntries.end();
720}
721
722MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700723#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
724 auto entryIter = mEntries.find(entryHash);
725 if (entryIter == mEntries.end()) {
726 return {};
727 }
728
729 MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
730 MultifileEntryStats entryStats = entryStatsIter->second;
731 return entryStats;
732#else
Cody Northrop6cca6c22023-02-08 20:23:13 -0700733 return mEntryStats[entryHash];
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700734#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
735}
736
737void MultifileBlobCache::updateEntryTime(uint32_t entryHash, const timespec& newTime) {
738#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
739 // This function updates the ordering of the map by removing the old iterators
740 // and re-adding them. If should be perforant as it does not perform a full re-sort.
741 // First, pull out the old entryStats
742 auto entryIter = mEntries.find(entryHash);
743 MultifileEntryStatsMapIter entryStatsIter = entryIter->second;
744 MultifileEntryStats entryStats = std::move(entryStatsIter->second);
745
746 // Remove the old iterators
747 mEntryStats.erase(entryStatsIter);
748 mEntries.erase(entryIter);
749
750 // Insert the new with updated time
751 entryStatsIter = mEntryStats.emplace(std::make_pair(newTime, std::move(entryStats)));
752 mEntries.emplace(entryHash, entryStatsIter);
753#else
754 (void)entryHash;
755 (void)newTime;
756#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
Cody Northrop6cca6c22023-02-08 20:23:13 -0700757}
758
759void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
760 mTotalCacheSize += fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600761 mTotalCacheEntries++;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700762}
763
764void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
765 mTotalCacheSize -= fileSize;
Cody Northropb5267032023-10-24 10:11:21 -0600766 mTotalCacheEntries--;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700767}
768
769bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
770 size_t newEntrySize) {
771 ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
772
773 // Clear space if we need to
774 if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
775 ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
776 "mHotCacheLimit "
777 "(%zu), freeing up space for %u",
778 mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
779
780 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600781 ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700782 waitForWorkComplete();
783
784 // Free up old entries until under the limit
785 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
786 uint32_t oldEntryHash = hotCacheIter->first;
787 MultifileHotCache oldEntry = hotCacheIter->second;
788
789 // Move our iterator before deleting the entry
790 hotCacheIter++;
791 if (!removeFromHotCache(oldEntryHash)) {
792 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
793 return false;
794 }
795
796 // Clear at least half the hot cache
797 if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
798 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
799 break;
800 }
801 }
802 }
803
804 // Track it
805 mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
806 mHotCacheSize += newEntrySize;
807
808 ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
809
810 return true;
811}
812
813bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
814 if (mHotCache.find(entryHash) != mHotCache.end()) {
815 ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
816
817 // Wait for all the files to complete writing so our hot cache is accurate
Cody Northropbe163732023-03-22 10:14:26 -0600818 ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700819 waitForWorkComplete();
820
821 ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
822 MultifileHotCache entry = mHotCache[entryHash];
823 freeHotCacheEntry(entry);
824
825 // Delete the entry from our tracking
826 mHotCacheSize -= entry.entrySize;
827 mHotCache.erase(entryHash);
828
829 return true;
830 }
831
832 return false;
833}
834
Cody Northropb5267032023-10-24 10:11:21 -0600835bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700836 // Walk through our map of sorted last access times and remove files until under the limit
837 for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700838#if COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
839 const MultifileEntryStats& entryStats = cacheEntryIter->second;
840 uint32_t entryHash = entryStats.entryHash;
841#else
Cody Northrop6cca6c22023-02-08 20:23:13 -0700842 uint32_t entryHash = cacheEntryIter->first;
Cody Northrop6aebcf22024-11-08 15:55:30 -0700843 const MultifileEntryStats& entryStats = cacheEntryIter->second;
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700844#endif // COM_ANDROID_GRAPHICS_EGL_FLAGS(MULTIFILE_BLOBCACHE_ADVANCED_USAGE)
Cody Northrop6cca6c22023-02-08 20:23:13 -0700845
846 ALOGV("LRU: Removing entryHash %u", entryHash);
847
Cody Northrop6cca6c22023-02-08 20:23:13 -0700848 // Remove it from hot cache if present
849 removeFromHotCache(entryHash);
850
851 // Remove it from the system
852 std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
853 if (remove(entryPath.c_str()) != 0) {
854 ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
855 return false;
856 }
857
858 // Increment the iterator before clearing the entry
859 cacheEntryIter++;
860
Cody Northrop6aebcf22024-11-08 15:55:30 -0700861 // Delete the entry from our tracking and update the overall cache size
862 if (!removeEntry(entryHash)) {
863 ALOGE("LRU: Failed to remove entryHash %u", entryHash);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700864 return false;
865 }
866
867 // See if it has been reduced enough
868 size_t totalCacheSize = getTotalSize();
Cody Northropb5267032023-10-24 10:11:21 -0600869 size_t totalCacheEntries = getTotalEntries();
870 if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700871 // Success
Cody Northropb5267032023-10-24 10:11:21 -0600872 ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
Cody Northrop6cca6c22023-02-08 20:23:13 -0700873 return true;
874 }
875 }
876
Cody Northropf2588a82023-03-27 23:03:49 -0600877 ALOGV("LRU: Cache is empty");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700878 return false;
879}
880
Cody Northrop027f2422023-11-12 22:51:01 -0700881// Clear the cache by removing all entries and deleting the directory
882bool MultifileBlobCache::clearCache() {
883 DIR* dir;
884 struct dirent* entry;
885 dir = opendir(mMultifileDirName.c_str());
886 if (dir == nullptr) {
887 ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
888 return false;
889 }
890
891 // Delete all entries and the status file
892 while ((entry = readdir(dir)) != nullptr) {
893 if (entry->d_name == "."s || entry->d_name == ".."s) {
894 continue;
895 }
896
897 std::string entryName = entry->d_name;
898 std::string fullPath = mMultifileDirName + "/" + entryName;
899 if (remove(fullPath.c_str()) != 0) {
900 ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
901 return false;
902 }
903 }
904
905 // Delete the directory
906 if (remove(mMultifileDirName.c_str()) != 0) {
907 ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
908 return false;
909 }
910
911 ALOGV("CLEAR: Cleared the multifile blobcache");
912 return true;
913}
914
Cody Northrop6cca6c22023-02-08 20:23:13 -0700915// Calculate the cache size and remove old entries until under the limit
Cody Northropf2588a82023-03-27 23:03:49 -0600916void MultifileBlobCache::trimCache() {
Cody Northrop6cca6c22023-02-08 20:23:13 -0700917 // Wait for all deferred writes to complete
Cody Northropbe163732023-03-22 10:14:26 -0600918 ALOGV("TRIM: Waiting for work to complete.");
Cody Northrop6cca6c22023-02-08 20:23:13 -0700919 waitForWorkComplete();
920
Cody Northropb5267032023-10-24 10:11:21 -0600921 ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700922 mMaxTotalSize / mTotalCacheSizeDivisor, mMaxTotalEntries / mTotalCacheSizeDivisor);
923
924 if (!applyLRU(mMaxTotalSize / mTotalCacheSizeDivisor,
925 mMaxTotalEntries / mTotalCacheSizeDivisor)) {
Cody Northropf2588a82023-03-27 23:03:49 -0600926 ALOGE("Error when clearing multifile shader cache");
927 return;
Cody Northrop6cca6c22023-02-08 20:23:13 -0700928 }
929}
930
931// This function performs a task. It only knows how to write files to disk,
932// but it could be expanded if needed.
933void MultifileBlobCache::processTask(DeferredTask& task) {
934 switch (task.getTaskCommand()) {
935 case TaskCommand::Exit: {
936 ALOGV("DEFERRED: Shutting down");
937 return;
938 }
939 case TaskCommand::WriteToDisk: {
940 uint32_t entryHash = task.getEntryHash();
941 std::string& fullPath = task.getFullPath();
942 uint8_t* buffer = task.getBuffer();
943 size_t bufferSize = task.getBufferSize();
944
945 // Create the file or reset it if already present, read+write for user only
946 int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
947 if (fd == -1) {
948 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
949 fullPath.c_str(), std::strerror(errno));
950 return;
951 }
952
953 ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
954
Cody Northrop999db232023-02-27 17:02:50 -0700955 // Add CRC check to the header (always do this last!)
956 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
Jisun Lee88a1b762024-10-17 20:12:29 +0900957 header->crc = GenerateCRC32(buffer + sizeof(MultifileHeader),
958 bufferSize - sizeof(MultifileHeader));
Cody Northrop999db232023-02-27 17:02:50 -0700959
Cody Northrop6cca6c22023-02-08 20:23:13 -0700960 ssize_t result = write(fd, buffer, bufferSize);
961 if (result != bufferSize) {
962 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
963 std::strerror(errno));
964 return;
965 }
966
Cody Northrop99e8f2c2024-11-21 15:32:32 -0700967 if (flags::multifile_blobcache_advanced_usage()) {
968 // Update last access time and last modify time
969 struct timespec times[2];
970 times[0].tv_nsec = UTIME_NOW;
971 times[1].tv_nsec = UTIME_NOW;
972 futimens(fd, times);
973 }
974
Cody Northrop6cca6c22023-02-08 20:23:13 -0700975 ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
976 close(fd);
977
978 // Erase the entry from mDeferredWrites
979 // Since there could be multiple outstanding writes for an entry, find the matching one
Cody Northropbe163732023-03-22 10:14:26 -0600980 {
981 // Synchronize access to deferred write status
982 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
983 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
984 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
985 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
986 if (it->second == buffer) {
987 ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
988 it->second);
989 mDeferredWrites.erase(it);
990 break;
991 }
Cody Northrop6cca6c22023-02-08 20:23:13 -0700992 }
993 }
994
995 return;
996 }
997 default: {
998 ALOGE("DEFERRED: Unhandled task type");
999 return;
1000 }
1001 }
1002}
1003
1004// This function will wait until tasks arrive, then execute them
1005// If the exit command is submitted, the loop will terminate
1006void MultifileBlobCache::processTasksImpl(bool* exitThread) {
1007 while (true) {
1008 std::unique_lock<std::mutex> lock(mWorkerMutex);
1009 if (mTasks.empty()) {
1010 ALOGV("WORKER: No tasks available, waiting");
1011 mWorkerThreadIdle = true;
1012 mWorkerIdleCondition.notify_all();
1013 // Only wake if notified and command queue is not empty
1014 mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
1015 }
1016
1017 ALOGV("WORKER: Task available, waking up.");
1018 mWorkerThreadIdle = false;
1019 DeferredTask task = std::move(mTasks.front());
1020 mTasks.pop();
1021
1022 if (task.getTaskCommand() == TaskCommand::Exit) {
1023 ALOGV("WORKER: Exiting work loop.");
1024 *exitThread = true;
1025 mWorkerThreadIdle = true;
1026 mWorkerIdleCondition.notify_one();
1027 return;
1028 }
1029
1030 lock.unlock();
1031 processTask(task);
1032 }
1033}
1034
1035// Process tasks until the exit task is submitted
1036void MultifileBlobCache::processTasks() {
1037 while (true) {
1038 bool exitThread = false;
1039 processTasksImpl(&exitThread);
1040 if (exitThread) {
1041 break;
1042 }
1043 }
1044}
1045
1046// Add a task to the queue to be processed by the worker thread
1047void MultifileBlobCache::queueTask(DeferredTask&& task) {
1048 std::lock_guard<std::mutex> queueLock(mWorkerMutex);
1049 mTasks.emplace(std::move(task));
1050 mWorkAvailableCondition.notify_one();
1051}
1052
1053// Wait until all tasks have been completed
1054void MultifileBlobCache::waitForWorkComplete() {
1055 std::unique_lock<std::mutex> lock(mWorkerMutex);
1056 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
1057}
1058
Cody Northrop999db232023-02-27 17:02:50 -07001059}; // namespace android