11#include "kcoreaddons_debug.h"
13#include "ksdcmemory_p.h"
25static unsigned int MurmurHashAligned(
const void *key,
int len,
unsigned int seed)
27 const unsigned int m = 0xc6a4a793;
30 const unsigned char *data =
reinterpret_cast<const unsigned char *
>(key);
32 unsigned int h = seed ^ (len * m);
34 int align =
reinterpret_cast<quintptr
>(data) & 3;
36 if (align && len >= 4) {
58 int sl = 8 * (4 - align);
64 d = *
reinterpret_cast<const unsigned int *
>(data);
65 t = (t >> sr) | (d << sl);
77 int pack = len < align ? len : align;
92 h += (t >> sr) | (d << sl);
101 h += *
reinterpret_cast<const unsigned int *
>(data);
138quint32 SharedMemory::generateHash(
const QByteArray &buffer)
142 return MurmurHashAligned(buffer.
data(), buffer.
size(), 0xF0F00F0F);
153#if defined(Q_CC_GNU) || defined(Q_CC_SUN)
154#define ALIGNOF(x) (__alignof__(x))
160struct __alignmentHack {
163 static const size_t size = offsetof(__alignmentHack, obj);
165#define ALIGNOF(x) (__alignmentHack<x>::size)
173T *alignTo(
const void *
start, uint size = ALIGNOF(T))
175 quintptr mask = size - 1;
178 quintptr basePointer =
reinterpret_cast<quintptr
>(
start);
182 basePointer = (basePointer + mask) & ~mask;
184 return reinterpret_cast<T *
>(basePointer);
194const T *offsetAs(
const void *
const base, qint32 offset)
196 const char *ptr =
reinterpret_cast<const char *
>(base);
197 return alignTo<const T>(ptr + offset);
202T *offsetAs(
void *
const base, qint32 offset)
204 char *ptr =
reinterpret_cast<char *
>(base);
205 return alignTo<T>(ptr + offset);
213unsigned SharedMemory::intCeil(
unsigned a,
unsigned b)
216 if (Q_UNLIKELY(b == 0 || ((a + b) < a))) {
217 throw KSDCCorrupted();
220 return (a + b - 1) / b;
226static unsigned countSetBits(
unsigned value)
232 for (count = 0; value != 0; count++) {
233 value &= (value - 1);
241unsigned SharedMemory::equivalentPageSize(
unsigned itemSize)
248 while ((itemSize >>= 1) != 0) {
254 log2OfSize = qBound(9, log2OfSize, 18);
256 return (1 << log2OfSize);
260unsigned SharedMemory::cachePageSize()
const
262 unsigned _pageSize =
static_cast<unsigned>(
pageSize.loadRelaxed());
264 static const unsigned validSizeMask = 0x7FE00u;
267 if (Q_UNLIKELY(countSetBits(_pageSize) != 1 || (_pageSize & ~validSizeMask))) {
268 throw KSDCCorrupted();
286bool SharedMemory::performInitialSetup(uint _cacheSize, uint _pageSize)
288 if (_cacheSize < MINIMUM_CACHE_SIZE) {
289 qCCritical(KCOREADDONS_DEBUG) <<
"Internal error: Attempted to create a cache sized < " << MINIMUM_CACHE_SIZE;
293 if (_pageSize == 0) {
294 qCCritical(KCOREADDONS_DEBUG) <<
"Internal error: Attempted to create a cache with 0-sized pages.";
298 shmLock.type = findBestSharedLock();
299 if (shmLock.type == LOCKTYPE_INVALID) {
300 qCCritical(KCOREADDONS_DEBUG) <<
"Unable to find an appropriate lock to guard the shared cache. "
301 <<
"This *should* be essentially impossible. :(";
305 bool isProcessShared =
false;
306 std::unique_ptr<KSDCLock> tempLock(createLockFromId(shmLock.type, shmLock));
308 if (!tempLock->initialize(isProcessShared)) {
309 qCCritical(KCOREADDONS_DEBUG) <<
"Unable to initialize the lock for the cache!";
313 if (!isProcessShared) {
314 qCWarning(KCOREADDONS_DEBUG) <<
"Cache initialized, but does not support being"
315 <<
"shared across processes.";
320 cacheSize = _cacheSize;
322 version = PIXMAP_CACHE_VERSION;
323 cacheTimestamp =
static_cast<unsigned>(::time(
nullptr));
325 clearInternalTables();
332void SharedMemory::clearInternalTables()
335 cacheAvail = pageTableSize();
338 PageTableEntry *table = pageTable();
339 for (uint i = 0; i < pageTableSize(); ++i) {
344 IndexTableEntry *indices = indexTable();
345 for (uint i = 0; i < indexTableSize(); ++i) {
346 indices[i].firstPage = -1;
347 indices[i].useCount = 0;
348 indices[i].fileNameHash = 0;
349 indices[i].totalItemSize = 0;
350 indices[i].addTime = 0;
351 indices[i].lastUsedTime = 0;
355const IndexTableEntry *SharedMemory::indexTable()
const
359 return offsetAs<IndexTableEntry>(
this,
sizeof(*
this));
362const PageTableEntry *SharedMemory::pageTable()
const
364 const IndexTableEntry *base = indexTable();
365 base += indexTableSize();
368 return alignTo<PageTableEntry>(base);
371const void *SharedMemory::cachePages()
const
373 const PageTableEntry *tableStart = pageTable();
374 tableStart += pageTableSize();
377 return alignTo<void>(tableStart, cachePageSize());
380const void *SharedMemory::page(pageID at)
const
382 if (
static_cast<uint
>(at) >= pageTableSize()) {
387 const char *pageStart =
reinterpret_cast<const char *
>(cachePages());
388 pageStart += (at * cachePageSize());
390 return reinterpret_cast<const void *
>(pageStart);
397IndexTableEntry *SharedMemory::indexTable()
399 const SharedMemory *that =
const_cast<const SharedMemory *
>(
this);
400 return const_cast<IndexTableEntry *
>(that->indexTable());
403PageTableEntry *SharedMemory::pageTable()
405 const SharedMemory *that =
const_cast<const SharedMemory *
>(
this);
406 return const_cast<PageTableEntry *
>(that->pageTable());
409void *SharedMemory::cachePages()
411 const SharedMemory *that =
const_cast<const SharedMemory *
>(
this);
412 return const_cast<void *
>(that->cachePages());
415void *SharedMemory::page(pageID at)
417 const SharedMemory *that =
const_cast<const SharedMemory *
>(
this);
418 return const_cast<void *
>(that->page(at));
421uint SharedMemory::pageTableSize()
const
423 return cacheSize / cachePageSize();
426uint SharedMemory::indexTableSize()
const
430 return pageTableSize() / 2;
437pageID SharedMemory::findEmptyPages(uint pagesNeeded)
const
439 if (Q_UNLIKELY(pagesNeeded > pageTableSize())) {
440 return pageTableSize();
445 const PageTableEntry *table = pageTable();
446 uint contiguousPagesFound = 0;
448 for (pageID i = 0; i < static_cast<int>(pageTableSize()); ++i) {
449 if (table[i].index < 0) {
450 if (contiguousPagesFound == 0) {
453 contiguousPagesFound++;
455 contiguousPagesFound = 0;
458 if (contiguousPagesFound == pagesNeeded) {
463 return pageTableSize();
467bool SharedMemory::lruCompare(
const IndexTableEntry &l,
const IndexTableEntry &r)
470 if (l.firstPage < 0 && r.firstPage >= 0) {
473 if (l.firstPage >= 0 && r.firstPage < 0) {
479 return l.lastUsedTime < r.lastUsedTime;
483bool SharedMemory::seldomUsedCompare(
const IndexTableEntry &l,
const IndexTableEntry &r)
486 if (l.firstPage < 0 && r.firstPage >= 0) {
489 if (l.firstPage >= 0 && r.firstPage < 0) {
494 return l.useCount < r.useCount;
498bool SharedMemory::ageCompare(
const IndexTableEntry &l,
const IndexTableEntry &r)
501 if (l.firstPage < 0 && r.firstPage >= 0) {
504 if (l.firstPage >= 0 && r.firstPage < 0) {
510 return l.addTime < r.addTime;
513void SharedMemory::defragment()
515 if (cacheAvail * cachePageSize() == cacheSize) {
519 qCDebug(KCOREADDONS_DEBUG) <<
"Defragmenting the shared cache";
525 pageID currentPage = 0;
526 pageID idLimit =
static_cast<pageID
>(pageTableSize());
527 PageTableEntry *pages = pageTable();
529 if (Q_UNLIKELY(!pages || idLimit <= 0)) {
530 throw KSDCCorrupted();
534 while (currentPage < idLimit && pages[currentPage].index >= 0) {
538 pageID freeSpot = currentPage;
542 while (currentPage < idLimit) {
544 while (currentPage < idLimit && pages[currentPage].index < 0) {
548 if (currentPage >= idLimit) {
553 qint32 affectedIndex = pages[currentPage].index;
554 if (Q_UNLIKELY(affectedIndex < 0 || affectedIndex >= idLimit || indexTable()[affectedIndex].firstPage != currentPage)) {
555 throw KSDCCorrupted();
558 indexTable()[affectedIndex].firstPage = freeSpot;
562 while (currentPage < idLimit && pages[currentPage].index >= 0) {
563 const void *
const sourcePage = page(currentPage);
564 void *
const destinationPage = page(freeSpot);
568 if (Q_UNLIKELY(!sourcePage || !destinationPage || sourcePage < destinationPage)) {
569 throw KSDCCorrupted();
572 ::memcpy(destinationPage, sourcePage, cachePageSize());
573 pages[freeSpot].index = affectedIndex;
574 pages[currentPage].index = -1;
580 if (currentPage >= idLimit) {
587 if (affectedIndex != pages[currentPage].index) {
588 indexTable()[pages[currentPage].index].firstPage = freeSpot;
590 affectedIndex = pages[currentPage].index;
605qint32 SharedMemory::findNamedEntry(
const QByteArray &key)
const
607 uint keyHash = SharedMemory::generateHash(key);
608 uint position = keyHash % indexTableSize();
609 uint probeNumber = 1;
615 while (indexTable()[position].fileNameHash != keyHash && probeNumber < MAX_PROBE_COUNT) {
616 position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % indexTableSize();
620 if (indexTable()[position].fileNameHash == keyHash) {
621 pageID
firstPage = indexTable()[position].firstPage;
622 if (firstPage < 0 ||
static_cast<uint
>(firstPage) >= pageTableSize()) {
626 const void *resultPage = page(firstPage);
627 if (Q_UNLIKELY(!resultPage)) {
628 throw KSDCCorrupted();
631 const char *utf8FileName =
reinterpret_cast<const char *
>(resultPage);
632 if (qstrncmp(utf8FileName, key.
constData(), cachePageSize()) == 0) {
641void SharedMemory::deleteTable(IndexTableEntry *table)
656uint SharedMemory::removeUsedPages(uint numberNeeded)
658 if (numberNeeded == 0) {
659 qCCritical(KCOREADDONS_DEBUG) <<
"Internal error: Asked to remove exactly 0 pages for some reason.";
660 throw KSDCCorrupted();
663 if (numberNeeded > pageTableSize()) {
664 qCCritical(KCOREADDONS_DEBUG) <<
"Internal error: Requested more space than exists in the cache.";
665 qCCritical(KCOREADDONS_DEBUG) << numberNeeded <<
"requested, " << pageTableSize() <<
"is the total possible.";
666 throw KSDCCorrupted();
675 qCDebug(KCOREADDONS_DEBUG) <<
"Removing old entries to free up" << numberNeeded <<
"pages," << cacheAvail <<
"are already theoretically available.";
677 if (cacheAvail > 3 * numberNeeded) {
679 uint result = findEmptyPages(numberNeeded);
681 if (result < pageTableSize()) {
684 qCCritical(KCOREADDONS_DEBUG) <<
"Just defragmented a locked cache, but still there"
685 <<
"isn't enough room for the current request.";
692 std::unique_ptr<IndexTableEntry,
decltype(deleteTable) *> tablePtr(
new IndexTableEntry[indexTableSize()], deleteTable);
695 qCCritical(KCOREADDONS_DEBUG) <<
"Unable to allocate temporary memory for sorting the cache!";
696 clearInternalTables();
697 throw KSDCCorrupted();
702 IndexTableEntry *table = tablePtr.get();
704 ::memcpy(table, indexTable(),
sizeof(IndexTableEntry) * indexTableSize());
711 for (uint i = 0; i < indexTableSize(); ++i) {
712 table[i].firstPage = table[i].useCount > 0 ?
static_cast<pageID
>(i) : -1;
717 bool (*compareFunction)(
const IndexTableEntry &,
const IndexTableEntry &);
718 switch (evictionPolicy.loadRelaxed()) {
719 case KSharedDataCache::EvictLeastOftenUsed:
720 case KSharedDataCache::NoEvictionPreference:
722 compareFunction = seldomUsedCompare;
725 case KSharedDataCache::EvictLeastRecentlyUsed:
726 compareFunction = lruCompare;
729 case KSharedDataCache::EvictOldest:
730 compareFunction = ageCompare;
734 std::sort(table, table + indexTableSize(), compareFunction);
745 while (i < indexTableSize() && numberNeeded > cacheAvail) {
746 int curIndex = table[i++].firstPage;
749 if (curIndex < 0 ||
static_cast<uint
>(curIndex) >= indexTableSize()) {
750 qCCritical(KCOREADDONS_DEBUG) <<
"Trying to remove index" << curIndex <<
"out-of-bounds for index table of size" << indexTableSize();
751 throw KSDCCorrupted();
754 qCDebug(KCOREADDONS_DEBUG) <<
"Removing entry of" << indexTable()[curIndex].totalItemSize <<
"size";
755 removeEntry(curIndex);
762 pageID result = pageTableSize();
763 while (i < indexTableSize() && (
static_cast<uint
>(result = findEmptyPages(numberNeeded))) >= pageTableSize()) {
764 int curIndex = table[i++].firstPage;
769 return findEmptyPages(numberNeeded);
772 if (Q_UNLIKELY(
static_cast<uint
>(curIndex) >= indexTableSize())) {
773 throw KSDCCorrupted();
776 removeEntry(curIndex);
784uint SharedMemory::totalSize(uint cacheSize, uint effectivePageSize)
786 uint numberPages = intCeil(cacheSize, effectivePageSize);
787 uint indexTableSize = numberPages / 2;
792 IndexTableEntry *indexTableStart = offsetAs<IndexTableEntry>(
static_cast<void *
>(
nullptr),
sizeof(SharedMemory));
794 indexTableStart += indexTableSize;
796 PageTableEntry *pageTableStart =
reinterpret_cast<PageTableEntry *
>(indexTableStart);
797 pageTableStart = alignTo<PageTableEntry>(pageTableStart);
798 pageTableStart += numberPages;
801 char *cacheStart =
reinterpret_cast<char *
>(pageTableStart);
802 cacheStart += (numberPages * effectivePageSize);
805 cacheStart = alignTo<char>(cacheStart, ALIGNOF(
void *));
809 return static_cast<uint
>(
reinterpret_cast<quintptr
>(cacheStart));
812uint SharedMemory::fileNameHash(
const QByteArray &utf8FileName)
const
814 return generateHash(utf8FileName) % indexTableSize();
817void SharedMemory::clear()
819 clearInternalTables();
823void SharedMemory::removeEntry(uint index)
825 if (index >= indexTableSize() || cacheAvail > pageTableSize()) {
826 throw KSDCCorrupted();
829 PageTableEntry *pageTableEntries = pageTable();
830 IndexTableEntry *entriesIndex = indexTable();
833 pageID
firstPage = entriesIndex[index].firstPage;
834 if (firstPage < 0 ||
static_cast<quint32
>(firstPage) >= pageTableSize()) {
835 qCDebug(KCOREADDONS_DEBUG) <<
"Trying to remove an entry which is already invalid. This "
836 <<
"cache is likely corrupt.";
837 throw KSDCCorrupted();
840 if (index !=
static_cast<uint
>(pageTableEntries[firstPage].index)) {
841 qCCritical(KCOREADDONS_DEBUG) <<
"Removing entry" << index <<
"but the matching data"
842 <<
"doesn't link back -- cache is corrupt, clearing.";
843 throw KSDCCorrupted();
846 uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize());
847 uint savedCacheSize = cacheAvail;
848 for (uint i = firstPage; i < pageTableSize() && static_cast<uint>(pageTableEntries[i].index) == index; ++i) {
849 pageTableEntries[i].index = -1;
853 if ((cacheAvail - savedCacheSize) != entriesToRemove) {
854 qCCritical(KCOREADDONS_DEBUG) <<
"We somehow did not remove" << entriesToRemove <<
"when removing entry" << index <<
", instead we removed"
855 << (cacheAvail - savedCacheSize);
856 throw KSDCCorrupted();
861 void *
const startOfData = page(firstPage);
864 str.prepend(
" REMOVED: ");
866 str.prepend(
"ENTRY ");
868 ::memcpy(startOfData, str.constData(), str.size() + 1);
873 entriesIndex[index].fileNameHash = 0;
874 entriesIndex[index].totalItemSize = 0;
875 entriesIndex[index].useCount = 0;
876 entriesIndex[index].lastUsedTime = 0;
877 entriesIndex[index].addTime = 0;
878 entriesIndex[index].firstPage = -1;
Q_SCRIPTABLE Q_NOREPLY void start()
KDB_EXPORT KDbVersionInfo version()
KREPORT_EXPORT QPageSize::PageSizeId pageSize(const QString &key)
QAction * firstPage(const QObject *recvr, const char *slot, QObject *parent)
const char * constData() const const
QByteArray number(double n, char format, int precision)
qsizetype size() const const