2 * Copyright (C) 2014-2015 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "NetworkCacheStorage.h"
29 #if ENABLE(NETWORK_CACHE)
32 #include "NetworkCacheCoders.h"
33 #include "NetworkCacheFileSystem.h"
34 #include "NetworkCacheIOChannel.h"
35 #include <condition_variable>
36 #include <wtf/RandomNumber.h>
37 #include <wtf/RunLoop.h>
38 #include <wtf/text/CString.h>
39 #include <wtf/text/StringBuilder.h>
42 namespace NetworkCache {
44 static const char versionDirectoryPrefix[] = "Version ";
45 static const char recordsDirectoryName[] = "Records";
46 static const char blobsDirectoryName[] = "Blobs";
47 static const char bodyPostfix[] = "-body";
49 static double computeRecordWorth(FileTimes);
51 struct Storage::ReadOperation {
52 ReadOperation(const Key& key, const RetrieveCompletionHandler& completionHandler)
54 , completionHandler(completionHandler)
58 const RetrieveCompletionHandler completionHandler;
60 std::unique_ptr<Record> resultRecord;
61 SHA1::Digest expectedBodyHash;
62 BlobStorage::Blob resultBodyBlob;
63 std::atomic<unsigned> activeCount { 0 };
66 struct Storage::WriteOperation {
67 WriteOperation(const Record& record, const MappedBodyHandler& mappedBodyHandler)
69 , mappedBodyHandler(mappedBodyHandler)
73 const MappedBodyHandler mappedBodyHandler;
75 std::atomic<unsigned> activeCount { 0 };
78 struct Storage::TraverseOperation {
79 TraverseOperation(TraverseFlags flags, const TraverseHandler& handler)
84 const TraverseFlags flags;
85 const TraverseHandler handler;
87 std::mutex activeMutex;
88 std::condition_variable activeCondition;
89 unsigned activeCount { 0 };
92 std::unique_ptr<Storage> Storage::open(const String& cachePath)
94 ASSERT(RunLoop::isMain());
96 if (!WebCore::makeAllDirectories(cachePath))
98 return std::unique_ptr<Storage>(new Storage(cachePath));
101 static String makeVersionedDirectoryPath(const String& baseDirectoryPath)
103 String versionSubdirectory = versionDirectoryPrefix + String::number(Storage::version);
104 return WebCore::pathByAppendingComponent(baseDirectoryPath, versionSubdirectory);
107 static String makeRecordsDirectoryPath(const String& baseDirectoryPath)
109 return WebCore::pathByAppendingComponent(makeVersionedDirectoryPath(baseDirectoryPath), recordsDirectoryName);
112 static String makeBlobDirectoryPath(const String& baseDirectoryPath)
114 return WebCore::pathByAppendingComponent(makeVersionedDirectoryPath(baseDirectoryPath), blobsDirectoryName);
117 void traverseRecordsFiles(const String& recordsPath, const std::function<void (const String&, const String&)>& function)
119 traverseDirectory(recordsPath, [&recordsPath, &function](const String& subdirName, DirectoryEntryType type) {
120 if (type != DirectoryEntryType::Directory)
122 String partitionPath = WebCore::pathByAppendingComponent(recordsPath, subdirName);
123 traverseDirectory(partitionPath, [&function, &partitionPath](const String& fileName, DirectoryEntryType type) {
124 if (type != DirectoryEntryType::File)
126 function(fileName, partitionPath);
131 static void deleteEmptyRecordsDirectories(const String& recordsPath)
133 traverseDirectory(recordsPath, [&recordsPath](const String& subdirName, DirectoryEntryType type) {
134 if (type != DirectoryEntryType::Directory)
136 // Let system figure out if it is really empty.
137 WebCore::deleteEmptyDirectory(WebCore::pathByAppendingComponent(recordsPath, subdirName));
141 Storage::Storage(const String& baseDirectoryPath)
142 : m_basePath(baseDirectoryPath)
143 , m_recordsPath(makeRecordsDirectoryPath(baseDirectoryPath))
144 , m_writeOperationDispatchTimer(*this, &Storage::dispatchPendingWriteOperations)
145 , m_ioQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage", WorkQueue::Type::Concurrent))
146 , m_backgroundIOQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage.background", WorkQueue::Type::Concurrent, WorkQueue::QOS::Background))
147 , m_serialBackgroundIOQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background))
148 , m_blobStorage(makeBlobDirectoryPath(baseDirectoryPath))
158 String Storage::basePath() const
160 return m_basePath.isolatedCopy();
163 String Storage::versionPath() const
165 return makeVersionedDirectoryPath(basePath());
168 String Storage::recordsPath() const
170 return m_recordsPath.isolatedCopy();
173 size_t Storage::approximateSize() const
175 return m_approximateRecordsSize + m_blobStorage.approximateSize();
178 void Storage::synchronize()
180 ASSERT(RunLoop::isMain());
182 if (m_synchronizationInProgress || m_shrinkInProgress)
184 m_synchronizationInProgress = true;
186 LOG(NetworkCacheStorage, "(NetworkProcess) synchronizing cache");
188 backgroundIOQueue().dispatch([this] {
189 auto recordFilter = std::make_unique<ContentsFilter>();
190 auto bodyFilter = std::make_unique<ContentsFilter>();
191 size_t recordsSize = 0;
193 traverseRecordsFiles(recordsPath(), [&recordFilter, &bodyFilter, &recordsSize, &count](const String& fileName, const String& partitionPath) {
194 auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
196 bool isBody = fileName.endsWith(bodyPostfix);
197 String hashString = isBody ? fileName.substring(0, Key::hashStringLength()) : fileName;
199 if (!Key::stringToHash(hashString, hash)) {
200 WebCore::deleteFile(filePath);
203 long long fileSize = 0;
204 WebCore::getFileSize(filePath, fileSize);
206 WebCore::deleteFile(filePath);
210 bodyFilter->add(hash);
213 recordFilter->add(hash);
214 recordsSize += fileSize;
218 auto* recordFilterPtr = recordFilter.release();
219 auto* bodyFilterPtr = bodyFilter.release();
220 RunLoop::main().dispatch([this, recordFilterPtr, bodyFilterPtr, recordsSize] {
221 auto recordFilter = std::unique_ptr<ContentsFilter>(recordFilterPtr);
222 auto bodyFilter = std::unique_ptr<ContentsFilter>(bodyFilterPtr);
224 for (auto hash : m_recordFilterHashesAddedDuringSynchronization)
225 recordFilter->add(hash);
226 m_recordFilterHashesAddedDuringSynchronization.clear();
228 for (auto hash : m_bodyFilterHashesAddedDuringSynchronization)
229 bodyFilter->add(hash);
230 m_bodyFilterHashesAddedDuringSynchronization.clear();
232 m_recordFilter = WTF::move(recordFilter);
233 m_bodyFilter = WTF::move(bodyFilter);
234 m_approximateRecordsSize = recordsSize;
235 m_synchronizationInProgress = false;
238 m_blobStorage.synchronize();
240 deleteEmptyRecordsDirectories(recordsPath());
242 LOG(NetworkCacheStorage, "(NetworkProcess) cache synchronization completed size=%zu count=%d", recordsSize, count);
246 void Storage::addToRecordFilter(const Key& key)
248 ASSERT(RunLoop::isMain());
251 m_recordFilter->add(key.hash());
253 // If we get new entries during filter synchronization take care to add them to the new filter as well.
254 if (m_synchronizationInProgress)
255 m_recordFilterHashesAddedDuringSynchronization.append(key.hash());
258 bool Storage::mayContain(const Key& key) const
260 ASSERT(RunLoop::isMain());
261 return !m_recordFilter || m_recordFilter->mayContain(key.hash());
264 String Storage::partitionPathForKey(const Key& key) const
266 ASSERT(!key.partition().isEmpty());
267 return WebCore::pathByAppendingComponent(recordsPath(), key.partition());
270 static String fileNameForKey(const Key& key)
272 return key.hashAsString();
275 String Storage::recordPathForKey(const Key& key) const
277 return WebCore::pathByAppendingComponent(partitionPathForKey(key), fileNameForKey(key));
280 static String bodyPathForRecordPath(const String& recordPath)
282 return recordPath + bodyPostfix;
285 String Storage::bodyPathForKey(const Key& key) const
287 return bodyPathForRecordPath(recordPathForKey(key));
290 struct RecordMetaData {
292 explicit RecordMetaData(const Key& key)
293 : cacheStorageVersion(Storage::version)
297 unsigned cacheStorageVersion;
299 // FIXME: Add encoder/decoder for time_point.
300 std::chrono::milliseconds epochRelativeTimeStamp;
301 SHA1::Digest headerHash;
303 SHA1::Digest bodyHash;
307 // Not encoded as a field. Header starts immediately after meta data.
308 uint64_t headerOffset;
311 static bool decodeRecordMetaData(RecordMetaData& metaData, const Data& fileData)
313 bool success = false;
314 fileData.apply([&metaData, &success](const uint8_t* data, size_t size) {
315 Decoder decoder(data, size);
316 if (!decoder.decode(metaData.cacheStorageVersion))
318 if (!decoder.decode(metaData.key))
320 if (!decoder.decode(metaData.epochRelativeTimeStamp))
322 if (!decoder.decode(metaData.headerHash))
324 if (!decoder.decode(metaData.headerSize))
326 if (!decoder.decode(metaData.bodyHash))
328 if (!decoder.decode(metaData.bodySize))
330 if (!decoder.decode(metaData.isBodyInline))
332 if (!decoder.verifyChecksum())
334 metaData.headerOffset = decoder.currentOffset();
341 static bool decodeRecordHeader(const Data& fileData, RecordMetaData& metaData, Data& headerData)
343 if (!decodeRecordMetaData(metaData, fileData)) {
344 LOG(NetworkCacheStorage, "(NetworkProcess) meta data decode failure");
348 if (metaData.cacheStorageVersion != Storage::version) {
349 LOG(NetworkCacheStorage, "(NetworkProcess) version mismatch");
353 headerData = fileData.subrange(metaData.headerOffset, metaData.headerSize);
354 if (metaData.headerHash != computeSHA1(headerData)) {
355 LOG(NetworkCacheStorage, "(NetworkProcess) header checksum mismatch");
361 void Storage::readRecord(ReadOperation& readOperation, const Data& recordData)
363 ASSERT(!RunLoop::isMain());
365 RecordMetaData metaData;
367 if (!decodeRecordHeader(recordData, metaData, headerData))
370 if (metaData.key != readOperation.key)
373 // Sanity check against time stamps in future.
374 auto timeStamp = std::chrono::system_clock::time_point(metaData.epochRelativeTimeStamp);
375 if (timeStamp > std::chrono::system_clock::now())
379 if (metaData.isBodyInline) {
380 size_t bodyOffset = metaData.headerOffset + headerData.size();
381 if (bodyOffset + metaData.bodySize != recordData.size())
383 bodyData = recordData.subrange(bodyOffset, metaData.bodySize);
384 if (metaData.bodyHash != computeSHA1(bodyData))
388 readOperation.expectedBodyHash = metaData.bodyHash;
389 readOperation.resultRecord = std::make_unique<Storage::Record>(Storage::Record {
397 static Data encodeRecordMetaData(const RecordMetaData& metaData)
401 encoder << metaData.cacheStorageVersion;
402 encoder << metaData.key;
403 encoder << metaData.epochRelativeTimeStamp;
404 encoder << metaData.headerHash;
405 encoder << metaData.headerSize;
406 encoder << metaData.bodyHash;
407 encoder << metaData.bodySize;
408 encoder << metaData.isBodyInline;
410 encoder.encodeChecksum();
412 return Data(encoder.buffer(), encoder.bufferSize());
415 Optional<BlobStorage::Blob> Storage::storeBodyAsBlob(WriteOperation& writeOperation)
417 auto bodyPath = bodyPathForKey(writeOperation.record.key);
420 auto blob = m_blobStorage.add(bodyPath, writeOperation.record.body);
421 if (blob.data.isNull())
424 ++writeOperation.activeCount;
426 RunLoop::main().dispatch([this, blob, &writeOperation] {
428 m_bodyFilter->add(writeOperation.record.key.hash());
429 if (m_synchronizationInProgress)
430 m_bodyFilterHashesAddedDuringSynchronization.append(writeOperation.record.key.hash());
432 if (writeOperation.mappedBodyHandler)
433 writeOperation.mappedBodyHandler(blob.data);
435 finishWriteOperation(writeOperation);
440 Data Storage::encodeRecord(const Record& record, Optional<BlobStorage::Blob> blob)
442 ASSERT(!blob || bytesEqual(blob.value().data, record.body));
444 RecordMetaData metaData(record.key);
445 metaData.epochRelativeTimeStamp = std::chrono::duration_cast<std::chrono::milliseconds>(record.timeStamp.time_since_epoch());
446 metaData.headerHash = computeSHA1(record.header);
447 metaData.headerSize = record.header.size();
448 metaData.bodyHash = blob ? blob.value().hash : computeSHA1(record.body);
449 metaData.bodySize = record.body.size();
450 metaData.isBodyInline = !blob;
452 auto encodedMetaData = encodeRecordMetaData(metaData);
453 auto headerData = concatenate(encodedMetaData, record.header);
455 if (metaData.isBodyInline)
456 return concatenate(headerData, record.body);
458 return { headerData };
461 void Storage::remove(const Key& key)
463 ASSERT(RunLoop::isMain());
465 // We can't remove the key from the Bloom filter (but some false positives are expected anyway).
466 // For simplicity we also don't reduce m_approximateSize on removals.
467 // The next synchronization will update everything.
469 serialBackgroundIOQueue().dispatch([this, key] {
470 WebCore::deleteFile(recordPathForKey(key));
471 m_blobStorage.remove(bodyPathForKey(key));
475 void Storage::updateFileModificationTime(const String& path)
477 StringCapture filePathCapture(path);
478 serialBackgroundIOQueue().dispatch([filePathCapture] {
479 updateFileModificationTimeIfNeeded(filePathCapture.string());
483 void Storage::dispatchReadOperation(ReadOperation& readOperation)
485 ASSERT(RunLoop::isMain());
486 ASSERT(m_activeReadOperations.contains(&readOperation));
488 auto recordPath = recordPathForKey(readOperation.key);
490 ++readOperation.activeCount;
492 bool shouldGetBodyBlob = !m_bodyFilter || m_bodyFilter->mayContain(readOperation.key.hash());
493 if (shouldGetBodyBlob)
494 ++readOperation.activeCount;
496 RefPtr<IOChannel> channel = IOChannel::open(recordPath, IOChannel::Type::Read);
497 channel->read(0, std::numeric_limits<size_t>::max(), &ioQueue(), [this, &readOperation](const Data& fileData, int error) {
499 readRecord(readOperation, fileData);
500 finishReadOperation(readOperation);
503 if (!shouldGetBodyBlob)
506 // Read the body blob in parallel with the record read.
507 ioQueue().dispatch([this, &readOperation] {
508 auto bodyPath = bodyPathForKey(readOperation.key);
509 readOperation.resultBodyBlob = m_blobStorage.get(bodyPath);
510 finishReadOperation(readOperation);
514 void Storage::finishReadOperation(ReadOperation& readOperation)
516 ASSERT(readOperation.activeCount);
517 // Record and body blob reads must finish.
518 if (--readOperation.activeCount)
521 RunLoop::main().dispatch([this, &readOperation] {
522 if (readOperation.resultRecord && readOperation.resultRecord->body.isNull()) {
523 if (readOperation.resultBodyBlob.hash == readOperation.expectedBodyHash)
524 readOperation.resultRecord->body = readOperation.resultBodyBlob.data;
526 readOperation.resultRecord = nullptr;
529 bool success = readOperation.completionHandler(WTF::move(readOperation.resultRecord));
531 updateFileModificationTime(recordPathForKey(readOperation.key));
533 remove(readOperation.key);
534 ASSERT(m_activeReadOperations.contains(&readOperation));
535 m_activeReadOperations.remove(&readOperation);
536 dispatchPendingReadOperations();
538 LOG(NetworkCacheStorage, "(NetworkProcess) read complete success=%d", success);
542 void Storage::dispatchPendingReadOperations()
544 ASSERT(RunLoop::isMain());
546 const int maximumActiveReadOperationCount = 5;
548 for (int priority = maximumRetrievePriority; priority >= 0; --priority) {
549 if (m_activeReadOperations.size() > maximumActiveReadOperationCount) {
550 LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel retrieves");
553 auto& pendingRetrieveQueue = m_pendingReadOperationsByPriority[priority];
554 if (pendingRetrieveQueue.isEmpty())
556 auto readOperation = pendingRetrieveQueue.takeLast();
557 auto& read = *readOperation;
558 m_activeReadOperations.add(WTF::move(readOperation));
559 dispatchReadOperation(read);
563 template <class T> bool retrieveFromMemory(const T& operations, const Key& key, Storage::RetrieveCompletionHandler& completionHandler)
565 for (auto& operation : operations) {
566 if (operation->record.key == key) {
567 LOG(NetworkCacheStorage, "(NetworkProcess) found write operation in progress");
568 auto record = operation->record;
569 RunLoop::main().dispatch([record, completionHandler] {
570 completionHandler(std::make_unique<Storage::Record>(record));
578 void Storage::dispatchPendingWriteOperations()
580 ASSERT(RunLoop::isMain());
582 const int maximumActiveWriteOperationCount { 1 };
584 while (!m_pendingWriteOperations.isEmpty()) {
585 if (m_activeWriteOperations.size() >= maximumActiveWriteOperationCount) {
586 LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel writes");
589 auto writeOperation = m_pendingWriteOperations.takeLast();
590 auto& write = *writeOperation;
591 m_activeWriteOperations.add(WTF::move(writeOperation));
593 dispatchWriteOperation(write);
597 static bool shouldStoreBodyAsBlob(const Data& bodyData)
599 const size_t maximumInlineBodySize { 16 * 1024 };
600 return bodyData.size() > maximumInlineBodySize;
603 void Storage::dispatchWriteOperation(WriteOperation& writeOperation)
605 ASSERT(RunLoop::isMain());
606 ASSERT(m_activeWriteOperations.contains(&writeOperation));
608 // This was added already when starting the store but filter might have been wiped.
609 addToRecordFilter(writeOperation.record.key);
611 backgroundIOQueue().dispatch([this, &writeOperation] {
612 auto partitionPath = partitionPathForKey(writeOperation.record.key);
613 auto recordPath = recordPathForKey(writeOperation.record.key);
615 WebCore::makeAllDirectories(partitionPath);
617 ++writeOperation.activeCount;
619 bool shouldStoreAsBlob = shouldStoreBodyAsBlob(writeOperation.record.body);
620 auto bodyBlob = shouldStoreAsBlob ? storeBodyAsBlob(writeOperation) : Nullopt;
622 auto recordData = encodeRecord(writeOperation.record, bodyBlob);
624 auto channel = IOChannel::open(recordPath, IOChannel::Type::Create);
625 size_t recordSize = recordData.size();
626 channel->write(0, recordData, nullptr, [this, &writeOperation, recordSize](int error) {
627 // On error the entry still stays in the contents filter until next synchronization.
628 m_approximateRecordsSize += recordSize;
629 finishWriteOperation(writeOperation);
631 LOG(NetworkCacheStorage, "(NetworkProcess) write complete error=%d", error);
636 void Storage::finishWriteOperation(WriteOperation& writeOperation)
638 ASSERT(RunLoop::isMain());
639 ASSERT(writeOperation.activeCount);
640 ASSERT(m_activeWriteOperations.contains(&writeOperation));
642 if (--writeOperation.activeCount)
645 m_activeWriteOperations.remove(&writeOperation);
646 dispatchPendingWriteOperations();
651 void Storage::retrieve(const Key& key, unsigned priority, RetrieveCompletionHandler&& completionHandler)
653 ASSERT(RunLoop::isMain());
654 ASSERT(priority <= maximumRetrievePriority);
655 ASSERT(!key.isNull());
658 completionHandler(nullptr);
662 if (!mayContain(key)) {
663 completionHandler(nullptr);
667 if (retrieveFromMemory(m_pendingWriteOperations, key, completionHandler))
669 if (retrieveFromMemory(m_activeWriteOperations, key, completionHandler))
672 auto readOperation = std::make_unique<ReadOperation>(key, WTF::move(completionHandler));
673 m_pendingReadOperationsByPriority[priority].prepend(WTF::move(readOperation));
674 dispatchPendingReadOperations();
677 void Storage::store(const Record& record, MappedBodyHandler&& mappedBodyHandler)
679 ASSERT(RunLoop::isMain());
680 ASSERT(!record.key.isNull());
685 auto writeOperation = std::make_unique<WriteOperation>(record, WTF::move(mappedBodyHandler));
686 m_pendingWriteOperations.prepend(WTF::move(writeOperation));
688 // Add key to the filter already here as we do lookups from the pending operations too.
689 addToRecordFilter(record.key);
691 bool isInitialWrite = m_pendingWriteOperations.size() == 1;
695 // Delay the start of writes a bit to avoid affecting early page load.
696 // Completing writes will dispatch more writes without delay.
697 static const auto initialWriteDelay = 1_s;
698 m_writeOperationDispatchTimer.startOneShot(initialWriteDelay);
701 void Storage::traverse(TraverseFlags flags, TraverseHandler&& traverseHandler)
703 ASSERT(RunLoop::isMain());
704 ASSERT(traverseHandler);
705 // Avoid non-thread safe std::function copies.
707 auto traverseOperationPtr = std::make_unique<TraverseOperation>(flags, WTF::move(traverseHandler));
708 auto& traverseOperation = *traverseOperationPtr;
709 m_activeTraverseOperations.add(WTF::move(traverseOperationPtr));
711 ioQueue().dispatch([this, &traverseOperation] {
712 traverseRecordsFiles(recordsPath(), [this, &traverseOperation](const String& fileName, const String& partitionPath) {
713 if (fileName.length() != Key::hashStringLength())
715 auto recordPath = WebCore::pathByAppendingComponent(partitionPath, fileName);
718 if (traverseOperation.flags & TraverseFlag::ComputeWorth)
719 worth = computeRecordWorth(fileTimes(recordPath));
720 unsigned bodyShareCount = 0;
721 if (traverseOperation.flags & TraverseFlag::ShareCount)
722 bodyShareCount = m_blobStorage.shareCount(bodyPathForRecordPath(recordPath));
724 std::unique_lock<std::mutex> lock(traverseOperation.activeMutex);
725 ++traverseOperation.activeCount;
727 auto channel = IOChannel::open(recordPath, IOChannel::Type::Read);
728 channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [this, &traverseOperation, worth, bodyShareCount](Data& fileData, int) {
729 RecordMetaData metaData;
731 if (decodeRecordHeader(fileData, metaData, headerData)) {
734 std::chrono::system_clock::time_point(metaData.epochRelativeTimeStamp),
739 static_cast<size_t>(metaData.bodySize),
742 String::fromUTF8(SHA1::hexDigest(metaData.bodyHash))
744 traverseOperation.handler(&record, info);
747 std::lock_guard<std::mutex> lock(traverseOperation.activeMutex);
748 --traverseOperation.activeCount;
749 traverseOperation.activeCondition.notify_one();
752 const unsigned maximumParallelReadCount = 5;
753 traverseOperation.activeCondition.wait(lock, [&traverseOperation] {
754 return traverseOperation.activeCount <= maximumParallelReadCount;
757 // Wait for all reads to finish.
758 std::unique_lock<std::mutex> lock(traverseOperation.activeMutex);
759 traverseOperation.activeCondition.wait(lock, [&traverseOperation] {
760 return !traverseOperation.activeCount;
762 RunLoop::main().dispatch([this, &traverseOperation] {
763 traverseOperation.handler(nullptr, { });
764 m_activeTraverseOperations.remove(&traverseOperation);
769 void Storage::setCapacity(size_t capacity)
771 ASSERT(RunLoop::isMain());
774 const size_t assumedAverageRecordSize = 50 << 10;
775 size_t maximumRecordCount = capacity / assumedAverageRecordSize;
776 // ~10 bits per element are required for <1% false positive rate.
777 size_t effectiveBloomFilterCapacity = ContentsFilter::tableSize / 10;
778 // If this gets hit it might be time to increase the filter size.
779 ASSERT(maximumRecordCount < effectiveBloomFilterCapacity);
782 m_capacity = capacity;
787 void Storage::clear(std::chrono::system_clock::time_point modifiedSinceTime, std::function<void ()>&& completionHandler)
789 ASSERT(RunLoop::isMain());
790 LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
793 m_recordFilter->clear();
795 m_bodyFilter->clear();
796 m_approximateRecordsSize = 0;
798 // Avoid non-thread safe std::function copies.
799 auto* completionHandlerPtr = completionHandler ? new std::function<void ()>(WTF::move(completionHandler)) : nullptr;
801 ioQueue().dispatch([this, modifiedSinceTime, completionHandlerPtr] {
802 auto recordsPath = this->recordsPath();
803 traverseRecordsFiles(recordsPath, [modifiedSinceTime](const String& fileName, const String& partitionPath) {
804 auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
805 if (modifiedSinceTime > std::chrono::system_clock::time_point::min()) {
806 auto times = fileTimes(filePath);
807 if (times.modification < modifiedSinceTime)
810 WebCore::deleteFile(filePath);
813 deleteEmptyRecordsDirectories(recordsPath);
815 // This cleans unreferences blobs.
816 m_blobStorage.synchronize();
818 if (completionHandlerPtr) {
819 RunLoop::main().dispatch([completionHandlerPtr] {
820 (*completionHandlerPtr)();
821 delete completionHandlerPtr;
827 static double computeRecordWorth(FileTimes times)
829 using namespace std::chrono;
830 auto age = system_clock::now() - times.creation;
831 // File modification time is updated manually on cache read. We don't use access time since OS may update it automatically.
832 auto accessAge = times.modification - times.creation;
835 if (age <= 0_s || accessAge < 0_s || accessAge > age)
838 // We like old entries that have been accessed recently.
839 return duration<double>(accessAge) / age;
842 static double deletionProbability(FileTimes times, unsigned bodyShareCount)
844 static const double maximumProbability { 0.33 };
845 static const unsigned maximumEffectiveShareCount { 5 };
847 auto worth = computeRecordWorth(times);
849 // Adjust a bit so the most valuable entries don't get deleted at all.
850 auto effectiveWorth = std::min(1.1 * worth, 1.);
852 auto probability = (1 - effectiveWorth) * maximumProbability;
854 // It is less useful to remove an entry that shares its body data.
856 probability /= std::min(bodyShareCount, maximumEffectiveShareCount);
861 void Storage::shrinkIfNeeded()
863 ASSERT(RunLoop::isMain());
865 if (approximateSize() > m_capacity)
869 void Storage::shrink()
871 ASSERT(RunLoop::isMain());
873 if (m_shrinkInProgress || m_synchronizationInProgress)
875 m_shrinkInProgress = true;
877 LOG(NetworkCacheStorage, "(NetworkProcess) shrinking cache approximateSize=%zu capacity=%zu", approximateSize(), m_capacity);
879 backgroundIOQueue().dispatch([this] {
880 auto recordsPath = this->recordsPath();
881 traverseRecordsFiles(recordsPath, [this](const String& fileName, const String& partitionPath) {
882 if (fileName.length() != Key::hashStringLength())
884 auto recordPath = WebCore::pathByAppendingComponent(partitionPath, fileName);
885 auto bodyPath = bodyPathForRecordPath(recordPath);
887 auto times = fileTimes(recordPath);
888 unsigned bodyShareCount = m_blobStorage.shareCount(bodyPath);
889 auto probability = deletionProbability(times, bodyShareCount);
891 bool shouldDelete = randomNumber() < probability;
893 LOG(NetworkCacheStorage, "Deletion probability=%f bodyLinkCount=%d shouldDelete=%d", probability, bodyShareCount, shouldDelete);
896 WebCore::deleteFile(recordPath);
897 m_blobStorage.remove(bodyPath);
901 RunLoop::main().dispatch([this] {
902 m_shrinkInProgress = false;
903 // We could synchronize during the shrink traversal. However this is fast and it is better to have just one code path.
907 LOG(NetworkCacheStorage, "(NetworkProcess) cache shrink completed");
911 void Storage::clearWriteQueue()
913 LOG(NetworkCacheStorage, "(NetworkProcess) clearing write queue");
915 m_pendingWriteOperations.clear();
918 void Storage::deleteOldVersions()
920 backgroundIOQueue().dispatch([this] {
921 auto cachePath = basePath();
922 traverseDirectory(cachePath, [&cachePath](const String& subdirName, DirectoryEntryType type) {
923 if (type != DirectoryEntryType::Directory)
925 if (!subdirName.startsWith(versionDirectoryPrefix))
927 auto versionString = subdirName.substring(strlen(versionDirectoryPrefix));
929 unsigned directoryVersion = versionString.toUIntStrict(&success);
932 if (directoryVersion >= version)
935 auto oldVersionPath = WebCore::pathByAppendingComponent(cachePath, subdirName);
936 LOG(NetworkCacheStorage, "(NetworkProcess) deleting old cache version, path %s", oldVersionPath.utf8().data());
938 deleteDirectoryRecursively(oldVersionPath);