1 /*
2 ** Copyright 2022, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17 // #define LOG_NDEBUG 0
18
19 #include "MultifileBlobCache.h"
20
21 #include <android-base/properties.h>
22 #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
41 using namespace std::literals;
42
43 constexpr uint32_t kMultifileMagic = 'MFB$';
44 constexpr uint32_t kCrcPlaceholder = 0;
45
46 namespace {
47
48 // Helper function to close entries or free them
freeHotCacheEntry(android::MultifileHotCache & entry)49 void 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
52 // We need to unmap the entry
53 munmap(entry.entryBuffer, entry.entrySize);
54 } 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
63 namespace android {
64
MultifileBlobCache(size_t maxKeySize,size_t maxValueSize,size_t maxTotalSize,size_t maxTotalEntries,const std::string & baseDir)65 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
66 size_t maxTotalEntries, const std::string& baseDir)
67 : mInitialized(false),
68 mCacheVersion(0),
69 mMaxKeySize(maxKeySize),
70 mMaxValueSize(maxValueSize),
71 mMaxTotalSize(maxTotalSize),
72 mMaxTotalEntries(maxTotalEntries),
73 mTotalCacheSize(0),
74 mTotalCacheEntries(0),
75 mHotCacheLimit(0),
76 mHotCacheSize(0),
77 mWorkerThreadIdle(true) {
78 if (baseDir.empty()) {
79 ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
80 return;
81 }
82
83 // 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
103 // Establish the name of our multifile directory
104 mMultifileDirName = baseDir + ".multifile";
105
106 // 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);
109
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
120 bool statusGood = false;
121
122 // Check that our cacheVersion and buildId match
123 struct stat st;
124 if (stat(mMultifileDirName.c_str(), &st) == 0) {
125 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) {
137 // 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) {
142 if (entry->d_name == "."s || entry->d_name == ".."s ||
143 strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
144 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
162 // 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) {
166 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
167 std::strerror(errno));
168 }
169 continue;
170 }
171
172 // 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
180 // 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)) {
184 ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
185 fullPath.c_str(), std::strerror(errno));
186 close(fd);
187 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) {
194 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
195 std::strerror(errno));
196 }
197 close(fd);
198 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));
207
208 // We can close the file now and the mmap will remain
209 close(fd);
210
211 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
217 if (header.crc !=
218 crc32c(mappedEntry + sizeof(MultifileHeader),
219 fileSize - sizeof(MultifileHeader))) {
220 ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
221 if (remove(fullPath.c_str()) != 0) {
222 ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
223 }
224 continue;
225 }
226
227 // If the cache entry is damaged or no good, remove it
228 if (header.keySize <= 0 || header.valueSize <= 0) {
229 ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
230 "removing.",
231 entryHash, header.keySize, header.valueSize);
232 if (remove(fullPath.c_str()) != 0) {
233 ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
234 std::strerror(errno));
235 }
236 continue;
237 }
238
239 ALOGV("INIT: Entry %u is good, tracking it now.", entryHash);
240
241 // Track details for rapid lookup later
242 trackEntry(entryHash, header.valueSize, fileSize, st.st_atime);
243
244 // Track the total size
245 increaseTotalCacheSize(fileSize);
246
247 // Preload the entry for fast retrieval
248 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
249 ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
250 "entryHash %u",
251 fd, mappedEntry, entryHash);
252
253 // Track the details of the preload so they can be retrieved later
254 if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
255 ALOGE("INIT Failed to add %u to hot cache", entryHash);
256 munmap(mappedEntry, fileSize);
257 return;
258 }
259 } else {
260 // If we're not keeping it in hot cache, unmap it now
261 munmap(mappedEntry, fileSize);
262 }
263 }
264 closedir(dir);
265 } else {
266 ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
267 }
268 } else {
269 // If the multifile directory does not exist, create it and start from scratch
270 if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
271 ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
272 return;
273 }
274
275 // Create new status file
276 if (!createStatus(mMultifileDirName.c_str())) {
277 ALOGE("INIT: Failed to create status file!");
278 return;
279 }
280 }
281
282 ALOGV("INIT: Multifile BlobCache initialization succeeded");
283 mInitialized = true;
284 }
285
~MultifileBlobCache()286 MultifileBlobCache::~MultifileBlobCache() {
287 if (!mInitialized) {
288 return;
289 }
290
291 // Inform the worker thread we're done
292 ALOGV("DESCTRUCTOR: Shutting down worker thread");
293 DeferredTask task(TaskCommand::Exit);
294 queueTask(std::move(task));
295
296 // Wait for it to complete
297 ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
298 waitForWorkComplete();
299 if (mTaskThread.joinable()) {
300 mTaskThread.join();
301 }
302 }
303
304 // Set will add the entry to hot cache and start a deferred process to write it to disk
set(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)305 void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
306 EGLsizeiANDROID valueSize) {
307 if (!mInitialized) {
308 return;
309 }
310
311 // Ensure key and value are under their limits
312 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
313 ALOGW("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
314 valueSize, mMaxValueSize);
315 return;
316 }
317
318 // Generate a hash of the key and use it to track this entry
319 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
320
321 size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
322
323 // If we're going to be over the cache limit, kick off a trim to clear space
324 if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
325 ALOGV("SET: Cache is full, calling trimCache to clear space");
326 trimCache();
327 }
328
329 ALOGV("SET: Add %u to cache", entryHash);
330
331 uint8_t* buffer = new uint8_t[fileSize];
332
333 // Write placeholders for magic and CRC until deferred thread completes the write
334 android::MultifileHeader header = {kMultifileMagic, kCrcPlaceholder, keySize, valueSize};
335 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
336 sizeof(android::MultifileHeader));
337 // Write the key and value after the header
338 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
339 keySize);
340 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
341 static_cast<const void*>(value), valueSize);
342
343 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
344
345 // Track the size and access time for quick recall
346 trackEntry(entryHash, valueSize, fileSize, time(0));
347
348 // Update the overall cache size
349 increaseTotalCacheSize(fileSize);
350
351 // Keep the entry in hot cache for quick retrieval
352 ALOGV("SET: Adding %u to hot cache.", entryHash);
353
354 // Sending -1 as the fd indicates we don't have an fd for this
355 if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
356 ALOGE("SET: Failed to add %u to hot cache", entryHash);
357 delete[] buffer;
358 return;
359 }
360
361 // Track that we're creating a pending write for this entry
362 // Include the buffer to handle the case when multiple writes are pending for an entry
363 {
364 // Synchronize access to deferred write status
365 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
366 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
367 }
368
369 // Create deferred task to write to storage
370 ALOGV("SET: Adding task to queue.");
371 DeferredTask task(TaskCommand::WriteToDisk);
372 task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
373 queueTask(std::move(task));
374 }
375
376 // Get will check the hot cache, then load it from disk if needed
get(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)377 EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
378 EGLsizeiANDROID valueSize) {
379 if (!mInitialized) {
380 return 0;
381 }
382
383 // Ensure key and value are under their limits
384 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
385 ALOGW("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
386 valueSize, mMaxValueSize);
387 return 0;
388 }
389
390 // Generate a hash of the key and use it to track this entry
391 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
392
393 // See if we have this file
394 if (!contains(entryHash)) {
395 ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
396 return 0;
397 }
398
399 // Look up the data for this entry
400 MultifileEntryStats entryStats = getEntryStats(entryHash);
401
402 size_t cachedValueSize = entryStats.valueSize;
403 if (cachedValueSize > valueSize) {
404 ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
405 "size (%zu)",
406 valueSize, entryHash, cachedValueSize);
407 return cachedValueSize;
408 }
409
410 // We have the file and have enough room to write it out, return the entry
411 ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
412
413 // Look up the size of the file
414 size_t fileSize = entryStats.fileSize;
415 if (keySize > fileSize) {
416 ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
417 "file",
418 keySize, fileSize);
419 return 0;
420 }
421
422 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
423
424 // Open the hashed filename path
425 uint8_t* cacheEntry = 0;
426
427 // Check hot cache
428 if (mHotCache.find(entryHash) != mHotCache.end()) {
429 ALOGV("GET: HotCache HIT for entry %u", entryHash);
430 cacheEntry = mHotCache[entryHash].entryBuffer;
431 } else {
432 ALOGV("GET: HotCache MISS for entry: %u", entryHash);
433
434 // Wait for writes to complete if there is an outstanding write for this entry
435 bool wait = false;
436 {
437 // Synchronize access to deferred write status
438 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
439 wait = mDeferredWrites.find(entryHash) != mDeferredWrites.end();
440 }
441
442 if (wait) {
443 ALOGV("GET: Waiting for write to complete for %u", entryHash);
444 waitForWorkComplete();
445 }
446
447 // Open the entry file
448 int fd = open(fullPath.c_str(), O_RDONLY);
449 if (fd == -1) {
450 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
451 std::strerror(errno));
452 return 0;
453 }
454
455 // Memory map the file
456 cacheEntry =
457 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
458
459 // We can close the file now and the mmap will remain
460 close(fd);
461
462 if (cacheEntry == MAP_FAILED) {
463 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
464 return 0;
465 }
466
467 ALOGV("GET: Adding %u to hot cache", entryHash);
468 if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
469 ALOGE("GET: Failed to add %u to hot cache", entryHash);
470 return 0;
471 }
472
473 cacheEntry = mHotCache[entryHash].entryBuffer;
474 }
475
476 // Ensure the header matches
477 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
478 if (header->keySize != keySize || header->valueSize != valueSize) {
479 ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
480 "to cache header values for fullPath: %s",
481 keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
482 removeFromHotCache(entryHash);
483 return 0;
484 }
485
486 // Compare the incoming key with our stored version (the beginning of the entry)
487 uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
488 int compare = memcmp(cachedKey, key, keySize);
489 if (compare != 0) {
490 ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
491 removeFromHotCache(entryHash);
492 return 0;
493 }
494
495 // Remaining entry following the key is the value
496 uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
497 memcpy(value, cachedValue, cachedValueSize);
498
499 return cachedValueSize;
500 }
501
finish()502 void MultifileBlobCache::finish() {
503 if (!mInitialized) {
504 return;
505 }
506
507 // Wait for all deferred writes to complete
508 ALOGV("FINISH: Waiting for work to complete.");
509 waitForWorkComplete();
510
511 // Close all entries in the hot cache
512 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
513 uint32_t entryHash = hotCacheIter->first;
514 MultifileHotCache entry = hotCacheIter->second;
515
516 ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
517 freeHotCacheEntry(entry);
518
519 mHotCache.erase(hotCacheIter++);
520 }
521 }
522
createStatus(const std::string & baseDir)523 bool MultifileBlobCache::createStatus(const std::string& baseDir) {
524 // Populate the status struct
525 struct MultifileStatus status;
526 memset(&status, 0, sizeof(status));
527 status.magic = kMultifileMagic;
528 status.cacheVersion = mCacheVersion;
529
530 // Copy the buildId string in, up to our allocated space
531 strncpy(status.buildId, mBuildId.c_str(),
532 mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
533
534 // Finally update the crc, using cacheVersion and everything the follows
535 status.crc =
536 crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
537 sizeof(status) - offsetof(MultifileStatus, cacheVersion));
538
539 // Create the status file
540 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
541 int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
542 if (fd == -1) {
543 ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
544 std::strerror(errno));
545 return false;
546 }
547
548 // Write the buffer contents to disk
549 ssize_t result = write(fd, &status, sizeof(status));
550 close(fd);
551 if (result != sizeof(status)) {
552 ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
553 std::strerror(errno));
554 return false;
555 }
556
557 ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
558 return true;
559 }
560
checkStatus(const std::string & baseDir)561 bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
562 std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
563
564 // Does status exist
565 struct stat st;
566 if (stat(cacheStatus.c_str(), &st) != 0) {
567 ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
568 return false;
569 }
570
571 // If the status entry is damaged or no good, remove it
572 if (st.st_size <= 0 || st.st_atime <= 0) {
573 ALOGE("STATUS(CHECK): Cache status has invalid stats!");
574 return false;
575 }
576
577 // Open the file so we can read its header
578 int fd = open(cacheStatus.c_str(), O_RDONLY);
579 if (fd == -1) {
580 ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
581 cacheStatus.c_str(), std::strerror(errno));
582 return false;
583 }
584
585 // Read in the status header
586 MultifileStatus status;
587 size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
588 close(fd);
589 if (result != sizeof(MultifileStatus)) {
590 ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
591 std::strerror(errno));
592 return false;
593 }
594
595 // Verify header magic
596 if (status.magic != kMultifileMagic) {
597 ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
598 return false;
599 }
600
601 // Ensure we have a good CRC
602 if (status.crc !=
603 crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
604 sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
605 ALOGE("STATUS(CHECK): Cache status failed CRC check!");
606 return false;
607 }
608
609 // Check cacheVersion
610 if (status.cacheVersion != mCacheVersion) {
611 ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
612 mCacheVersion);
613 return false;
614 }
615
616 // Check buildId
617 if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
618 ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
619 mBuildId.c_str());
620 return false;
621 }
622
623 // All checks passed!
624 ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
625 status.cacheVersion, status.buildId, cacheStatus.c_str());
626 return true;
627 }
628
trackEntry(uint32_t entryHash,EGLsizeiANDROID valueSize,size_t fileSize,time_t accessTime)629 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
630 time_t accessTime) {
631 mEntries.insert(entryHash);
632 mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
633 }
634
contains(uint32_t hashEntry) const635 bool MultifileBlobCache::contains(uint32_t hashEntry) const {
636 return mEntries.find(hashEntry) != mEntries.end();
637 }
638
getEntryStats(uint32_t entryHash)639 MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
640 return mEntryStats[entryHash];
641 }
642
increaseTotalCacheSize(size_t fileSize)643 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
644 mTotalCacheSize += fileSize;
645 mTotalCacheEntries++;
646 }
647
decreaseTotalCacheSize(size_t fileSize)648 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
649 mTotalCacheSize -= fileSize;
650 mTotalCacheEntries--;
651 }
652
addToHotCache(uint32_t newEntryHash,int newFd,uint8_t * newEntryBuffer,size_t newEntrySize)653 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
654 size_t newEntrySize) {
655 ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
656
657 // Clear space if we need to
658 if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
659 ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
660 "mHotCacheLimit "
661 "(%zu), freeing up space for %u",
662 mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
663
664 // Wait for all the files to complete writing so our hot cache is accurate
665 ALOGV("HOTCACHE(ADD): Waiting for work to complete for %u", newEntryHash);
666 waitForWorkComplete();
667
668 // Free up old entries until under the limit
669 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
670 uint32_t oldEntryHash = hotCacheIter->first;
671 MultifileHotCache oldEntry = hotCacheIter->second;
672
673 // Move our iterator before deleting the entry
674 hotCacheIter++;
675 if (!removeFromHotCache(oldEntryHash)) {
676 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
677 return false;
678 }
679
680 // Clear at least half the hot cache
681 if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
682 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
683 break;
684 }
685 }
686 }
687
688 // Track it
689 mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
690 mHotCacheSize += newEntrySize;
691
692 ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
693
694 return true;
695 }
696
removeFromHotCache(uint32_t entryHash)697 bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
698 if (mHotCache.find(entryHash) != mHotCache.end()) {
699 ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
700
701 // Wait for all the files to complete writing so our hot cache is accurate
702 ALOGV("HOTCACHE(REMOVE): Waiting for work to complete for %u", entryHash);
703 waitForWorkComplete();
704
705 ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
706 MultifileHotCache entry = mHotCache[entryHash];
707 freeHotCacheEntry(entry);
708
709 // Delete the entry from our tracking
710 mHotCacheSize -= entry.entrySize;
711 mHotCache.erase(entryHash);
712
713 return true;
714 }
715
716 return false;
717 }
718
applyLRU(size_t cacheSizeLimit,size_t cacheEntryLimit)719 bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
720 // Walk through our map of sorted last access times and remove files until under the limit
721 for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
722 uint32_t entryHash = cacheEntryIter->first;
723
724 ALOGV("LRU: Removing entryHash %u", entryHash);
725
726 // Track the overall size
727 MultifileEntryStats entryStats = getEntryStats(entryHash);
728 decreaseTotalCacheSize(entryStats.fileSize);
729
730 // Remove it from hot cache if present
731 removeFromHotCache(entryHash);
732
733 // Remove it from the system
734 std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
735 if (remove(entryPath.c_str()) != 0) {
736 ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
737 return false;
738 }
739
740 // Increment the iterator before clearing the entry
741 cacheEntryIter++;
742
743 // Delete the entry from our tracking
744 size_t count = mEntryStats.erase(entryHash);
745 if (count != 1) {
746 ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
747 return false;
748 }
749
750 // See if it has been reduced enough
751 size_t totalCacheSize = getTotalSize();
752 size_t totalCacheEntries = getTotalEntries();
753 if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
754 // Success
755 ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
756 return true;
757 }
758 }
759
760 ALOGV("LRU: Cache is empty");
761 return false;
762 }
763
764 // Clear the cache by removing all entries and deleting the directory
clearCache()765 bool MultifileBlobCache::clearCache() {
766 DIR* dir;
767 struct dirent* entry;
768 dir = opendir(mMultifileDirName.c_str());
769 if (dir == nullptr) {
770 ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
771 return false;
772 }
773
774 // Delete all entries and the status file
775 while ((entry = readdir(dir)) != nullptr) {
776 if (entry->d_name == "."s || entry->d_name == ".."s) {
777 continue;
778 }
779
780 std::string entryName = entry->d_name;
781 std::string fullPath = mMultifileDirName + "/" + entryName;
782 if (remove(fullPath.c_str()) != 0) {
783 ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
784 return false;
785 }
786 }
787
788 // Delete the directory
789 if (remove(mMultifileDirName.c_str()) != 0) {
790 ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
791 return false;
792 }
793
794 ALOGV("CLEAR: Cleared the multifile blobcache");
795 return true;
796 }
797
798 // When removing files, what fraction of the overall limit should be reached when removing files
799 // A divisor of two will decrease the cache to 50%, four to 25% and so on
800 // We use the same limit to manage size and entry count
801 constexpr uint32_t kCacheLimitDivisor = 2;
802
803 // Calculate the cache size and remove old entries until under the limit
trimCache()804 void MultifileBlobCache::trimCache() {
805 // Wait for all deferred writes to complete
806 ALOGV("TRIM: Waiting for work to complete.");
807 waitForWorkComplete();
808
809 ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
810 mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
811 if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
812 ALOGE("Error when clearing multifile shader cache");
813 return;
814 }
815 }
816
817 // This function performs a task. It only knows how to write files to disk,
818 // but it could be expanded if needed.
processTask(DeferredTask & task)819 void MultifileBlobCache::processTask(DeferredTask& task) {
820 switch (task.getTaskCommand()) {
821 case TaskCommand::Exit: {
822 ALOGV("DEFERRED: Shutting down");
823 return;
824 }
825 case TaskCommand::WriteToDisk: {
826 uint32_t entryHash = task.getEntryHash();
827 std::string& fullPath = task.getFullPath();
828 uint8_t* buffer = task.getBuffer();
829 size_t bufferSize = task.getBufferSize();
830
831 // Create the file or reset it if already present, read+write for user only
832 int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
833 if (fd == -1) {
834 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
835 fullPath.c_str(), std::strerror(errno));
836 return;
837 }
838
839 ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
840
841 // Add CRC check to the header (always do this last!)
842 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(buffer);
843 header->crc =
844 crc32c(buffer + sizeof(MultifileHeader), bufferSize - sizeof(MultifileHeader));
845
846 ssize_t result = write(fd, buffer, bufferSize);
847 if (result != bufferSize) {
848 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
849 std::strerror(errno));
850 return;
851 }
852
853 ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
854 close(fd);
855
856 // Erase the entry from mDeferredWrites
857 // Since there could be multiple outstanding writes for an entry, find the matching one
858 {
859 // Synchronize access to deferred write status
860 std::lock_guard<std::mutex> lock(mDeferredWriteStatusMutex);
861 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
862 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
863 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
864 if (it->second == buffer) {
865 ALOGV("DEFERRED: Marking write complete for %u at %p", it->first,
866 it->second);
867 mDeferredWrites.erase(it);
868 break;
869 }
870 }
871 }
872
873 return;
874 }
875 default: {
876 ALOGE("DEFERRED: Unhandled task type");
877 return;
878 }
879 }
880 }
881
882 // This function will wait until tasks arrive, then execute them
883 // If the exit command is submitted, the loop will terminate
processTasksImpl(bool * exitThread)884 void MultifileBlobCache::processTasksImpl(bool* exitThread) {
885 while (true) {
886 std::unique_lock<std::mutex> lock(mWorkerMutex);
887 if (mTasks.empty()) {
888 ALOGV("WORKER: No tasks available, waiting");
889 mWorkerThreadIdle = true;
890 mWorkerIdleCondition.notify_all();
891 // Only wake if notified and command queue is not empty
892 mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
893 }
894
895 ALOGV("WORKER: Task available, waking up.");
896 mWorkerThreadIdle = false;
897 DeferredTask task = std::move(mTasks.front());
898 mTasks.pop();
899
900 if (task.getTaskCommand() == TaskCommand::Exit) {
901 ALOGV("WORKER: Exiting work loop.");
902 *exitThread = true;
903 mWorkerThreadIdle = true;
904 mWorkerIdleCondition.notify_one();
905 return;
906 }
907
908 lock.unlock();
909 processTask(task);
910 }
911 }
912
913 // Process tasks until the exit task is submitted
processTasks()914 void MultifileBlobCache::processTasks() {
915 while (true) {
916 bool exitThread = false;
917 processTasksImpl(&exitThread);
918 if (exitThread) {
919 break;
920 }
921 }
922 }
923
924 // Add a task to the queue to be processed by the worker thread
queueTask(DeferredTask && task)925 void MultifileBlobCache::queueTask(DeferredTask&& task) {
926 std::lock_guard<std::mutex> queueLock(mWorkerMutex);
927 mTasks.emplace(std::move(task));
928 mWorkAvailableCondition.notify_one();
929 }
930
931 // Wait until all tasks have been completed
waitForWorkComplete()932 void MultifileBlobCache::waitForWorkComplete() {
933 std::unique_lock<std::mutex> lock(mWorkerMutex);
934 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
935 }
936
937 }; // namespace android
938