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

Skip menu "kdelibs-4.8.3 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