10#include "kshareddatacache.h"
11#include "kcoreaddons_debug.h"
12#include "ksdcmapping_p.h"
13#include "ksdcmemory_p.h"
15#include "kshareddatacache_p.h"
20#include <QRandomGenerator>
21#include <QStandardPaths>
28 Private(
const QString &name,
unsigned defaultCacheSize,
unsigned expectedItemSize)
32 , m_defaultCacheSize(defaultCacheSize)
33 , m_expectedItemSize(expectedItemSize)
35 createMemoryMapping();
38 void createMemoryMapping()
44 unsigned cacheSize = qMax(m_defaultCacheSize, uint(SharedMemory::MINIMUM_CACHE_SIZE));
45 unsigned pageSize = SharedMemory::equivalentPageSize(m_expectedItemSize);
50 cacheSize = qMax(pageSize * 256, cacheSize);
55 QFile file(cacheName);
58 qCWarning(KCOREADDONS_DEBUG) <<
"Failed to create cache dir" << fileInfo.absolutePath();
68 uint size = SharedMemory::totalSize(cacheSize, pageSize);
69 Q_ASSERT(size >= cacheSize);
72 if (file.open(
QIODevice::ReadWrite) && (file.size() >= size || (ensureFileAllocated(file.handle(), size) && file.resize(size)))) {
74 m_mapping.reset(
new KSDCMapping(&file, size, cacheSize, pageSize));
75 shm = m_mapping->m_mapped;
76 }
catch (KSDCCorrupted) {
80 qCWarning(KCOREADDONS_DEBUG) <<
"Deleting corrupted cache" << cacheName;
82 QFile file(cacheName);
83 if (file.open(
QIODevice::ReadWrite) && ensureFileAllocated(file.handle(), size) && file.resize(size)) {
85 m_mapping.reset(
new KSDCMapping(&file, size, cacheSize, pageSize));
86 }
catch (KSDCCorrupted) {
88 qCCritical(KCOREADDONS_DEBUG) <<
"Even a brand-new cache starts off corrupted, something is"
89 <<
"seriously wrong. :-(";
96 m_mapping.reset(
new KSDCMapping(
nullptr, size, cacheSize, pageSize));
97 shm = m_mapping->m_mapped;
103 void recoverCorruptedCache()
105 qCWarning(KCOREADDONS_DEBUG) <<
"Deleting corrupted cache" << m_cacheName;
109 createMemoryMapping();
123 while (!d->m_mapping->lock() && !d->m_mapping->isLockedCacheSafe()) {
124 d->recoverCorruptedCache();
126 if (!d->m_mapping->isValid()) {
127 qCWarning(KCOREADDONS_DEBUG) <<
"Lost the connection to shared memory for cache" << d->m_cacheName;
131 if (lockCount++ > 4) {
132 qCCritical(KCOREADDONS_DEBUG) <<
"There is a very serious problem with the KDE data cache" << d->m_cacheName
133 <<
"giving up trying to access cache.";
142 CacheLocker(
const Private *_d)
143 : d(const_cast<Private *>(_d))
145 if (Q_UNLIKELY(!d || !cautiousLock())) {
153 d->m_mapping->unlock();
157 CacheLocker(
const CacheLocker &) =
delete;
158 CacheLocker &operator=(
const CacheLocker &) =
delete;
168 std::unique_ptr<KSDCMapping> m_mapping;
169 uint m_defaultCacheSize;
170 uint m_expectedItemSize;
177 d =
new Private(cacheName, defaultCacheSize, expectedItemSize);
178 }
catch (KSDCCorrupted) {
179 qCCritical(KCOREADDONS_DEBUG) <<
"Failed to initialize KSharedDataCache!";
184KSharedDataCache::~KSharedDataCache()
196 Private::CacheLocker lock(d);
202 uint keyHash = SharedMemory::generateHash(encodedKey);
203 uint position = keyHash % d->shm->indexTableSize();
206 IndexTableEntry *indices = d->shm->indexTable();
213 const static double startCullPoint = 0.5l;
214 const static double mustCullPoint = 0.96l;
217 double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize() / d->shm->cacheSize);
218 bool cullCollisions =
false;
220 if (Q_UNLIKELY(loadFactor >= mustCullPoint)) {
221 cullCollisions =
true;
222 }
else if (loadFactor > startCullPoint) {
223 const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint);
225 cullCollisions =
true;
233 uint probeNumber = 1;
234 while (indices[position].useCount > 0 && probeNumber < SharedMemory::MAX_PROBE_COUNT) {
238 if (Q_UNLIKELY(indices[position].fileNameHash == keyHash)) {
246 if (cullCollisions && (::time(
nullptr) - indices[position].lastUsedTime) > 60) {
247 indices[position].useCount >>= 1;
248 if (indices[position].useCount == 0) {
249 qCDebug(KCOREADDONS_DEBUG) <<
"Overwriting existing old cached entry due to collision.";
250 d->shm->removeEntry(position);
255 position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % d->shm->indexTableSize();
259 if (indices[position].useCount > 0 && indices[position].firstPage >= 0) {
260 qCDebug(KCOREADDONS_DEBUG) <<
"Overwriting existing cached entry due to collision.";
261 d->shm->removeEntry(position);
267 uint fileNameLength = 1 + encodedKey.
length();
268 uint requiredSize = fileNameLength + data.
size();
269 uint pagesNeeded = SharedMemory::intCeil(requiredSize, d->shm->cachePageSize());
272 if (pagesNeeded >= d->shm->pageTableSize()) {
273 qCWarning(KCOREADDONS_DEBUG) << key <<
"is too large to be cached.";
279 if (pagesNeeded > d->shm->cacheAvail || (firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize()) {
281 uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2);
283 if (d->shm->cacheAvail > freePagesDesired) {
286 d->shm->defragment();
287 firstPage = d->shm->findEmptyPages(pagesNeeded);
293 d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize()) - d->shm->cacheAvail);
294 firstPage = d->shm->findEmptyPages(pagesNeeded);
297 if (firstPage >= d->shm->pageTableSize() || d->shm->cacheAvail < pagesNeeded) {
298 qCCritical(KCOREADDONS_DEBUG) <<
"Unable to free up memory for" << key;
304 PageTableEntry *table = d->shm->pageTable();
305 for (uint i = 0; i < pagesNeeded; ++i) {
306 table[firstPage + i].index = position;
310 indices[position].fileNameHash = keyHash;
311 indices[position].totalItemSize = requiredSize;
312 indices[position].useCount = 1;
313 indices[position].addTime = ::time(
nullptr);
314 indices[position].lastUsedTime = indices[position].addTime;
315 indices[position].firstPage = firstPage;
318 d->shm->cacheAvail -= pagesNeeded;
321 void *dataPage = d->shm->page(firstPage);
322 if (Q_UNLIKELY(!dataPage)) {
323 throw KSDCCorrupted();
327 d->m_mapping->verifyProposedMemoryAccess(dataPage, requiredSize);
330 uchar *startOfPageData =
reinterpret_cast<uchar *
>(dataPage);
331 ::memcpy(startOfPageData, encodedKey.
constData(), fileNameLength);
332 ::memcpy(startOfPageData + fileNameLength, data.
constData(), data.
size());
335 }
catch (KSDCCorrupted) {
336 d->recoverCorruptedCache();
344 Private::CacheLocker lock(d);
351 qint32 entry = d->shm->findNamedEntry(encodedKey);
354 const IndexTableEntry *header = &d->shm->indexTable()[entry];
355 const void *resultPage = d->shm->page(header->firstPage);
356 if (Q_UNLIKELY(!resultPage)) {
357 throw KSDCCorrupted();
360 d->m_mapping->verifyProposedMemoryAccess(resultPage, header->totalItemSize);
363 header->lastUsedTime = ::time(
nullptr);
367 const char *cacheData =
reinterpret_cast<const char *
>(resultPage);
368 cacheData += encodedKey.
size();
372 *destination =
QByteArray(cacheData, header->totalItemSize - encodedKey.
size() - 1);
377 }
catch (KSDCCorrupted) {
378 d->recoverCorruptedCache();
387 Private::CacheLocker lock(d);
389 if (!lock.failed()) {
392 }
catch (KSDCCorrupted) {
393 d->recoverCorruptedCache();
400 Private::CacheLocker lock(d);
405 return d->shm->findNamedEntry(key.
toUtf8()) >= 0;
406 }
catch (KSDCCorrupted) {
407 d->recoverCorruptedCache();
419 qCDebug(KCOREADDONS_DEBUG) <<
"Removing cache at" << cachePath;
426 Private::CacheLocker lock(d);
431 return d->shm->cacheSize;
432 }
catch (KSDCCorrupted) {
433 d->recoverCorruptedCache();
441 Private::CacheLocker lock(d);
446 return d->shm->cacheAvail * d->shm->cachePageSize();
447 }
catch (KSDCCorrupted) {
448 d->recoverCorruptedCache();
456 return static_cast<EvictionPolicy
>(d->shm->evictionPolicy.fetchAndAddAcquire(0));
459 return NoEvictionPreference;
465 d->shm->evictionPolicy.fetchAndStoreRelease(
static_cast<int>(newPolicy));
472 return static_cast<unsigned>(d->shm->cacheTimestamp.fetchAndAddAcquire(0));
481 d->shm->cacheTimestamp.fetchAndStoreRelease(
static_cast<int>(newTimestamp));
A simple data cache which uses shared memory to quickly access data stored on disk.
unsigned freeSize() const
Returns the amount of free space in the cache, in bytes.
static void deleteCache(const QString &cacheName)
Removes the underlying file from the cache.
void clear()
Removes all entries from the cache.
unsigned totalSize() const
Returns the usable cache size in bytes.
void setEvictionPolicy(EvictionPolicy newPolicy)
Sets the entry removal policy for the shared cache to newPolicy.
KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize=0)
Attaches to a shared cache, creating it if necessary.
bool insert(const QString &key, const QByteArray &data)
Attempts to insert the entry data into the shared cache, named by key, and returns true only if succe...
unsigned timestamp() const
bool contains(const QString &key) const
Returns true if the cache currently contains the image for the given filename.
EvictionPolicy evictionPolicy() const
void setTimestamp(unsigned newTimestamp)
Sets the shared timestamp of the cache.
bool find(const QString &key, QByteArray *destination) const
Returns the data in the cache named by key (even if it's some other process's data named with the sam...
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
QString name(StandardAction id)
const char * constData() const const
qsizetype length() const const
qsizetype size() const const
QRandomGenerator * global()
QString writableLocation(StandardLocation type)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QByteArray toUtf8() const const