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