• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.9.5 API Reference
  • KDE Home
  • Contact Us
 

KDECore

kshareddatacache.cpp
Go to the documentation of this file.
00001 /*
00002  * This file is part of the KDE project.
00003  * Copyright © 2010, 2012 Michael Pyne <mpyne@kde.org>
00004  * Copyright © 2012 Ralf Jung <ralfjung-e@gmx.de>
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Library General Public
00008  * License version 2 as published by the Free Software Foundation.
00009  *
00010  * This library includes "MurmurHash" code from Austin Appleby, which is
00011  * placed in the public domain. See http://sites.google.com/site/murmurhash/
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016  * Library General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Library General Public License
00019  * along with this library; see the file COPYING.LIB.  If not, write to
00020  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00021  * Boston, MA 02110-1301, USA.
00022  */
00023 
00024 #include "kshareddatacache.h"
00025 #include "kshareddatacache_p.h" // Various auxiliary support code
00026 
00027 #include <kdebug.h>
00028 #include <kglobal.h>
00029 #include <kstandarddirs.h>
00030 #include <krandom.h>
00031 
00032 #include <QtCore/QDateTime>
00033 #include <QtCore/QSharedPointer>
00034 #include <QtCore/QByteArray>
00035 #include <QtCore/QFile>
00036 #include <QtCore/QAtomicInt>
00037 #include <QtCore/QList>
00038 #include <QtCore/QMutex>
00039 #include <QtCore/QMutexLocker>
00040 
00041 #include <sys/types.h>
00042 #include <sys/mman.h>
00043 #include <stdlib.h>
00044 
00047 static const uint MAX_PROBE_COUNT = 6;
00048 
00049 int ksdcArea()
00050 {
00051     static int s_ksdcArea = KDebug::registerArea("KSharedDataCache", false);
00052     return s_ksdcArea;
00053 }
00054 
00063 class KSDCCorrupted
00064 {
00065     public:
00066     KSDCCorrupted()
00067     {
00068         kError(ksdcArea()) << "Error detected in cache, re-generating";
00069     }
00070 };
00071 
00072 //-----------------------------------------------------------------------------
00073 // MurmurHashAligned, by Austin Appleby
00074 // (Released to the public domain, or licensed under the MIT license where
00075 // software may not be released to the public domain. See
00076 // http://sites.google.com/site/murmurhash/)
00077 
00078 // Same algorithm as MurmurHash, but only does aligned reads - should be safer
00079 // on certain platforms.
00080 static unsigned int MurmurHashAligned(const void *key, int len, unsigned int seed)
00081 {
00082     const unsigned int m = 0xc6a4a793;
00083     const int r = 16;
00084 
00085     const unsigned char * data = reinterpret_cast<const unsigned char *>(key);
00086 
00087     unsigned int h = seed ^ (len * m);
00088 
00089     int align = reinterpret_cast<quintptr>(data) & 3;
00090 
00091     if(align & (len >= 4))
00092     {
00093         // Pre-load the temp registers
00094 
00095         unsigned int t = 0, d = 0;
00096 
00097         switch(align)
00098         {
00099             case 1: t |= data[2] << 16;
00100             case 2: t |= data[1] << 8;
00101             case 3: t |= data[0];
00102         }
00103 
00104         t <<= (8 * align);
00105 
00106         data += 4-align;
00107         len -= 4-align;
00108 
00109         int sl = 8 * (4-align);
00110         int sr = 8 * align;
00111 
00112         // Mix
00113 
00114         while(len >= 4)
00115         {
00116             d = *reinterpret_cast<const unsigned int *>(data);
00117             t = (t >> sr) | (d << sl);
00118             h += t;
00119             h *= m;
00120             h ^= h >> r;
00121             t = d;
00122 
00123             data += 4;
00124             len -= 4;
00125         }
00126 
00127         // Handle leftover data in temp registers
00128 
00129         int pack = len < align ? len : align;
00130 
00131         d = 0;
00132 
00133         switch(pack)
00134         {
00135         case 3: d |= data[2] << 16;
00136         case 2: d |= data[1] << 8;
00137         case 1: d |= data[0];
00138         case 0: h += (t >> sr) | (d << sl);
00139                 h *= m;
00140                 h ^= h >> r;
00141         }
00142 
00143         data += pack;
00144         len -= pack;
00145     }
00146     else
00147     {
00148         while(len >= 4)
00149         {
00150             h += *reinterpret_cast<const unsigned int *>(data);
00151             h *= m;
00152             h ^= h >> r;
00153 
00154             data += 4;
00155             len -= 4;
00156         }
00157     }
00158 
00159     //----------
00160     // Handle tail bytes
00161 
00162     switch(len)
00163     {
00164     case 3: h += data[2] << 16;
00165     case 2: h += data[1] << 8;
00166     case 1: h += data[0];
00167             h *= m;
00168             h ^= h >> r;
00169     };
00170 
00171     h *= m;
00172     h ^= h >> 10;
00173     h *= m;
00174     h ^= h >> 17;
00175 
00176     return h;
00177 }
00178 
00183 static quint32 generateHash(const QByteArray &buffer)
00184 {
00185     // The final constant is the "seed" for MurmurHash. Do *not* change it
00186     // without incrementing the cache version.
00187     return MurmurHashAligned(buffer.data(), buffer.size(), 0xF0F00F0F);
00188 }
00189 
00190 // Alignment concerns become a big deal when we're dealing with shared memory,
00191 // since trying to access a structure sized at, say 8 bytes at an address that
00192 // is not evenly divisible by 8 is a crash-inducing error on some
00193 // architectures. The compiler would normally take care of this, but with
00194 // shared memory the compiler will not necessarily know the alignment expected,
00195 // so make sure we account for this ourselves. To do so we need a way to find
00196 // out the expected alignment. Enter ALIGNOF...
00197 #ifndef ALIGNOF
00198 #if defined(Q_CC_GNU) || defined(Q_CC_SUN)
00199 #define ALIGNOF(x) (__alignof__ (x)) // GCC provides what we want directly
00200 #else
00201 
00202 #include <stddef.h> // offsetof
00203 
00204 template<class T>
00205 struct __alignmentHack
00206 {
00207     char firstEntry;
00208     T    obj;
00209     static const size_t size = offsetof(__alignmentHack, obj);
00210 };
00211 #define ALIGNOF(x) (__alignmentHack<x>::size)
00212 #endif // Non gcc
00213 #endif // ALIGNOF undefined
00214 
00215 // Returns a pointer properly aligned to handle size alignment.
00216 // size should be a power of 2. start is assumed to be the lowest
00217 // permissible address, therefore the return value will be >= start.
00218 template<class T>
00219 T* alignTo(const void *start, uint size = ALIGNOF(T))
00220 {
00221     quintptr mask = size - 1;
00222 
00223     // Cast to int-type to handle bit-twiddling
00224     quintptr basePointer = reinterpret_cast<quintptr>(start);
00225 
00226     // If (and only if) we are already aligned, adding mask into basePointer
00227     // will not increment any of the bits in ~mask and we get the right answer.
00228     basePointer = (basePointer + mask) & ~mask;
00229 
00230     return reinterpret_cast<T *>(basePointer);
00231 }
00232 
00239 template<class T>
00240 const T *offsetAs(const void *const base, qint32 offset)
00241 {
00242     const char *ptr = reinterpret_cast<const char*>(base);
00243     return alignTo<const T>(ptr + offset);
00244 }
00245 
00246 // Same as above, but for non-const objects
00247 template<class T>
00248 T *offsetAs(void *const base, qint32 offset)
00249 {
00250     char *ptr = reinterpret_cast<char *>(base);
00251     return alignTo<T>(ptr + offset);
00252 }
00253 
00259 unsigned intCeil(unsigned a, unsigned b)
00260 {
00261     if (KDE_ISUNLIKELY(b == 0)) {
00262         throw KSDCCorrupted();
00263     }
00264 
00265     return (a + b - 1) / b;
00266 }
00267 
00271 static unsigned countSetBits(unsigned value)
00272 {
00273     // K&R / Wegner's algorithm used. GCC supports __builtin_popcount but we
00274     // expect there to always be only 1 bit set so this should be perhaps a bit
00275     // faster 99.9% of the time.
00276     unsigned count = 0;
00277     for (count = 0; value != 0; count++) {
00278         value &= (value - 1); // Clears least-significant set bit.
00279     }
00280     return count;
00281 }
00282 
00283 typedef qint32 pageID;
00284 
00285 // =========================================================================
00286 // Description of the cache:
00287 //
00288 // The shared memory cache is designed to be handled as two separate objects,
00289 // all contained in the same global memory segment. First off, there is the
00290 // basic header data, consisting of the global header followed by the
00291 // accounting data necessary to hold items (described in more detail
00292 // momentarily). Following the accounting data is the start of the "page table"
00293 // (essentially just as you'd see it in an Operating Systems text).
00294 //
00295 // The page table contains shared memory split into fixed-size pages, with a
00296 // configurable page size. In the event that the data is too large to fit into
00297 // a single logical page, it will need to occupy consecutive pages of memory.
00298 //
00299 // The accounting data that was referenced earlier is split into two:
00300 //
00301 // 1. index table, containing a fixed-size list of possible cache entries.
00302 // Each index entry is of type IndexTableEntry (below), and holds the various
00303 // accounting data and a pointer to the first page.
00304 //
00305 // 2. page table, which is used to speed up the process of searching for
00306 // free pages of memory. There is one entry for every page in the page table,
00307 // and it contains the index of the one entry in the index table actually
00308 // holding the page (or <0 if the page is free).
00309 //
00310 // The entire segment looks like so:
00311 // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
00312 // ? Header │ Index Table │ Page Table ? Pages │       │       │       │...?
00313 // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
00314 // =========================================================================
00315 
00316 // All elements of this struct must be "plain old data" (POD) types since it
00317 // will be in shared memory.  In addition, no pointers!  To point to something
00318 // you must use relative offsets since the pointer start addresses will be
00319 // different in each process.
00320 struct IndexTableEntry
00321 {
00322             uint   fileNameHash;
00323             uint   totalItemSize; // in bytes
00324     mutable uint   useCount;
00325             time_t addTime;
00326     mutable time_t lastUsedTime;
00327             pageID firstPage;
00328 };
00329 
00330 // Page table entry
00331 struct PageTableEntry
00332 {
00333     // int so we can use values <0 for unassigned pages.
00334     qint32 index;
00335 };
00336 
00337 // Each individual page contains the cached data. The first page starts off with
00338 // the utf8-encoded key, a null '\0', and then the data follows immediately
00339 // from the next byte, possibly crossing consecutive page boundaries to hold
00340 // all of the data.
00341 // There is, however, no specific struct for a page, it is simply a location in
00342 // memory.
00343 
00344 // This is effectively the layout of the shared memory segment. The variables
00345 // contained within form the header, data contained afterwards is pointed to
00346 // by using special accessor functions.
00347 struct SharedMemory
00348 {
00356     enum {
00357         PIXMAP_CACHE_VERSION = 12,
00358         MINIMUM_CACHE_SIZE = 4096
00359     };
00360 
00361     // Note to those who follow me. You should not, under any circumstances, ever
00362     // re-arrange the following two fields, even if you change the version number
00363     // for later revisions of this code.
00364     QAtomicInt ready; 
00365     quint8     version;
00366 
00367     // See kshareddatacache_p.h
00368     SharedLock shmLock;
00369 
00370     uint       cacheSize;
00371     uint       cacheAvail;
00372     QAtomicInt evictionPolicy;
00373 
00374     // pageSize and cacheSize determine the number of pages. The number of
00375     // pages determine the page table size and (indirectly) the index table
00376     // size.
00377     QAtomicInt pageSize;
00378 
00379     // This variable is added to reserve space for later cache timestamping
00380     // support. The idea is this variable will be updated when the cache is
00381     // written to, to allow clients to detect a changed cache quickly.
00382     QAtomicInt cacheTimestamp;
00383 
00387     static unsigned equivalentPageSize(unsigned itemSize)
00388     {
00389         if (itemSize == 0) {
00390             return 4096; // Default average item size.
00391         }
00392 
00393         int log2OfSize = 0;
00394         while ((itemSize >>= 1) != 0) {
00395             log2OfSize++;
00396         }
00397 
00398         // Bound page size between 512 bytes and 256 KiB.
00399         // If this is adjusted, also alter validSizeMask in cachePageSize
00400         log2OfSize = qBound(9, log2OfSize, 18);
00401 
00402         return (1 << log2OfSize);
00403     }
00404 
00405     // Returns pageSize in unsigned format.
00406     unsigned cachePageSize() const
00407     {
00408         unsigned _pageSize = static_cast<unsigned>(pageSize);
00409         // bits 9-18 may be set.
00410         static const unsigned validSizeMask = 0x7FE00u;
00411 
00412         // Check for page sizes that are not a power-of-2, or are too low/high.
00413         if (KDE_ISUNLIKELY(countSetBits(_pageSize) != 1 || (_pageSize & ~validSizeMask))) {
00414             throw KSDCCorrupted();
00415         }
00416 
00417         return _pageSize;
00418     }
00419 
00432     bool performInitialSetup(uint _cacheSize, uint _pageSize)
00433     {
00434         if (_cacheSize < MINIMUM_CACHE_SIZE) {
00435             kError(ksdcArea()) << "Internal error: Attempted to create a cache sized < "
00436                         << MINIMUM_CACHE_SIZE;
00437             return false;
00438         }
00439 
00440         if (_pageSize == 0) {
00441             kError(ksdcArea()) << "Internal error: Attempted to create a cache with 0-sized pages.";
00442             return false;
00443         }
00444 
00445         shmLock.type = findBestSharedLock();
00446         if (shmLock.type == LOCKTYPE_INVALID) {
00447             kError(ksdcArea()) << "Unable to find an appropriate lock to guard the shared cache. "
00448                         << "This *should* be essentially impossible. :(";
00449             return false;
00450         }
00451 
00452         bool isProcessShared = false;
00453         QSharedPointer<KSDCLock> tempLock(createLockFromId(shmLock.type, shmLock));
00454 
00455         if (!tempLock->initialize(isProcessShared)) {
00456             kError(ksdcArea()) << "Unable to initialize the lock for the cache!";
00457             return false;
00458         }
00459 
00460         if (!isProcessShared) {
00461             kWarning(ksdcArea()) << "Cache initialized, but does not support being"
00462                           << "shared across processes.";
00463         }
00464 
00465         // These must be updated to make some of our auxiliary functions
00466         // work right since their values will be based on the cache size.
00467         cacheSize = _cacheSize;
00468         pageSize = _pageSize;
00469         version = PIXMAP_CACHE_VERSION;
00470         cacheTimestamp = static_cast<unsigned>(::time(0));
00471 
00472         clearInternalTables();
00473 
00474         // Unlock the mini-lock, and introduce a total memory barrier to make
00475         // sure all changes have propagated even without a mutex.
00476         ready.ref();
00477 
00478         return true;
00479     }
00480 
00481     void clearInternalTables()
00482     {
00483         // Assumes we're already locked somehow.
00484         cacheAvail = pageTableSize();
00485 
00486         // Setup page tables to point nowhere
00487         PageTableEntry *table = pageTable();
00488         for (uint i = 0; i < pageTableSize(); ++i) {
00489             table[i].index = -1;
00490         }
00491 
00492         // Setup index tables to be accurate.
00493         IndexTableEntry *indices = indexTable();
00494         for (uint i = 0; i < indexTableSize(); ++i) {
00495             indices[i].firstPage = -1;
00496             indices[i].useCount = 0;
00497             indices[i].fileNameHash = 0;
00498             indices[i].totalItemSize = 0;
00499             indices[i].addTime = 0;
00500             indices[i].lastUsedTime = 0;
00501         }
00502     }
00503 
00504     const IndexTableEntry *indexTable() const
00505     {
00506         // Index Table goes immediately after this struct, at the first byte
00507         // where alignment constraints are met (accounted for by offsetAs).
00508         return offsetAs<IndexTableEntry>(this, sizeof(*this));
00509     }
00510 
00511     const PageTableEntry *pageTable() const
00512     {
00513         const IndexTableEntry *base = indexTable();
00514         base += indexTableSize();
00515 
00516         // Let's call wherever we end up the start of the page table...
00517         return alignTo<PageTableEntry>(base);
00518     }
00519 
00520     const void *cachePages() const
00521     {
00522         const PageTableEntry *tableStart = pageTable();
00523         tableStart += pageTableSize();
00524 
00525         // Let's call wherever we end up the start of the data...
00526         return alignTo<void>(tableStart, cachePageSize());
00527     }
00528 
00529     const void *page(pageID at) const
00530     {
00531         if (static_cast<uint>(at) >= pageTableSize()) {
00532             return 0;
00533         }
00534 
00535         // We must manually calculate this one since pageSize varies.
00536         const char *pageStart = reinterpret_cast<const char *>(cachePages());
00537         pageStart += (at * cachePageSize());
00538 
00539         return reinterpret_cast<const void *>(pageStart);
00540     }
00541 
00542     // The following are non-const versions of some of the methods defined
00543     // above.  They use const_cast<> because I feel that is better than
00544     // duplicating the code.  I suppose template member functions (?)
00545     // may work, may investigate later.
00546     IndexTableEntry *indexTable()
00547     {
00548         const SharedMemory *that = const_cast<const SharedMemory*>(this);
00549         return const_cast<IndexTableEntry *>(that->indexTable());
00550     }
00551 
00552     PageTableEntry *pageTable()
00553     {
00554         const SharedMemory *that = const_cast<const SharedMemory*>(this);
00555         return const_cast<PageTableEntry *>(that->pageTable());
00556     }
00557 
00558     void *cachePages()
00559     {
00560         const SharedMemory *that = const_cast<const SharedMemory*>(this);
00561         return const_cast<void *>(that->cachePages());
00562     }
00563 
00564     void *page(pageID at)
00565     {
00566         const SharedMemory *that = const_cast<const SharedMemory*>(this);
00567         return const_cast<void *>(that->page(at));
00568     }
00569 
00570     uint pageTableSize() const
00571     {
00572         return cacheSize / cachePageSize();
00573     }
00574 
00575     uint indexTableSize() const
00576     {
00577         // Assume 2 pages on average are needed -> the number of entries
00578         // would be half of the number of pages.
00579         return pageTableSize() / 2;
00580     }
00581 
00586     pageID findEmptyPages(uint pagesNeeded) const
00587     {
00588         if (KDE_ISUNLIKELY(pagesNeeded > pageTableSize())) {
00589             return pageTableSize();
00590         }
00591 
00592         // Loop through the page table, find the first empty page, and just
00593         // makes sure that there are enough free pages.
00594         const PageTableEntry *table = pageTable();
00595         uint contiguousPagesFound = 0;
00596         pageID base = 0;
00597         for (pageID i = 0; i < static_cast<int>(pageTableSize()); ++i) {
00598             if (table[i].index < 0) {
00599                 if (contiguousPagesFound == 0) {
00600                     base = i;
00601                 }
00602                 contiguousPagesFound++;
00603             }
00604             else {
00605                 contiguousPagesFound = 0;
00606             }
00607 
00608             if (contiguousPagesFound == pagesNeeded) {
00609                 return base;
00610             }
00611         }
00612 
00613         return pageTableSize();
00614     }
00615 
00616     // left < right?
00617     static bool lruCompare(const IndexTableEntry &l, const IndexTableEntry &r)
00618     {
00619         // Ensure invalid entries migrate to the end
00620         if (l.firstPage < 0 && r.firstPage >= 0) {
00621             return false;
00622         }
00623         if (l.firstPage >= 0 && r.firstPage < 0) {
00624             return true;
00625         }
00626 
00627         // Most recently used will have the highest absolute time =>
00628         // least recently used (lowest) should go first => use left < right
00629         return l.lastUsedTime < r.lastUsedTime;
00630     }
00631 
00632     // left < right?
00633     static bool seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r)
00634     {
00635         // Ensure invalid entries migrate to the end
00636         if (l.firstPage < 0 && r.firstPage >= 0) {
00637             return false;
00638         }
00639         if (l.firstPage >= 0 && r.firstPage < 0) {
00640             return true;
00641         }
00642 
00643         // Put lowest use count at start by using left < right
00644         return l.useCount < r.useCount;
00645     }
00646 
00647     // left < right?
00648     static bool ageCompare(const IndexTableEntry &l, const IndexTableEntry &r)
00649     {
00650         // Ensure invalid entries migrate to the end
00651         if (l.firstPage < 0 && r.firstPage >= 0) {
00652             return false;
00653         }
00654         if (l.firstPage >= 0 && r.firstPage < 0) {
00655             return true;
00656         }
00657 
00658         // Oldest entries die first -- they have the lowest absolute add time,
00659         // so just like the others use left < right
00660         return l.addTime < r.addTime;
00661     }
00662 
00663     void defragment()
00664     {
00665         if (cacheAvail * cachePageSize() == cacheSize) {
00666             return; // That was easy
00667         }
00668 
00669         kDebug(ksdcArea()) << "Defragmenting the shared cache";
00670 
00671         // Just do a linear scan, and anytime there is free space, swap it
00672         // with the pages to its right. In order to meet the precondition
00673         // we need to skip any used pages first.
00674 
00675         pageID currentPage = 0;
00676         pageID idLimit = static_cast<pageID>(pageTableSize());
00677         PageTableEntry *pages = pageTable();
00678 
00679         if (KDE_ISUNLIKELY(!pages || idLimit <= 0)) {
00680             throw KSDCCorrupted();
00681         }
00682 
00683         // Skip used pages
00684         while (currentPage < idLimit && pages[currentPage].index >= 0) {
00685             ++currentPage;
00686         }
00687 
00688         pageID freeSpot = currentPage;
00689 
00690         // Main loop, starting from a free page, skip to the used pages and
00691         // move them back.
00692         while (currentPage < idLimit) {
00693             // Find the next used page
00694             while (currentPage < idLimit && pages[currentPage].index < 0) {
00695                 ++currentPage;
00696             }
00697 
00698             if (currentPage >= idLimit) {
00699                 break;
00700             }
00701 
00702             // Found an entry, move it.
00703             qint32 affectedIndex = pages[currentPage].index;
00704             if (KDE_ISUNLIKELY(affectedIndex < 0 ||
00705                         affectedIndex >= idLimit ||
00706                         indexTable()[affectedIndex].firstPage != currentPage))
00707             {
00708                 throw KSDCCorrupted();
00709             }
00710 
00711             indexTable()[affectedIndex].firstPage = freeSpot;
00712 
00713             // Moving one page at a time guarantees we can use memcpy safely
00714             // (in other words, the source and destination will not overlap).
00715             while (currentPage < idLimit && pages[currentPage].index >= 0) {
00716                 const void *const sourcePage = page(currentPage);
00717                 void *const destinationPage = page(freeSpot);
00718 
00719                 if(KDE_ISUNLIKELY(!sourcePage || !destinationPage)) {
00720                     throw KSDCCorrupted();
00721                 }
00722 
00723                 ::memcpy(destinationPage, sourcePage, cachePageSize());
00724                 pages[freeSpot].index = affectedIndex;
00725                 pages[currentPage].index = -1;
00726                 ++currentPage;
00727                 ++freeSpot;
00728 
00729                 // If we've just moved the very last page and it happened to
00730                 // be at the very end of the cache then we're done.
00731                 if (currentPage >= idLimit) {
00732                     break;
00733                 }
00734 
00735                 // We're moving consecutive used pages whether they belong to
00736                 // our affected entry or not, so detect if we've started moving
00737                 // the data for a different entry and adjust if necessary.
00738                 if (affectedIndex != pages[currentPage].index) {
00739                     indexTable()[pages[currentPage].index].firstPage = freeSpot;
00740                 }
00741                 affectedIndex = pages[currentPage].index;
00742             }
00743 
00744             // At this point currentPage is on a page that is unused, and the
00745             // cycle repeats. However, currentPage is not the first unused
00746             // page, freeSpot is, so leave it alone.
00747         }
00748     }
00749 
00756     qint32 findNamedEntry(const QByteArray &key) const
00757     {
00758         uint keyHash = generateHash(key);
00759         uint position = keyHash % indexTableSize();
00760         uint probeNumber = 1; // See insert() for description
00761 
00762         // Imagine 3 entries A, B, C in this logical probing chain. If B is
00763         // later removed then we can't find C either. So, we must keep
00764         // searching for probeNumber number of tries (or we find the item,
00765         // obviously).
00766         while (indexTable()[position].fileNameHash != keyHash &&
00767                probeNumber < MAX_PROBE_COUNT)
00768         {
00769             position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2)
00770                        % indexTableSize();
00771             probeNumber++;
00772         }
00773 
00774         if (indexTable()[position].fileNameHash == keyHash) {
00775             pageID firstPage = indexTable()[position].firstPage;
00776             if (firstPage < 0 || static_cast<uint>(firstPage) >= pageTableSize()) {
00777                 return -1;
00778             }
00779 
00780             const void *resultPage = page(firstPage);
00781             if (KDE_ISUNLIKELY(!resultPage)) {
00782                 throw KSDCCorrupted();
00783             }
00784 
00785             const char *utf8FileName = reinterpret_cast<const char *>(resultPage);
00786             if (qstrncmp(utf8FileName, key.constData(), cachePageSize()) == 0) {
00787                 return position;
00788             }
00789         }
00790 
00791         return -1; // Not found, or a different one found.
00792     }
00793 
00794     // Function to use with QSharedPointer in removeUsedPages below...
00795     static void deleteTable(IndexTableEntry *table) {
00796         delete [] table;
00797     }
00798 
00809     uint removeUsedPages(uint numberNeeded)
00810     {
00811         if (numberNeeded == 0) {
00812             kError(ksdcArea()) << "Internal error: Asked to remove exactly 0 pages for some reason.";
00813             throw KSDCCorrupted();
00814         }
00815 
00816         if (numberNeeded > pageTableSize()) {
00817             kError(ksdcArea()) << "Internal error: Requested more space than exists in the cache.";
00818             kError(ksdcArea()) << numberNeeded << "requested, " << pageTableSize() << "is the total possible.";
00819             throw KSDCCorrupted();
00820         }
00821 
00822         // If the cache free space is large enough we will defragment first
00823         // instead since it's likely we're highly fragmented.
00824         // Otherwise, we will (eventually) simply remove entries per the
00825         // eviction order set for the cache until there is enough room
00826         // available to hold the number of pages we need.
00827 
00828         kDebug(ksdcArea()) << "Removing old entries to free up" << numberNeeded << "pages,"
00829                     << cacheAvail << "are already theoretically available.";
00830 
00831         if (cacheAvail > 3 * numberNeeded) {
00832             defragment();
00833             uint result = findEmptyPages(numberNeeded);
00834 
00835             if (result < pageTableSize()) {
00836                 return result;
00837             }
00838             else {
00839                 kError(ksdcArea()) << "Just defragmented a locked cache, but still there"
00840                             << "isn't enough room for the current request.";
00841             }
00842         }
00843 
00844         // At this point we know we'll have to free some space up, so sort our
00845         // list of entries by whatever the current criteria are and start
00846         // killing expired entries.
00847         QSharedPointer<IndexTableEntry> tablePtr(new IndexTableEntry[indexTableSize()], deleteTable);
00848 
00849         if (!tablePtr) {
00850             kError(ksdcArea()) << "Unable to allocate temporary memory for sorting the cache!";
00851             clearInternalTables();
00852             throw KSDCCorrupted();
00853         }
00854 
00855         // We use tablePtr to ensure the data is destroyed, but do the access
00856         // via a helper pointer to allow for array ops.
00857         IndexTableEntry *table = tablePtr.data();
00858 
00859         ::memcpy(table, indexTable(), sizeof(IndexTableEntry) * indexTableSize());
00860 
00861         // Our entry ID is simply its index into the
00862         // index table, which qSort will rearrange all willy-nilly, so first
00863         // we'll save the *real* entry ID into firstPage (which is useless in
00864         // our copy of the index table). On the other hand if the entry is not
00865         // used then we note that with -1.
00866         for (uint i = 0; i < indexTableSize(); ++i) {
00867             table[i].firstPage = table[i].useCount > 0 ? static_cast<pageID>(i)
00868                                                        : -1;
00869         }
00870 
00871         // Declare the comparison function that we'll use to pass to qSort,
00872         // based on our cache eviction policy.
00873         bool (*compareFunction)(const IndexTableEntry &, const IndexTableEntry &);
00874         switch((int) evictionPolicy) {
00875         case (int) KSharedDataCache::EvictLeastOftenUsed:
00876         case (int) KSharedDataCache::NoEvictionPreference:
00877         default:
00878             compareFunction = seldomUsedCompare;
00879         break;
00880 
00881         case (int) KSharedDataCache::EvictLeastRecentlyUsed:
00882             compareFunction = lruCompare;
00883         break;
00884 
00885         case (int) KSharedDataCache::EvictOldest:
00886             compareFunction = ageCompare;
00887         break;
00888         }
00889 
00890         qSort(table, table + indexTableSize(), compareFunction);
00891 
00892         // Least recently used entries will be in the front.
00893         // Start killing until we have room.
00894 
00895         // Note on removeEntry: It expects an index into the index table,
00896         // but our sorted list is all jumbled. But we stored the real index
00897         // in the firstPage member.
00898         // Remove entries until we've removed at least the required number
00899         // of pages.
00900         uint i = 0;
00901         while (i < indexTableSize() && numberNeeded > cacheAvail) {
00902             int curIndex = table[i++].firstPage; // Really an index, not a page
00903 
00904             // Removed everything, still no luck (or curIndex is set but too high).
00905             if (curIndex < 0 || static_cast<uint>(curIndex) >= indexTableSize()) {
00906                 kError(ksdcArea()) << "Trying to remove index" << curIndex
00907                     << "out-of-bounds for index table of size" << indexTableSize();
00908                 throw KSDCCorrupted();
00909             }
00910 
00911             kDebug(ksdcArea()) << "Removing entry of" << indexTable()[curIndex].totalItemSize
00912                         << "size";
00913             removeEntry(curIndex);
00914         }
00915 
00916         // At this point let's see if we have freed up enough data by
00917         // defragmenting first and seeing if we can find that free space.
00918         defragment();
00919 
00920         pageID result = pageTableSize();
00921         while (i < indexTableSize() &&
00922               (static_cast<uint>(result = findEmptyPages(numberNeeded))) >= pageTableSize())
00923         {
00924             int curIndex = table[i++].firstPage;
00925 
00926             if (curIndex < 0) {
00927                 // One last shot.
00928                 defragment();
00929                 return findEmptyPages(numberNeeded);
00930             }
00931 
00932             if (KDE_ISUNLIKELY(static_cast<uint>(curIndex) >= indexTableSize())) {
00933                 throw KSDCCorrupted();
00934             }
00935 
00936             removeEntry(curIndex);
00937         }
00938 
00939         // Whew.
00940         return result;
00941     }
00942 
00943     // Returns the total size required for a given cache size.
00944     static uint totalSize(uint cacheSize, uint effectivePageSize)
00945     {
00946         uint numberPages = intCeil(cacheSize, effectivePageSize);
00947         uint indexTableSize = numberPages / 2;
00948 
00949         // Knowing the number of pages, we can determine what addresses we'd be
00950         // using (properly aligned), and from there determine how much memory
00951         // we'd use.
00952         IndexTableEntry *indexTableStart =
00953                     offsetAs<IndexTableEntry>(static_cast<void*>(0), sizeof (SharedMemory));
00954 
00955         indexTableStart += indexTableSize;
00956 
00957         PageTableEntry *pageTableStart = reinterpret_cast<PageTableEntry *>(indexTableStart);
00958         pageTableStart = alignTo<PageTableEntry>(pageTableStart);
00959         pageTableStart += numberPages;
00960 
00961         // The weird part, we must manually adjust the pointer based on the page size.
00962         char *cacheStart = reinterpret_cast<char *>(pageTableStart);
00963         cacheStart += (numberPages * effectivePageSize);
00964 
00965         // ALIGNOF gives pointer alignment
00966         cacheStart = alignTo<char>(cacheStart, ALIGNOF(void*));
00967 
00968         // We've traversed the header, index, page table, and cache.
00969         // Wherever we're at now is the size of the enchilada.
00970         return static_cast<uint>(reinterpret_cast<quintptr>(cacheStart));
00971     }
00972 
00973     uint fileNameHash(const QByteArray &utf8FileName) const
00974     {
00975         return generateHash(utf8FileName) % indexTableSize();
00976     }
00977 
00978     void clear()
00979     {
00980         clearInternalTables();
00981     }
00982 
00983     void removeEntry(uint index);
00984 };
00985 
00986 // The per-instance private data, such as map size, whether
00987 // attached or not, pointer to shared memory, etc.
00988 class KSharedDataCache::Private
00989 {
00990     public:
00991     Private(const QString &name,
00992             unsigned defaultCacheSize,
00993             unsigned expectedItemSize
00994            )
00995         : m_cacheName(name)
00996         , shm(0)
00997         , m_lock(0)
00998         , m_mapSize(0)
00999         , m_defaultCacheSize(defaultCacheSize)
01000         , m_expectedItemSize(expectedItemSize)
01001         , m_expectedType(LOCKTYPE_INVALID)
01002     {
01003         mapSharedMemory();
01004     }
01005 
01006     // Put the cache in a condition to be able to call mapSharedMemory() by
01007     // completely detaching from shared memory (such as to respond to an
01008     // unrecoverable error).
01009     // m_mapSize must already be set to the amount of memory mapped to shm.
01010     void detachFromSharedMemory()
01011     {
01012         // The lock holds a reference into shared memory, so this must be
01013         // cleared before shm is removed.
01014         m_lock.clear();
01015 
01016         if (shm && !::munmap(shm, m_mapSize)) {
01017             kError(ksdcArea()) << "Unable to unmap shared memory segment"
01018                 << static_cast<void*>(shm);
01019         }
01020 
01021         shm = 0;
01022         m_mapSize = 0;
01023     }
01024 
01025     // This function does a lot of the important work, attempting to connect to shared
01026     // memory, a private anonymous mapping if that fails, and failing that, nothing (but
01027     // the cache remains "valid", we just don't actually do anything).
01028     void mapSharedMemory()
01029     {
01030         // 0-sized caches are fairly useless.
01031         unsigned cacheSize = qMax(m_defaultCacheSize, uint(SharedMemory::MINIMUM_CACHE_SIZE));
01032         unsigned pageSize = SharedMemory::equivalentPageSize(m_expectedItemSize);
01033 
01034         // Ensure that the cache is sized such that there is a minimum number of
01035         // pages available. (i.e. a cache consisting of only 1 page is fairly
01036         // useless and probably crash-prone).
01037         cacheSize = qMax(pageSize * 256, cacheSize);
01038 
01039         // The m_cacheName is used to find the file to store the cache in.
01040         QString cacheName = KGlobal::dirs()->locateLocal("cache", m_cacheName + QLatin1String(".kcache"));
01041         QFile file(cacheName);
01042 
01043         // The basic idea is to open the file that we want to map into shared
01044         // memory, and then actually establish the mapping. Once we have mapped the
01045         // file into shared memory we can close the file handle, the mapping will
01046         // still be maintained (unless the file is resized to be shorter than
01047         // expected, which we don't handle yet :-( )
01048 
01049         // size accounts for the overhead over the desired cacheSize
01050         uint size = SharedMemory::totalSize(cacheSize, pageSize);
01051         void *mapAddress = MAP_FAILED;
01052 
01053         if (size < cacheSize) {
01054             kError(ksdcArea()) << "Asked for a cache size less than requested size somehow -- Logic Error :(";
01055             return;
01056         }
01057 
01058         // We establish the shared memory mapping here, only if we will have appropriate
01059         // mutex support (systemSupportsProcessSharing), then we:
01060         // Open the file and resize to some sane value if the file is too small.
01061         if (file.open(QIODevice::ReadWrite) &&
01062            (file.size() >= size || file.resize(size)) &&
01063            ensureFileAllocated(file.handle(), size))
01064         {
01065             // Use mmap directly instead of QFile::map since the QFile (and its
01066             // shared mapping) will disappear unless we hang onto the QFile for no
01067             // reason (see the note below, we don't care about the file per se...)
01068             mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0);
01069 
01070             // So... it is possible that someone else has mapped this cache already
01071             // with a larger size. If that's the case we need to at least match
01072             // the size to be able to access every entry, so fixup the mapping.
01073             if (mapAddress != MAP_FAILED) {
01074                 SharedMemory *mapped = reinterpret_cast<SharedMemory *>(mapAddress);
01075 
01076                 // First make sure that the version of the cache on disk is
01077                 // valid.  We also need to check that version != 0 to
01078                 // disambiguate against an uninitialized cache.
01079                 if (mapped->version != SharedMemory::PIXMAP_CACHE_VERSION &&
01080                     mapped->version > 0)
01081                 {
01082                     kWarning(ksdcArea()) << "Deleting wrong version of cache" << cacheName;
01083 
01084                     // CAUTION: Potentially recursive since the recovery
01085                     // involves calling this function again.
01086                     m_mapSize = size;
01087                     shm = mapped;
01088                     recoverCorruptedCache();
01089                     return;
01090                 }
01091                 else if (mapped->cacheSize > cacheSize) {
01092                     // This order is very important. We must save the cache size
01093                     // before we remove the mapping, but unmap before overwriting
01094                     // the previous mapping size...
01095                     cacheSize = mapped->cacheSize;
01096                     unsigned actualPageSize = mapped->cachePageSize();
01097                     ::munmap(mapAddress, size);
01098                     size = SharedMemory::totalSize(cacheSize, actualPageSize);
01099                     mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0);
01100                 }
01101             }
01102         }
01103 
01104         // We could be here without the mapping established if:
01105         // 1) Process-shared synchronization is not supported, either at compile or run time,
01106         // 2) Unable to open the required file.
01107         // 3) Unable to resize the file to be large enough.
01108         // 4) Establishing the mapping failed.
01109         // 5) The mapping succeeded, but the size was wrong and we were unable to map when
01110         //    we tried again.
01111         // 6) The incorrect version of the cache was detected.
01112         // 7) The file could be created, but posix_fallocate failed to commit it fully to disk.
01113         // In any of these cases, attempt to fallback to the
01114         // better-supported anonymous private page style of mmap. This memory won't
01115         // be shared, but our code will still work the same.
01116         // NOTE: We never use the on-disk representation independently of the
01117         // shared memory. If we don't get shared memory the disk info is ignored,
01118         // if we do get shared memory we never look at disk again.
01119         if (mapAddress == MAP_FAILED) {
01120             kWarning(ksdcArea()) << "Failed to establish shared memory mapping, will fallback"
01121                           << "to private memory -- memory usage will increase";
01122 
01123             mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
01124         }
01125 
01126         // Well now we're really hosed. We can still work, but we can't even cache
01127         // data.
01128         if (mapAddress == MAP_FAILED) {
01129             kError(ksdcArea()) << "Unable to allocate shared memory segment for shared data cache"
01130                         << cacheName << "of size" << cacheSize;
01131             return;
01132         }
01133 
01134         m_mapSize = size;
01135 
01136         // We never actually construct shm, but we assign it the same address as the
01137         // shared memory we just mapped, so effectively shm is now a SharedMemory that
01138         // happens to be located at mapAddress.
01139         shm = reinterpret_cast<SharedMemory *>(mapAddress);
01140 
01141         // If we were first to create this memory map, all data will be 0.
01142         // Therefore if ready == 0 we're not initialized.  A fully initialized
01143         // header will have ready == 2.  Why?
01144         // Because 0 means "safe to initialize"
01145         //         1 means "in progress of initing"
01146         //         2 means "ready"
01147         uint usecSleepTime = 8; // Start by sleeping for 8 microseconds
01148         while (shm->ready != 2) {
01149             if (KDE_ISUNLIKELY(usecSleepTime >= (1 << 21))) {
01150                 // Didn't acquire within ~8 seconds?  Assume an issue exists
01151                 kError(ksdcArea()) << "Unable to acquire shared lock, is the cache corrupt?";
01152 
01153                 file.remove(); // Unlink the cache in case it's corrupt.
01154                 detachFromSharedMemory();
01155                 return; // Fallback to QCache (later)
01156             }
01157 
01158             if (shm->ready.testAndSetAcquire(0, 1)) {
01159                 if (!shm->performInitialSetup(cacheSize, pageSize)) {
01160                     kError(ksdcArea()) << "Unable to perform initial setup, this system probably "
01161                                    "does not really support process-shared pthreads or "
01162                                    "semaphores, even though it claims otherwise.";
01163 
01164                     file.remove();
01165                     detachFromSharedMemory();
01166                     return;
01167                 }
01168             }
01169             else {
01170                 usleep(usecSleepTime); // spin
01171 
01172                 // Exponential fallback as in Ethernet and similar collision resolution methods
01173                 usecSleepTime *= 2;
01174             }
01175         }
01176 
01177         m_expectedType = shm->shmLock.type;
01178         m_lock = QSharedPointer<KSDCLock>(createLockFromId(m_expectedType, shm->shmLock));
01179         bool isProcessSharingSupported = false;
01180 
01181         if (!m_lock->initialize(isProcessSharingSupported)) {
01182             kError(ksdcArea()) << "Unable to setup shared cache lock, although it worked when created.";
01183             detachFromSharedMemory();
01184         }
01185     }
01186 
01187     // Called whenever the cache is apparently corrupt (for instance, a timeout trying to
01188     // lock the cache). In this situation it is safer just to destroy it all and try again.
01189     void recoverCorruptedCache()
01190     {
01191         KSharedDataCache::deleteCache(m_cacheName);
01192 
01193         detachFromSharedMemory();
01194 
01195         // Do this even if we weren't previously cached -- it might work now.
01196         mapSharedMemory();
01197     }
01198 
01199     // This should be called for any memory access to shared memory. This
01200     // function will verify that the bytes [base, base+accessLength) are
01201     // actually mapped to d->shm. The cache itself may have incorrect cache
01202     // page sizes, incorrect cache size, etc. so this function should be called
01203     // despite the cache data indicating it should be safe.
01204     //
01205     // If the access is /not/ safe then a KSDCCorrupted exception will be
01206     // thrown, so be ready to catch that.
01207     void verifyProposedMemoryAccess(const void *base, unsigned accessLength) const
01208     {
01209         quintptr startOfAccess = reinterpret_cast<quintptr>(base);
01210         quintptr startOfShm = reinterpret_cast<quintptr>(shm);
01211 
01212         if (KDE_ISUNLIKELY(startOfAccess < startOfShm)) {
01213             throw KSDCCorrupted();
01214         }
01215 
01216         quintptr endOfShm = startOfShm + m_mapSize;
01217         quintptr endOfAccess = startOfAccess + accessLength;
01218 
01219         // Check for unsigned integer wraparound, and then
01220         // bounds access
01221         if (KDE_ISUNLIKELY((endOfShm < startOfShm) ||
01222                     (endOfAccess < startOfAccess) ||
01223                     (endOfAccess > endOfShm)))
01224         {
01225             throw KSDCCorrupted();
01226         }
01227     }
01228 
01229     bool lock() const
01230     {
01231         if (KDE_ISLIKELY(shm && shm->shmLock.type == m_expectedType)) {
01232             return m_lock->lock();
01233         }
01234 
01235         // No shm or wrong type --> corrupt!
01236         throw KSDCCorrupted();
01237     }
01238 
01239     void unlock() const
01240     {
01241         m_lock->unlock();
01242     }
01243 
01244     class CacheLocker
01245     {
01246         mutable Private * d;
01247 
01248         bool cautiousLock()
01249         {
01250             int lockCount = 0;
01251 
01252             // Locking can fail due to a timeout. If it happens too often even though
01253             // we're taking corrective action assume there's some disastrous problem
01254             // and give up.
01255             while (!d->lock()) {
01256                 d->recoverCorruptedCache();
01257 
01258                 if (!d->shm) {
01259                     kWarning(ksdcArea()) << "Lost the connection to shared memory for cache"
01260                                   << d->m_cacheName;
01261                     return false;
01262                 }
01263 
01264                 if (lockCount++ > 4) {
01265                     kError(ksdcArea()) << "There is a very serious problem with the KDE data cache"
01266                                 << d->m_cacheName << "giving up trying to access cache.";
01267                     d->detachFromSharedMemory();
01268                     return false;
01269                 }
01270             }
01271 
01272             return true;
01273         }
01274 
01275         public:
01276         CacheLocker(const Private *_d) : d(const_cast<Private *>(_d))
01277         {
01278             if (KDE_ISUNLIKELY(!d || !d->shm || !cautiousLock())) {
01279                 return;
01280             }
01281 
01282             uint testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize());
01283 
01284             // A while loop? Indeed, think what happens if this happens
01285             // twice -- hard to debug race conditions.
01286             while (testSize > d->m_mapSize) {
01287                 kDebug(ksdcArea()) << "Someone enlarged the cache on us,"
01288                             << "attempting to match new configuration.";
01289 
01290                 // Protect against two threads accessing this same KSDC
01291                 // from trying to execute the following remapping at the
01292                 // same time.
01293                 QMutexLocker d_locker(&d->m_threadLock);
01294                 if (testSize == d->m_mapSize) {
01295                     break; // Bail if the other thread already solved.
01296                 }
01297 
01298                 // Linux supports mremap, but it's not portable. So,
01299                 // drop the map and (try to) re-establish.
01300                 d->unlock();
01301 
01302 #ifdef KSDC_MSYNC_SUPPORTED
01303                 ::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC);
01304 #endif
01305                 ::munmap(d->shm, d->m_mapSize);
01306                 d->m_mapSize = 0;
01307                 d->shm = 0;
01308 
01309                 QFile f(d->m_cacheName);
01310                 if (!f.open(QFile::ReadWrite)) {
01311                     kError(ksdcArea()) << "Unable to re-open cache, unfortunately"
01312                                 << "the connection had to be dropped for"
01313                                 << "crash safety -- things will be much"
01314                                 << "slower now.";
01315                     return;
01316                 }
01317 
01318                 void *newMap = ::mmap(0, testSize, PROT_READ | PROT_WRITE,
01319                                       MAP_SHARED, f.handle(), 0);
01320                 if (newMap == MAP_FAILED) {
01321                     kError(ksdcArea()) << "Unopen to re-map the cache into memory"
01322                                 << "things will be much slower now";
01323                     return;
01324                 }
01325 
01326                 d->shm = reinterpret_cast<SharedMemory *>(newMap);
01327                 d->m_mapSize = testSize;
01328 
01329                 if (!cautiousLock()) {
01330                     return;
01331                 }
01332 
01333                 testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize());
01334             }
01335         }
01336 
01337         ~CacheLocker()
01338         {
01339             if (d && d->shm) {
01340                 d->unlock();
01341             }
01342         }
01343 
01344         bool failed() const
01345         {
01346             return !d || d->shm == 0;
01347         }
01348     };
01349 
01350     QString m_cacheName;
01351     QMutex m_threadLock;
01352     SharedMemory *shm;
01353     QSharedPointer<KSDCLock> m_lock;
01354     uint m_mapSize;
01355     uint m_defaultCacheSize;
01356     uint m_expectedItemSize;
01357     SharedLockId m_expectedType;
01358 };
01359 
01360 // Must be called while the lock is already held!
01361 void SharedMemory::removeEntry(uint index)
01362 {
01363     if (index >= indexTableSize() || cacheAvail > pageTableSize()) {
01364         throw KSDCCorrupted();
01365     }
01366 
01367     PageTableEntry *pageTableEntries = pageTable();
01368     IndexTableEntry *entriesIndex = indexTable();
01369 
01370     // Update page table first
01371     pageID firstPage = entriesIndex[index].firstPage;
01372     if (firstPage < 0 || static_cast<quint32>(firstPage) >= pageTableSize()) {
01373         kDebug(ksdcArea()) << "Trying to remove an entry which is already invalid. This "
01374                     << "cache is likely corrupt.";
01375         throw KSDCCorrupted();
01376     }
01377 
01378     if (index != static_cast<uint>(pageTableEntries[firstPage].index)) {
01379         kError(ksdcArea()) << "Removing entry" << index << "but the matching data"
01380                     << "doesn't link back -- cache is corrupt, clearing.";
01381         throw KSDCCorrupted();
01382     }
01383 
01384     uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize());
01385     uint savedCacheSize = cacheAvail;
01386     for (uint i = firstPage; i < pageTableSize() &&
01387         (uint) pageTableEntries[i].index == index; ++i)
01388     {
01389         pageTableEntries[i].index = -1;
01390         cacheAvail++;
01391     }
01392 
01393     if ((cacheAvail - savedCacheSize) != entriesToRemove) {
01394         kError(ksdcArea()) << "We somehow did not remove" << entriesToRemove
01395                     << "when removing entry" << index << ", instead we removed"
01396                     << (cacheAvail - savedCacheSize);
01397         throw KSDCCorrupted();
01398     }
01399 
01400     // For debugging
01401 #ifdef NDEBUG
01402     void *const startOfData = page(firstPage);
01403     if (startOfData) {
01404         QByteArray str((const char *) startOfData);
01405         str.prepend(" REMOVED: ");
01406         str.prepend(QByteArray::number(index));
01407         str.prepend("ENTRY ");
01408 
01409         ::memcpy(startOfData, str.constData(), str.size() + 1);
01410     }
01411 #endif
01412 
01413     // Update the index
01414     entriesIndex[index].fileNameHash = 0;
01415     entriesIndex[index].totalItemSize = 0;
01416     entriesIndex[index].useCount = 0;
01417     entriesIndex[index].lastUsedTime = 0;
01418     entriesIndex[index].addTime = 0;
01419     entriesIndex[index].firstPage = -1;
01420 }
01421 
01422 KSharedDataCache::KSharedDataCache(const QString &cacheName,
01423                                    unsigned defaultCacheSize,
01424                                    unsigned expectedItemSize)
01425   : d(0)
01426 {
01427     try {
01428         d = new Private(cacheName, defaultCacheSize, expectedItemSize);
01429     }
01430     catch(KSDCCorrupted) {
01431         KSharedDataCache::deleteCache(cacheName);
01432 
01433         // Try only once more
01434         try {
01435             d = new Private(cacheName, defaultCacheSize, expectedItemSize);
01436         }
01437         catch(KSDCCorrupted) {
01438             kError(ksdcArea())
01439                 << "Even a brand-new cache starts off corrupted, something is"
01440                 << "seriously wrong. :-(";
01441             d = 0; // Just in case
01442         }
01443     }
01444 }
01445 
01446 KSharedDataCache::~KSharedDataCache()
01447 {
01448     // Note that there is no other actions required to separate from the
01449     // shared memory segment, simply unmapping is enough. This makes things
01450     // *much* easier so I'd recommend maintaining this ideal.
01451     if (!d) {
01452         return;
01453     }
01454 
01455     if (d->shm) {
01456 #ifdef KSDC_MSYNC_SUPPORTED
01457         ::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC);
01458 #endif
01459         ::munmap(d->shm, d->m_mapSize);
01460     }
01461 
01462     // Do not delete d->shm, it was never constructed, it's just an alias.
01463     d->shm = 0;
01464 
01465     delete d;
01466 }
01467 
01468 bool KSharedDataCache::insert(const QString &key, const QByteArray &data)
01469 {
01470     try {
01471         Private::CacheLocker lock(d);
01472         if (lock.failed()) {
01473             return false;
01474         }
01475 
01476         QByteArray encodedKey = key.toUtf8();
01477         uint keyHash = generateHash(encodedKey);
01478         uint position = keyHash % d->shm->indexTableSize();
01479 
01480         // See if we're overwriting an existing entry.
01481         IndexTableEntry *indices = d->shm->indexTable();
01482 
01483         // In order to avoid the issue of a very long-lived cache having items
01484         // with a use count of 1 near-permanently, we attempt to artifically
01485         // reduce the use count of long-lived items when there is high load on
01486         // the cache. We do this randomly, with a weighting that makes the event
01487         // impossible if load < 0.5, and guaranteed if load >= 0.96.
01488         const static double startCullPoint = 0.5l;
01489         const static double mustCullPoint = 0.96l;
01490 
01491         // cacheAvail is in pages, cacheSize is in bytes.
01492         double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize()
01493                                   / d->shm->cacheSize);
01494         bool cullCollisions = false;
01495 
01496         if (KDE_ISUNLIKELY(loadFactor >= mustCullPoint)) {
01497             cullCollisions = true;
01498         }
01499         else if (loadFactor > startCullPoint) {
01500             const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint);
01501             if (KRandom::random() >= tripWireValue) {
01502                 cullCollisions = true;
01503             }
01504         }
01505 
01506         // In case of collisions in the index table (i.e. identical positions), use
01507         // quadratic chaining to attempt to find an empty slot. The equation we use
01508         // is:
01509         // position = (hash + (i + i*i) / 2) % size, where i is the probe number.
01510         uint probeNumber = 1;
01511         while (indices[position].useCount > 0 && probeNumber < MAX_PROBE_COUNT) {
01512             // If we actually stumbled upon an old version of the key we are
01513             // overwriting, then use that position, do not skip over it.
01514 
01515             if (KDE_ISUNLIKELY(indices[position].fileNameHash == keyHash)) {
01516                 break;
01517             }
01518 
01519             // If we are "culling" old entries, see if this one is old and if so
01520             // reduce its use count. If it reduces to zero then eliminate it and
01521             // use its old spot.
01522 
01523             if (cullCollisions && (::time(0) - indices[position].lastUsedTime) > 60) {
01524                 indices[position].useCount >>= 1;
01525                 if (indices[position].useCount == 0) {
01526                     kDebug(ksdcArea()) << "Overwriting existing old cached entry due to collision.";
01527                     d->shm->removeEntry(position); // Remove it first
01528 
01529                     break;
01530                 }
01531             }
01532 
01533             position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2)
01534                        % d->shm->indexTableSize();
01535             probeNumber++;
01536         }
01537 
01538         if (indices[position].useCount > 0 && indices[position].firstPage >= 0) {
01539             kDebug(ksdcArea()) << "Overwriting existing cached entry due to collision.";
01540             d->shm->removeEntry(position); // Remove it first
01541         }
01542 
01543         // Data will be stored as fileNamefoo\0PNGimagedata.....
01544         // So total size required is the length of the encoded file name + 1
01545         // for the trailing null, and then the length of the image data.
01546         uint fileNameLength = 1 + encodedKey.length();
01547         uint requiredSize = fileNameLength + data.size();
01548         uint pagesNeeded = intCeil(requiredSize, d->shm->cachePageSize());
01549         uint firstPage = (uint) -1;
01550 
01551         if (pagesNeeded >= d->shm->pageTableSize()) {
01552             kWarning(ksdcArea()) << key << "is too large to be cached.";
01553             return false;
01554         }
01555 
01556         // If the cache has no room, or the fragmentation is too great to find
01557         // the required number of consecutive free pages, take action.
01558         if (pagesNeeded > d->shm->cacheAvail ||
01559            (firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize())
01560         {
01561             // If we have enough free space just defragment
01562             uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2);
01563 
01564             if (d->shm->cacheAvail > freePagesDesired) {
01565                 // TODO: How the hell long does this actually take on real
01566                 // caches?
01567                 d->shm->defragment();
01568                 firstPage = d->shm->findEmptyPages(pagesNeeded);
01569             }
01570             else {
01571                 // If we already have free pages we don't want to remove a ton
01572                 // extra. However we can't rely on the return value of
01573                 // removeUsedPages giving us a good location since we're not
01574                 // passing in the actual number of pages that we need.
01575                 d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize())
01576                                         - d->shm->cacheAvail);
01577                 firstPage = d->shm->findEmptyPages(pagesNeeded);
01578             }
01579 
01580             if (firstPage >= d->shm->pageTableSize() ||
01581                d->shm->cacheAvail < pagesNeeded)
01582             {
01583                 kError(ksdcArea()) << "Unable to free up memory for" << key;
01584                 return false;
01585             }
01586         }
01587 
01588         // Update page table
01589         PageTableEntry *table = d->shm->pageTable();
01590         for (uint i = 0; i < pagesNeeded; ++i) {
01591             table[firstPage + i].index = position;
01592         }
01593 
01594         // Update index
01595         indices[position].fileNameHash = keyHash;
01596         indices[position].totalItemSize = requiredSize;
01597         indices[position].useCount = 1;
01598         indices[position].addTime = ::time(0);
01599         indices[position].lastUsedTime = indices[position].addTime;
01600         indices[position].firstPage = firstPage;
01601 
01602         // Update cache
01603         d->shm->cacheAvail -= pagesNeeded;
01604 
01605         // Actually move the data in place
01606         void *dataPage = d->shm->page(firstPage);
01607         if (KDE_ISUNLIKELY(!dataPage)) {
01608             throw KSDCCorrupted();
01609         }
01610 
01611         // Verify it will all fit
01612         d->verifyProposedMemoryAccess(dataPage, requiredSize);
01613 
01614         // Cast for byte-sized pointer arithmetic
01615         uchar *startOfPageData = reinterpret_cast<uchar *>(dataPage);
01616         ::memcpy(startOfPageData, encodedKey.constData(), fileNameLength);
01617         ::memcpy(startOfPageData + fileNameLength, data.constData(), data.size());
01618 
01619         return true;
01620     }
01621     catch(KSDCCorrupted) {
01622         d->recoverCorruptedCache();
01623         return false;
01624     }
01625 }
01626 
01627 bool KSharedDataCache::find(const QString &key, QByteArray *destination) const
01628 {
01629     try {
01630         Private::CacheLocker lock(d);
01631         if (lock.failed()) {
01632             return false;
01633         }
01634 
01635         // Search in the index for our data, hashed by key;
01636         QByteArray encodedKey = key.toUtf8();
01637         qint32 entry = d->shm->findNamedEntry(encodedKey);
01638 
01639         if (entry >= 0) {
01640             const IndexTableEntry *header = &d->shm->indexTable()[entry];
01641             const void *resultPage = d->shm->page(header->firstPage);
01642             if (KDE_ISUNLIKELY(!resultPage)) {
01643                 throw KSDCCorrupted();
01644             }
01645 
01646             d->verifyProposedMemoryAccess(resultPage, header->totalItemSize);
01647 
01648             header->useCount++;
01649             header->lastUsedTime = ::time(0);
01650 
01651             // Our item is the key followed immediately by the data, so skip
01652             // past the key.
01653             const char *cacheData = reinterpret_cast<const char *>(resultPage);
01654             cacheData += encodedKey.size();
01655             cacheData++; // Skip trailing null -- now we're pointing to start of data
01656 
01657             if (destination) {
01658                 *destination = QByteArray(cacheData, header->totalItemSize - encodedKey.size() - 1);
01659             }
01660 
01661             return true;
01662         }
01663     }
01664     catch(KSDCCorrupted) {
01665         d->recoverCorruptedCache();
01666     }
01667 
01668     return false;
01669 }
01670 
01671 void KSharedDataCache::clear()
01672 {
01673     try {
01674         Private::CacheLocker lock(d);
01675 
01676         if(!lock.failed()) {
01677             d->shm->clear();
01678         }
01679     }
01680     catch(KSDCCorrupted) {
01681         d->recoverCorruptedCache();
01682     }
01683 }
01684 
01685 bool KSharedDataCache::contains(const QString &key) const
01686 {
01687     try {
01688         Private::CacheLocker lock(d);
01689         if (lock.failed()) {
01690             return false;
01691         }
01692 
01693         return d->shm->findNamedEntry(key.toUtf8()) >= 0;
01694     }
01695     catch(KSDCCorrupted) {
01696         d->recoverCorruptedCache();
01697         return false;
01698     }
01699 }
01700 
01701 void KSharedDataCache::deleteCache(const QString &cacheName)
01702 {
01703     QString cachePath = KGlobal::dirs()->locateLocal("cache", cacheName + QLatin1String(".kcache"));
01704 
01705     // Note that it is important to simply unlink the file, and not truncate it
01706     // smaller first to avoid SIGBUS errors and similar with shared memory
01707     // attached to the underlying inode.
01708     kDebug(ksdcArea()) << "Removing cache at" << cachePath;
01709     QFile::remove(cachePath);
01710 }
01711 
01712 unsigned KSharedDataCache::totalSize() const
01713 {
01714     try {
01715         Private::CacheLocker lock(d);
01716         if (lock.failed()) {
01717             return 0u;
01718         }
01719 
01720         return d->shm->cacheSize;
01721     }
01722     catch(KSDCCorrupted) {
01723         d->recoverCorruptedCache();
01724         return 0u;
01725     }
01726 }
01727 
01728 unsigned KSharedDataCache::freeSize() const
01729 {
01730     try {
01731         Private::CacheLocker lock(d);
01732         if (lock.failed()) {
01733             return 0u;
01734         }
01735 
01736         return d->shm->cacheAvail * d->shm->cachePageSize();
01737     }
01738     catch(KSDCCorrupted) {
01739         d->recoverCorruptedCache();
01740         return 0u;
01741     }
01742 }
01743 
01744 KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const
01745 {
01746     if (d && d->shm) {
01747         return static_cast<EvictionPolicy>(d->shm->evictionPolicy.fetchAndAddAcquire(0));
01748     }
01749 
01750     return NoEvictionPreference;
01751 }
01752 
01753 void KSharedDataCache::setEvictionPolicy(EvictionPolicy newPolicy)
01754 {
01755     if (d && d->shm) {
01756         d->shm->evictionPolicy.fetchAndStoreRelease(static_cast<int>(newPolicy));
01757     }
01758 }
01759 
01760 unsigned KSharedDataCache::timestamp() const
01761 {
01762     if (d && d->shm) {
01763         return static_cast<unsigned>(d->shm->cacheTimestamp.fetchAndAddAcquire(0));
01764     }
01765 
01766     return 0;
01767 }
01768 
01769 void KSharedDataCache::setTimestamp(unsigned newTimestamp)
01770 {
01771     if (d && d->shm) {
01772         d->shm->cacheTimestamp.fetchAndStoreRelease(static_cast<int>(newTimestamp));
01773     }
01774 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:28:12 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDECore

Skip menu "KDECore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.9.5 API Reference

Skip menu "kdelibs-4.9.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal