blob: 304f9072e438c896dbd54db07303a8c1ac9a0f59 [file] [log] [blame]
Cody Northropc9576892023-02-03 14:00:16 -07001/*
2 ** Copyright 2022, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17// #define LOG_NDEBUG 0
18
19#include "MultifileBlobCache.h"
20
21#include <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
41using namespace std::literals;
42
43namespace {
44
45// Open the file and determine the size of the value it contains
46size_t getValueSizeFromFile(int fd, const std::string& entryPath) {
47 // Read the beginning of the file to get header
48 android::MultifileHeader header;
49 size_t result = read(fd, static_cast<void*>(&header), sizeof(android::MultifileHeader));
50 if (result != sizeof(android::MultifileHeader)) {
51 ALOGE("Error reading MultifileHeader from cache entry (%s): %s", entryPath.c_str(),
52 std::strerror(errno));
53 return 0;
54 }
55
56 return header.valueSize;
57}
58
59// Helper function to close entries or free them
60void freeHotCacheEntry(android::MultifileHotCache& entry) {
61 if (entry.entryFd != -1) {
62 // If we have an fd, then this entry was added to hot cache via INIT or GET
63 // We need to unmap and close the entry
64 munmap(entry.entryBuffer, entry.entrySize);
65 close(entry.entryFd);
66 } else {
67 // Otherwise, this was added to hot cache during SET, so it was never mapped
68 // and fd was only on the deferred thread.
69 delete[] entry.entryBuffer;
70 }
71}
72
73} // namespace
74
75namespace android {
76
77MultifileBlobCache::MultifileBlobCache(size_t maxTotalSize, size_t maxHotCacheSize,
78 const std::string& baseDir)
79 : mInitialized(false),
80 mMaxTotalSize(maxTotalSize),
81 mTotalCacheSize(0),
82 mHotCacheLimit(maxHotCacheSize),
83 mHotCacheSize(0),
84 mWorkerThreadIdle(true) {
85 if (baseDir.empty()) {
86 return;
87 }
88
89 // Establish the name of our multifile directory
90 mMultifileDirName = baseDir + ".multifile";
91
92 // Set a limit for max key and value, ensuring at least one entry can always fit in hot cache
93 mMaxKeySize = mHotCacheLimit / 4;
94 mMaxValueSize = mHotCacheLimit / 2;
95
96 // Initialize our cache with the contents of the directory
97 mTotalCacheSize = 0;
98
99 // See if the dir exists, and initialize using its contents
100 struct stat st;
101 if (stat(mMultifileDirName.c_str(), &st) == 0) {
102 // Read all the files and gather details, then preload their contents
103 DIR* dir;
104 struct dirent* entry;
105 if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
106 while ((entry = readdir(dir)) != nullptr) {
107 if (entry->d_name == "."s || entry->d_name == ".."s) {
108 continue;
109 }
110
111 std::string entryName = entry->d_name;
112 std::string fullPath = mMultifileDirName + "/" + entryName;
113
114 // The filename is the same as the entryHash
115 uint32_t entryHash = static_cast<uint32_t>(strtoul(entry->d_name, nullptr, 10));
116
117 // Look up the details of the file
118 struct stat st;
119 if (stat(fullPath.c_str(), &st) != 0) {
120 ALOGE("Failed to stat %s", fullPath.c_str());
121 return;
122 }
123
124 // Open the file so we can read its header
125 int fd = open(fullPath.c_str(), O_RDONLY);
126 if (fd == -1) {
127 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
128 std::strerror(errno));
129 return;
130 }
131
132 // Look up the details we track about each file
133 size_t valueSize = getValueSizeFromFile(fd, fullPath);
134
135 // If the cache entry is damaged or no good, remove it
136 // TODO: Perform any other checks
137 if (valueSize <= 0 || st.st_size <= 0 || st.st_atime <= 0) {
138 if (remove(fullPath.c_str()) != 0) {
139 ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
140 }
141 continue;
142 }
143
144 // Note: Converting from off_t (signed) to size_t (unsigned)
145 size_t fileSize = static_cast<size_t>(st.st_size);
146 time_t accessTime = st.st_atime;
147
148 // Track details for rapid lookup later
149 trackEntry(entryHash, valueSize, fileSize, accessTime);
150
151 // Track the total size
152 increaseTotalCacheSize(fileSize);
153
154 // Preload the entry for fast retrieval
155 if ((mHotCacheSize + fileSize) < mHotCacheLimit) {
156 // Memory map the file
157 uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
158 mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
159 if (mappedEntry == MAP_FAILED) {
160 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
161 }
162
163 ALOGV("INIT: Populating hot cache with fd = %i, cacheEntry = %p for "
164 "entryHash %u",
165 fd, mappedEntry, entryHash);
166
167 // Track the details of the preload so they can be retrieved later
168 if (!addToHotCache(entryHash, fd, mappedEntry, fileSize)) {
169 ALOGE("INIT Failed to add %u to hot cache", entryHash);
170 munmap(mappedEntry, fileSize);
171 close(fd);
172 return;
173 }
174 } else {
175 close(fd);
176 }
177 }
178 closedir(dir);
179 } else {
180 ALOGE("Unable to open filename: %s", mMultifileDirName.c_str());
181 }
182 } else {
183 // If the multifile directory does not exist, create it and start from scratch
184 if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
185 ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
186 }
187 }
188
189 mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
190
191 mInitialized = true;
192}
193
194MultifileBlobCache::~MultifileBlobCache() {
195 // Inform the worker thread we're done
196 ALOGV("DESCTRUCTOR: Shutting down worker thread");
197 DeferredTask task(TaskCommand::Exit);
198 queueTask(std::move(task));
199
200 // Wait for it to complete
201 ALOGV("DESCTRUCTOR: Waiting for worker thread to complete");
202 waitForWorkComplete();
203 mTaskThread.join();
204}
205
206// Set will add the entry to hot cache and start a deferred process to write it to disk
207void MultifileBlobCache::set(const void* key, EGLsizeiANDROID keySize, const void* value,
208 EGLsizeiANDROID valueSize) {
209 if (!mInitialized) {
210 return;
211 }
212
213 // Ensure key and value are under their limits
214 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
215 ALOGV("SET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
216 valueSize, mMaxValueSize);
217 return;
218 }
219
220 // Generate a hash of the key and use it to track this entry
221 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
222
223 size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
224
225 // If we're going to be over the cache limit, kick off a trim to clear space
226 if (getTotalSize() + fileSize > mMaxTotalSize) {
227 ALOGV("SET: Cache is full, calling trimCache to clear space");
228 trimCache(mMaxTotalSize);
229 }
230
231 ALOGV("SET: Add %u to cache", entryHash);
232
233 uint8_t* buffer = new uint8_t[fileSize];
234
235 // Write the key and value after the header
236 android::MultifileHeader header = {keySize, valueSize};
237 memcpy(static_cast<void*>(buffer), static_cast<const void*>(&header),
238 sizeof(android::MultifileHeader));
239 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
240 keySize);
241 memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
242 static_cast<const void*>(value), valueSize);
243
244 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
245
246 // Track the size and access time for quick recall
247 trackEntry(entryHash, valueSize, fileSize, time(0));
248
249 // Update the overall cache size
250 increaseTotalCacheSize(fileSize);
251
252 // Keep the entry in hot cache for quick retrieval
253 ALOGV("SET: Adding %u to hot cache.", entryHash);
254
255 // Sending -1 as the fd indicates we don't have an fd for this
256 if (!addToHotCache(entryHash, -1, buffer, fileSize)) {
257 ALOGE("GET: Failed to add %u to hot cache", entryHash);
258 return;
259 }
260
261 // Track that we're creating a pending write for this entry
262 // Include the buffer to handle the case when multiple writes are pending for an entry
263 mDeferredWrites.insert(std::make_pair(entryHash, buffer));
264
265 // Create deferred task to write to storage
266 ALOGV("SET: Adding task to queue.");
267 DeferredTask task(TaskCommand::WriteToDisk);
268 task.initWriteToDisk(entryHash, fullPath, buffer, fileSize);
269 queueTask(std::move(task));
270}
271
272// Get will check the hot cache, then load it from disk if needed
273EGLsizeiANDROID MultifileBlobCache::get(const void* key, EGLsizeiANDROID keySize, void* value,
274 EGLsizeiANDROID valueSize) {
275 if (!mInitialized) {
276 return 0;
277 }
278
279 // Ensure key and value are under their limits
280 if (keySize > mMaxKeySize || valueSize > mMaxValueSize) {
281 ALOGV("GET: keySize (%lu vs %zu) or valueSize (%lu vs %zu) too large", keySize, mMaxKeySize,
282 valueSize, mMaxValueSize);
283 return 0;
284 }
285
286 // Generate a hash of the key and use it to track this entry
287 uint32_t entryHash = android::JenkinsHashMixBytes(0, static_cast<const uint8_t*>(key), keySize);
288
289 // See if we have this file
290 if (!contains(entryHash)) {
291 ALOGV("GET: Cache MISS - cache does not contain entry: %u", entryHash);
292 return 0;
293 }
294
295 // Look up the data for this entry
296 MultifileEntryStats entryStats = getEntryStats(entryHash);
297
298 size_t cachedValueSize = entryStats.valueSize;
299 if (cachedValueSize > valueSize) {
300 ALOGV("GET: Cache MISS - valueSize not large enough (%lu) for entry %u, returning required"
301 "size (%zu)",
302 valueSize, entryHash, cachedValueSize);
303 return cachedValueSize;
304 }
305
306 // We have the file and have enough room to write it out, return the entry
307 ALOGV("GET: Cache HIT - cache contains entry: %u", entryHash);
308
309 // Look up the size of the file
310 size_t fileSize = entryStats.fileSize;
311 if (keySize > fileSize) {
312 ALOGW("keySize (%lu) is larger than entrySize (%zu). This is a hash collision or modified "
313 "file",
314 keySize, fileSize);
315 return 0;
316 }
317
318 std::string fullPath = mMultifileDirName + "/" + std::to_string(entryHash);
319
320 // Open the hashed filename path
321 uint8_t* cacheEntry = 0;
322
323 // Check hot cache
324 if (mHotCache.find(entryHash) != mHotCache.end()) {
325 ALOGV("GET: HotCache HIT for entry %u", entryHash);
326 cacheEntry = mHotCache[entryHash].entryBuffer;
327 } else {
328 ALOGV("GET: HotCache MISS for entry: %u", entryHash);
329
330 if (mDeferredWrites.find(entryHash) != mDeferredWrites.end()) {
331 // Wait for writes to complete if there is an outstanding write for this entry
332 ALOGV("GET: Waiting for write to complete for %u", entryHash);
333 waitForWorkComplete();
334 }
335
336 // Open the entry file
337 int fd = open(fullPath.c_str(), O_RDONLY);
338 if (fd == -1) {
339 ALOGE("Cache error - failed to open fullPath: %s, error: %s", fullPath.c_str(),
340 std::strerror(errno));
341 return 0;
342 }
343
344 // Memory map the file
345 cacheEntry =
346 reinterpret_cast<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
347 if (cacheEntry == MAP_FAILED) {
348 ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
349 close(fd);
350 return 0;
351 }
352
353 ALOGV("GET: Adding %u to hot cache", entryHash);
354 if (!addToHotCache(entryHash, fd, cacheEntry, fileSize)) {
355 ALOGE("GET: Failed to add %u to hot cache", entryHash);
356 return 0;
357 }
358
359 cacheEntry = mHotCache[entryHash].entryBuffer;
360 }
361
362 // Ensure the header matches
363 MultifileHeader* header = reinterpret_cast<MultifileHeader*>(cacheEntry);
364 if (header->keySize != keySize || header->valueSize != valueSize) {
365 ALOGW("Mismatch on keySize(%ld vs. cached %ld) or valueSize(%ld vs. cached %ld) compared "
366 "to cache header values for fullPath: %s",
367 keySize, header->keySize, valueSize, header->valueSize, fullPath.c_str());
368 removeFromHotCache(entryHash);
369 return 0;
370 }
371
372 // Compare the incoming key with our stored version (the beginning of the entry)
373 uint8_t* cachedKey = cacheEntry + sizeof(MultifileHeader);
374 int compare = memcmp(cachedKey, key, keySize);
375 if (compare != 0) {
376 ALOGW("Cached key and new key do not match! This is a hash collision or modified file");
377 removeFromHotCache(entryHash);
378 return 0;
379 }
380
381 // Remaining entry following the key is the value
382 uint8_t* cachedValue = cacheEntry + (keySize + sizeof(MultifileHeader));
383 memcpy(value, cachedValue, cachedValueSize);
384
385 return cachedValueSize;
386}
387
388void MultifileBlobCache::finish() {
389 // Wait for all deferred writes to complete
390 ALOGV("FINISH: Waiting for work to complete.");
391 waitForWorkComplete();
392
393 // Close all entries in the hot cache
394 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
395 uint32_t entryHash = hotCacheIter->first;
396 MultifileHotCache entry = hotCacheIter->second;
397
398 ALOGV("FINISH: Closing hot cache entry for %u", entryHash);
399 freeHotCacheEntry(entry);
400
401 mHotCache.erase(hotCacheIter++);
402 }
403}
404
405void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
406 time_t accessTime) {
407 mEntries.insert(entryHash);
408 mEntryStats[entryHash] = {valueSize, fileSize, accessTime};
409}
410
411bool MultifileBlobCache::contains(uint32_t hashEntry) const {
412 return mEntries.find(hashEntry) != mEntries.end();
413}
414
415MultifileEntryStats MultifileBlobCache::getEntryStats(uint32_t entryHash) {
416 return mEntryStats[entryHash];
417}
418
419void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
420 mTotalCacheSize += fileSize;
421}
422
423void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
424 mTotalCacheSize -= fileSize;
425}
426
427bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
428 size_t newEntrySize) {
429 ALOGV("HOTCACHE(ADD): Adding %u to hot cache", newEntryHash);
430
431 // Clear space if we need to
432 if ((mHotCacheSize + newEntrySize) > mHotCacheLimit) {
433 ALOGV("HOTCACHE(ADD): mHotCacheSize (%zu) + newEntrySize (%zu) is to big for "
434 "mHotCacheLimit "
435 "(%zu), freeing up space for %u",
436 mHotCacheSize, newEntrySize, mHotCacheLimit, newEntryHash);
437
438 // Wait for all the files to complete writing so our hot cache is accurate
439 waitForWorkComplete();
440
441 // Free up old entries until under the limit
442 for (auto hotCacheIter = mHotCache.begin(); hotCacheIter != mHotCache.end();) {
443 uint32_t oldEntryHash = hotCacheIter->first;
444 MultifileHotCache oldEntry = hotCacheIter->second;
445
446 // Move our iterator before deleting the entry
447 hotCacheIter++;
448 if (!removeFromHotCache(oldEntryHash)) {
449 ALOGE("HOTCACHE(ADD): Unable to remove entry %u", oldEntryHash);
450 return false;
451 }
452
453 // Clear at least half the hot cache
454 if ((mHotCacheSize + newEntrySize) <= mHotCacheLimit / 2) {
455 ALOGV("HOTCACHE(ADD): Freed enough space for %zu", mHotCacheSize);
456 break;
457 }
458 }
459 }
460
461 // Track it
462 mHotCache[newEntryHash] = {newFd, newEntryBuffer, newEntrySize};
463 mHotCacheSize += newEntrySize;
464
465 ALOGV("HOTCACHE(ADD): New hot cache size: %zu", mHotCacheSize);
466
467 return true;
468}
469
470bool MultifileBlobCache::removeFromHotCache(uint32_t entryHash) {
471 if (mHotCache.find(entryHash) != mHotCache.end()) {
472 ALOGV("HOTCACHE(REMOVE): Removing %u from hot cache", entryHash);
473
474 // Wait for all the files to complete writing so our hot cache is accurate
475 waitForWorkComplete();
476
477 ALOGV("HOTCACHE(REMOVE): Closing hot cache entry for %u", entryHash);
478 MultifileHotCache entry = mHotCache[entryHash];
479 freeHotCacheEntry(entry);
480
481 // Delete the entry from our tracking
482 mHotCacheSize -= entry.entrySize;
483 mHotCache.erase(entryHash);
484
485 return true;
486 }
487
488 return false;
489}
490
491bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
492 // Walk through our map of sorted last access times and remove files until under the limit
493 for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
494 uint32_t entryHash = cacheEntryIter->first;
495
496 ALOGV("LRU: Removing entryHash %u", entryHash);
497
498 // Track the overall size
499 MultifileEntryStats entryStats = getEntryStats(entryHash);
500 decreaseTotalCacheSize(entryStats.fileSize);
501
502 // Remove it from hot cache if present
503 removeFromHotCache(entryHash);
504
505 // Remove it from the system
506 std::string entryPath = mMultifileDirName + "/" + std::to_string(entryHash);
507 if (remove(entryPath.c_str()) != 0) {
508 ALOGE("LRU: Error removing %s: %s", entryPath.c_str(), std::strerror(errno));
509 return false;
510 }
511
512 // Increment the iterator before clearing the entry
513 cacheEntryIter++;
514
515 // Delete the entry from our tracking
516 size_t count = mEntryStats.erase(entryHash);
517 if (count != 1) {
518 ALOGE("LRU: Failed to remove entryHash (%u) from mEntryStats", entryHash);
519 return false;
520 }
521
522 // See if it has been reduced enough
523 size_t totalCacheSize = getTotalSize();
524 if (totalCacheSize <= cacheLimit) {
525 // Success
526 ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
527 return true;
528 }
529 }
530
531 ALOGV("LRU: Cache is emptry");
532 return false;
533}
534
535// When removing files, what fraction of the overall limit should be reached when removing files
536// A divisor of two will decrease the cache to 50%, four to 25% and so on
537constexpr uint32_t kCacheLimitDivisor = 2;
538
539// Calculate the cache size and remove old entries until under the limit
540void MultifileBlobCache::trimCache(size_t cacheByteLimit) {
541 // Start with the value provided by egl_cache
542 size_t limit = cacheByteLimit;
543
544 // Wait for all deferred writes to complete
545 waitForWorkComplete();
546
547 size_t size = getTotalSize();
548
549 // If size is larger than the threshold, remove files using LRU
550 if (size > limit) {
551 ALOGV("TRIM: Multifile cache size is larger than %zu, removing old entries",
552 cacheByteLimit);
553 if (!applyLRU(limit / kCacheLimitDivisor)) {
554 ALOGE("Error when clearing multifile shader cache");
555 return;
556 }
557 }
558}
559
560// This function performs a task. It only knows how to write files to disk,
561// but it could be expanded if needed.
562void MultifileBlobCache::processTask(DeferredTask& task) {
563 switch (task.getTaskCommand()) {
564 case TaskCommand::Exit: {
565 ALOGV("DEFERRED: Shutting down");
566 return;
567 }
568 case TaskCommand::WriteToDisk: {
569 uint32_t entryHash = task.getEntryHash();
570 std::string& fullPath = task.getFullPath();
571 uint8_t* buffer = task.getBuffer();
572 size_t bufferSize = task.getBufferSize();
573
574 // Create the file or reset it if already present, read+write for user only
575 int fd = open(fullPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
576 if (fd == -1) {
577 ALOGE("Cache error in SET - failed to open fullPath: %s, error: %s",
578 fullPath.c_str(), std::strerror(errno));
579 return;
580 }
581
582 ALOGV("DEFERRED: Opened fd %i from %s", fd, fullPath.c_str());
583
584 ssize_t result = write(fd, buffer, bufferSize);
585 if (result != bufferSize) {
586 ALOGE("Error writing fileSize to cache entry (%s): %s", fullPath.c_str(),
587 std::strerror(errno));
588 return;
589 }
590
591 ALOGV("DEFERRED: Completed write for: %s", fullPath.c_str());
592 close(fd);
593
594 // Erase the entry from mDeferredWrites
595 // Since there could be multiple outstanding writes for an entry, find the matching one
596 typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
597 std::pair<entryIter, entryIter> iterPair = mDeferredWrites.equal_range(entryHash);
598 for (entryIter it = iterPair.first; it != iterPair.second; ++it) {
599 if (it->second == buffer) {
600 ALOGV("DEFERRED: Marking write complete for %u at %p", it->first, it->second);
601 mDeferredWrites.erase(it);
602 break;
603 }
604 }
605
606 return;
607 }
608 default: {
609 ALOGE("DEFERRED: Unhandled task type");
610 return;
611 }
612 }
613}
614
615// This function will wait until tasks arrive, then execute them
616// If the exit command is submitted, the loop will terminate
617void MultifileBlobCache::processTasksImpl(bool* exitThread) {
618 while (true) {
619 std::unique_lock<std::mutex> lock(mWorkerMutex);
620 if (mTasks.empty()) {
621 ALOGV("WORKER: No tasks available, waiting");
622 mWorkerThreadIdle = true;
623 mWorkerIdleCondition.notify_all();
624 // Only wake if notified and command queue is not empty
625 mWorkAvailableCondition.wait(lock, [this] { return !mTasks.empty(); });
626 }
627
628 ALOGV("WORKER: Task available, waking up.");
629 mWorkerThreadIdle = false;
630 DeferredTask task = std::move(mTasks.front());
631 mTasks.pop();
632
633 if (task.getTaskCommand() == TaskCommand::Exit) {
634 ALOGV("WORKER: Exiting work loop.");
635 *exitThread = true;
636 mWorkerThreadIdle = true;
637 mWorkerIdleCondition.notify_one();
638 return;
639 }
640
641 lock.unlock();
642 processTask(task);
643 }
644}
645
646// Process tasks until the exit task is submitted
647void MultifileBlobCache::processTasks() {
648 while (true) {
649 bool exitThread = false;
650 processTasksImpl(&exitThread);
651 if (exitThread) {
652 break;
653 }
654 }
655}
656
657// Add a task to the queue to be processed by the worker thread
658void MultifileBlobCache::queueTask(DeferredTask&& task) {
659 std::lock_guard<std::mutex> queueLock(mWorkerMutex);
660 mTasks.emplace(std::move(task));
661 mWorkAvailableCondition.notify_one();
662}
663
664// Wait until all tasks have been completed
665void MultifileBlobCache::waitForWorkComplete() {
666 std::unique_lock<std::mutex> lock(mWorkerMutex);
667 mWorkerIdleCondition.wait(lock, [this] { return (mTasks.empty() && mWorkerThreadIdle); });
668}
669
670}; // namespace android