diff options
Diffstat (limited to 'source/core/slang-persistent-cache.cpp')
| -rw-r--r-- | source/core/slang-persistent-cache.cpp | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/source/core/slang-persistent-cache.cpp b/source/core/slang-persistent-cache.cpp new file mode 100644 index 000000000..2b4113e16 --- /dev/null +++ b/source/core/slang-persistent-cache.cpp @@ -0,0 +1,289 @@ +#include "slang-persistent-cache.h" + +#include "../core/slang-io.h" +#include "../core/slang-stream.h" +#include "../core/slang-string-util.h" +#include "../core/slang-blob.h" + +namespace Slang +{ + +PersistentCache::PersistentCache(const Desc& desc) +{ + m_cacheDirectory = Path::simplify(desc.directory); + Path::createDirectory(m_cacheDirectory); + + m_lockFileName = Path::simplify(m_cacheDirectory + "/lock"); + m_indexFileName = Path::simplify(m_cacheDirectory + "/index"); + + m_lockFile.open(m_lockFileName); + + m_maxEntryCount = desc.maxEntryCount; + + resetStats(); + + initialize(); +} + +PersistentCache::~PersistentCache() +{ +} + +SlangResult PersistentCache::clear() +{ + if (!m_lockFile.isOpen()) + { + return SLANG_E_CANNOT_OPEN; + } + + // Acquire the exclusive lock. + std::lock_guard<std::mutex> mutexLock(m_mutex); + LockFileGuard fileLock(m_lockFile); + + struct Visitor : Path::Visitor + { + const String& directory; + const String& lockFileName; + + Visitor(const String& directory, const String& lockFileName) + : directory(directory) + , lockFileName(lockFileName) + {} + + void accept(Path::Type type, const UnownedStringSlice& fileName) SLANG_OVERRIDE + { + String fullPath = Path::simplify(directory + "/" + fileName);; + if (type == Path::Type::File && lockFileName != fullPath) + { + Path::remove(fullPath); + } + } + }; + + Visitor visitor(m_cacheDirectory, m_lockFileName); + Path::find(m_cacheDirectory, nullptr, &visitor); + + m_stats.entryCount = 0; + + return SLANG_OK; +} + +void PersistentCache::resetStats() +{ + m_stats.entryCount = 0; + m_stats.hitCount = 0; + m_stats.missCount = 0; +} + +SlangResult PersistentCache::readEntry(const Key& key, ISlangBlob** outData) +{ + // Be pessimistic and assume we have a cache miss. + ++m_stats.missCount; + + if (!m_lockFile.isOpen()) + { + return SLANG_E_CANNOT_OPEN; + } + + // Acquire the exclusive lock. + std::lock_guard<std::mutex> mutexLock(m_mutex); + LockFileGuard fileLock(m_lockFile); + + // Return if index does not exist. + if (!File::exists(m_indexFileName)) + { + return SLANG_E_NOT_FOUND; + } + + // Read the cache index. + CacheIndex cacheIndex; + SLANG_RETURN_ON_FAIL(readIndex(m_indexFileName, cacheIndex)); + + // Increase the age of all entries in the cache. + for (auto& entry : cacheIndex) + { + ++entry.age; + } + + // Find the entry. + Index entryIndex = cacheIndex.findFirstIndex([&key] (const CacheEntry& entry) { return entry.key == key; }); + if (entryIndex == -1) + { + return SLANG_E_NOT_FOUND; + } + + // Read the entry. + String entryFileName = getEntryFileName(key); + ScopedAllocation data; + SlangResult result = File::readAllBytes(entryFileName, data); + if (result == SLANG_OK) + { + --m_stats.missCount; + ++m_stats.hitCount; + cacheIndex[entryIndex].age = 0; + auto blob = RawBlob::moveCreate(data); + *outData = blob.detach(); + } + else + { + cacheIndex.removeAt(entryIndex); + } + + // Write the cache index. + SLANG_RETURN_ON_FAIL(writeIndex(m_indexFileName, cacheIndex)); + m_stats.entryCount = (Count)cacheIndex.getCount(); + + return result; +} + +SlangResult PersistentCache::writeEntry(const Key& key, ISlangBlob* data) +{ + SLANG_ASSERT(data); + + if (!m_lockFile.isOpen()) + { + return SLANG_E_CANNOT_OPEN; + } + + // Acquire the exclusive lock. + std::lock_guard<std::mutex> mutexLock(m_mutex); + LockFileGuard fileLock(m_lockFile); + + // Read the cache index. + // We ignore any errors when reading the index and just write a new one. + CacheIndex cacheIndex; + readIndex(m_indexFileName, cacheIndex); + + // Increase the age of all entries in the cache and get the index of + // the oldest entry. + Index oldestEntryIndex = -1; + uint32_t oldestEntryAge = 0; + for (Index entryIndex = 0; entryIndex < cacheIndex.getCount(); ++entryIndex) + { + auto& entry = cacheIndex[entryIndex]; + ++entry.age; + if (entry.age > oldestEntryAge) + { + oldestEntryIndex = entryIndex; + oldestEntryAge = entry.age; + } + } + + // Write the cache entry. + String entryFileName = getEntryFileName(key); + SLANG_RETURN_ON_FAIL(File::writeAllBytes(entryFileName, data->getBufferPointer(), data->getBufferSize())); + + // Update the index. + if (m_maxEntryCount > 0 && cacheIndex.getCount() >= m_maxEntryCount) + { + // Replace oldest entry. + SLANG_ASSERT(oldestEntryIndex >= 0); + File::remove(getEntryFileName(cacheIndex[oldestEntryIndex].key)); + cacheIndex[oldestEntryIndex] = CacheEntry{ key, 0 }; + } + else + { + // Add new entry. + cacheIndex.add(CacheEntry{ key, 0 }); + } + + // Write the cache index. + SlangResult result = writeIndex(m_indexFileName, cacheIndex); + if (result == SLANG_OK) + { + m_stats.entryCount = (Count)cacheIndex.getCount(); + } + else + { + // If writing the index failed, remove the entry file to avoid growing the cache. + Path::remove(entryFileName); + } + + return result; +} + +SlangResult PersistentCache::initialize() +{ + if (!m_lockFile.isOpen()) + { + return SLANG_E_CANNOT_OPEN; + } + + // Acquire the exclusive lock. + std::lock_guard<std::mutex> mutexLock(m_mutex); + LockFileGuard fileLock(m_lockFile); + + CacheIndex cacheIndex; + if (SLANG_SUCCEEDED(readIndex(m_indexFileName, cacheIndex))) + { + m_stats.entryCount = (Count)cacheIndex.getCount(); + } + + return SLANG_OK; +} + +String PersistentCache::getEntryFileName(const Key& key) +{ + StringBuilder str; + str << m_cacheDirectory << "/" << key.toString(); + return str; +} + +struct CacheIndexHeader +{ + char magic[4]; + uint32_t version; + uint32_t count; + uint32_t reserved; +}; + +static const char* kMagic = "SLS$"; +static const uint32_t kVersion = 1; + +SlangResult PersistentCache::readIndex(const String& fileName, CacheIndex& outIndex) +{ + FileStream fs; + SLANG_RETURN_ON_FAIL(fs.init(fileName, FileMode::Open)); + + // Get file size. + SLANG_RETURN_ON_FAIL(fs.seek(SeekOrigin::End, 0)); + size_t fileSize = (size_t)fs.getPosition(); + SLANG_RETURN_ON_FAIL(fs.seek(SeekOrigin::Start, 0)); + + CacheIndexHeader header; + SLANG_RETURN_ON_FAIL(fs.readExactly(&header, sizeof(header))); + if (::memcmp(header.magic, kMagic, 4) != 0 || header.version != kVersion) + { + return SLANG_E_INTERNAL_FAIL; + } + + // Return if payload does not have the right size. + if (header.count * sizeof(CacheEntry) != fileSize - sizeof(header)) + { + return SLANG_E_INTERNAL_FAIL; + } + + outIndex.setCount(header.count); + SLANG_RETURN_ON_FAIL(fs.readExactly(outIndex.getBuffer(), header.count * sizeof(CacheEntry))); + + return SLANG_OK; +} + +SlangResult PersistentCache::writeIndex(const String& fileName, const CacheIndex& index) +{ + FileStream fs; + SLANG_RETURN_ON_FAIL(fs.init(fileName, FileMode::Create)); + + CacheIndexHeader header; + ::memcpy(header.magic, kMagic, 4); + header.version = kVersion; + header.count = (uint32_t)index.getCount(); + header.reserved = 0; + SLANG_RETURN_ON_FAIL(fs.write(&header, sizeof(header))); + + SLANG_RETURN_ON_FAIL(fs.write(index.getBuffer(), index.getCount() * sizeof(CacheEntry))); + + return SLANG_OK; +} + +} |
