[KDE/Mac] KDE/kdelibs/kdecore/util

Michael Pyne mpyne at kde.org
Sun Oct 3 06:26:20 CEST 2010


SVN commit 1182037 by mpyne:

Overhaul the system-specific lock handling in KSharedDataCache.

This commit refactors the lock selection, creation, and acquisition/release
semantics in KSharedDataCache by introducing an interface for the cache locker
which is created once at KSharedDataCache creation and used from there on out.

Instead of checking the lock type every time the cache is meant to be locked or
unlocked, a quick check is done to ensure that the lock type in the cache
didn't somehow change (wrapped in KDE_ISLIKELY so hopefully essentially
instant). Profiling revealed no real change in performance.

The more important part is that it should be easier to actually add lock
handling for e.g. the Windows platform. Once Windows-native locks are supported
all we'd need is a mmap wrapper to get rudimentary working support on Windows
without having to use the kshareddatacache_win.cpp hack.

This has been tested using pthread_mutex_t and sem_t under normal usage, my
torture suite, libkgame's testbed, a KSharedDataCache benchmark from Manuel
Mommertz and a couple of games of KPat. ;)

Please let me know if I've dorked it up somehow for non-Linux/glibc.

CCMAIL:kde-windows at kde.org
CCMAIL:kde-freebsd at kde.org
CCMAIL:kde-mac at kde.org
CCMAIL:kde-solaris at kde.org


 M  +34 -153   kshareddatacache.cpp  
 A             kshareddatacache_p.h   [License: LGPL (v2)]


--- trunk/KDE/kdelibs/kdecore/util/kshareddatacache.cpp #1182036:1182037
@@ -18,6 +18,7 @@
  */
 
 #include "kshareddatacache.h"
+#include "kshareddatacache_p.h" // Various auxiliary support code
 
 #include <kdebug.h>
 #include <kglobal.h>
@@ -33,43 +34,10 @@
 #include <QtCore/QMutex>
 #include <QtCore/QMutexLocker>
 
-#include <pthread.h>
-#include <semaphore.h>
 #include <sys/types.h>
 #include <sys/mman.h>
 #include <stdlib.h>
-#include <unistd.h>
-#include <time.h>
 
-// Mac OS X, for all its POSIX compliance, does not support timeouts on its
-// mutexes, which is kind of a disaster for cross-process support. However
-// synchronization primitives still work, they just might hang if the cache is
-// corrupted, so keep going.
-#if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L))
-#define KSDC_TIMEOUTS_SUPPORTED 1
-#endif
-
-#if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED)
-#warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt"
-#endif
-
-#if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L))
-#define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1
-#endif
-
-#if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L))
-#define KSDC_SEMAPHORES_SUPPORTED 1
-#endif
-
-#if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
-#warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless."
-#endif
-
-// BSD/Mac OS X compat
-#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
-#define MAP_ANONYMOUS MAP_ANON
-#endif
-
 /**
  * This is the hash function used for our data to hopefully make the
  * hashing used to place the QByteArrays as efficient as possible.
@@ -245,7 +213,7 @@
      * e.g. the next version bump will be from 4 to 8, then 12, etc.
      */
     enum {
-        PIXMAP_CACHE_VERSION = 4,
+        PIXMAP_CACHE_VERSION = 8,
         MINIMUM_CACHE_SIZE = 4096
     };
 
@@ -255,25 +223,9 @@
     QAtomicInt ready; ///< DO NOT INITIALIZE
     quint8     version;
 
-    // This enum controls the type of the locking used for the cache to allow
-    // for as much portability as possible. This value will be stored in the
-    // cache and used by multiple processes, therefore you should consider this
-    // a versioned field, do not re-arrange.
-    enum {
-        LOCKTYPE_MUTEX     = 1,  // pthread_mutex
-        LOCKTYPE_SEMAPHORE = 2   // sem_t
-    };
+    // See kshareddatacache_p.h
+    SharedLock shmLock;
 
-    struct
-    {
-        union
-        {
-            mutable pthread_mutex_t mutex;
-            mutable sem_t semaphore;
-        };
-        uint type;
-    } shmLock;
-
     uint       cacheSize;
     uint       cacheAvail;
     QAtomicInt evictionPolicy;
@@ -321,7 +273,7 @@
      * 2. Any member variable you add takes up space in shared memory as well,
      * so make sure you need it.
      */
-    bool performInitialSetup(bool processShared, uint _cacheSize, uint _pageSize)
+    bool performInitialSetup(uint _cacheSize, uint _pageSize)
     {
         if (_cacheSize < MINIMUM_CACHE_SIZE) {
             kError(264) << "Internal error: Attempted to create a cache sized < "
@@ -334,56 +286,24 @@
             return false;
         }
 
-        if (processShared) {
-            bool success = false;
-
-#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
-            // Perform initialization.  We effectively hold a mini-lock right
-            // now as long as all clients cooperate...
-            pthread_mutexattr_t mutexAttr;
-
-            // Initialize attributes, enable process-shared primitives, and setup
-            // the mutex.
-            if (::sysconf(_SC_THREAD_PROCESS_SHARED) > 0 && pthread_mutexattr_init(&mutexAttr) == 0) {
-                if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 &&
-                    pthread_mutex_init(&shmLock.mutex, &mutexAttr) == 0)
-                {
-                    shmLock.type = LOCKTYPE_MUTEX;
-                    success = true;
+        shmLock.type = findBestSharedLock();
+        if (static_cast<int>(shmLock.type) == 0) {
+            kError(264) << "Unable to find an appropriate lock to guard the shared cache. "
+                        << "This *should* be essentially impossible. :(";
+            return false;
                 }
-                pthread_mutexattr_destroy(&mutexAttr);
-            }
-#endif
 
-#ifdef KSDC_SEMAPHORES_SUPPORTED
-            // Try using a semaphore if pthreads didn't work.
-            // sem_init sets up process-sharing for us.
-            if (!success && ::sysconf(_SC_SEMAPHORES) > 0 &&
-                sem_init(&shmLock.semaphore, 1, 1) == 0)
-            {
-                shmLock.type = LOCKTYPE_SEMAPHORE;
-                success = true;
-            }
-#endif
+        bool isProcessShared = false;
+        QSharedPointer<KSDCLock> tempLock(createLockFromId(shmLock.type, shmLock));
 
-            if (!success) {
-                return false; // No process sharing
-            }
-        }
-        else {
-            // Only thread-shared
-            if (pthread_mutex_init(&shmLock.mutex, 0) == 0) {
-                shmLock.type = LOCKTYPE_MUTEX;
-            }
-#ifdef KSDC_SEMAPHORES_SUPPORTED
-            // Semaphores may be supported only in a thread-local manner.
-            else if (::sysconf(_SC_SEMAPHORES) > 0 && sem_init(&shmLock.semaphore, 0, 1) == 0) {
-                shmLock.type = LOCKTYPE_SEMAPHORE;
-            }
-#endif
-            else {
+        if (!tempLock->initialize(isProcessShared)) {
+            kError(264) << "Unable to initialize the lock for the cache!";
                 return false;
             }
+
+        if (!isProcessShared) {
+            kWarning(264) << "Cache initialized, but does not support being"
+                          << "shared across processes.";
         }
 
         // These must be updated to make some of our auxiliary functions
@@ -880,11 +800,13 @@
             unsigned defaultCacheSize,
             unsigned expectedItemSize
            )
-        : shm(0)
-        , m_cacheName(name)
+        : m_cacheName(name)
+        , shm(0)
+        , m_lock(0)
         , m_mapSize(0)
         , m_defaultCacheSize(defaultCacheSize)
         , m_expectedItemSize(expectedItemSize)
+        , m_expectedType(static_cast<SharedLockId>(0))
     {
         mapSharedMemory();
     }
@@ -922,22 +844,10 @@
             return;
         }
 
-        bool systemSupportsProcessSharing = false;
-
-// Compile time check ensures the required constants from the .h are available to be
-// queried at all.
-#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
-        systemSupportsProcessSharing = ::sysconf(_SC_THREAD_PROCESS_SHARED) > 0;
-#endif
-#ifdef KSDC_SEMAPHORES_SUPPORTED
-        systemSupportsProcessSharing |= ::sysconf(_SC_SEMAPHORES) > 0;
-#endif
-
         // We establish the shared memory mapping here, only if we will have appropriate
         // mutex support (systemSupportsProcessSharing), then we:
         // Open the file and resize to some sane value if the file is too small.
-        if (systemSupportsProcessSharing &&
-            file.open(QIODevice::ReadWrite) &&
+        if (file.open(QIODevice::ReadWrite) &&
            (file.size() >= size || file.resize(size)))
         {
             // Use mmap directly instead of QFile::map since the QFile (and its
@@ -993,13 +903,11 @@
         // NOTE: We never use the on-disk representation independently of the
         // shared memory. If we don't get shared memory the disk info is ignored,
         // if we do get shared memory we never look at disk again.
-        bool usingSharedMapping = true;
         if (mapAddress == MAP_FAILED) {
             kWarning(264) << "Failed to establish shared memory mapping, will fallback"
                           << "to private memory -- memory usage will increase";
 
             mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-            usingSharedMapping = false;
         }
 
         // Well now we're really hosed. We can still work, but we can't even cache
@@ -1007,8 +915,6 @@
         if (mapAddress == MAP_FAILED) {
             kError(264) << "Unable to allocate shared memory segment for shared data cache"
                         << cacheName << "of size" << cacheSize;
-            kError(265) << "The error was";
-            perror(0);
             return;
         }
 
@@ -1038,7 +944,7 @@
             }
 
             if (shm->ready.testAndSetAcquire(0, 1)) {
-                if (!shm->performInitialSetup(usingSharedMapping, cacheSize, pageSize)) {
+                if (!shm->performInitialSetup(cacheSize, pageSize)) {
                     kError(264) << "Unable to perform initial setup, this system probably "
                                    "does not really support process-shared pthreads or "
                                    "semaphores, even though it claims otherwise.";
@@ -1056,6 +962,9 @@
             }
         }
 
+        m_expectedType = shm->shmLock.type;
+        m_lock = QSharedPointer<KSDCLock>(createLockFromId(m_expectedType, shm->shmLock));
+
         // We are "attached" if we have a valid memory mapping, whether it is
         // shared or private.
         kDebug(264) << "Cache attached to shared memory,"
@@ -1080,47 +989,17 @@
 
     bool lock() const
     {
-#ifdef KSDC_TIMEOUTS_SUPPORTED
-        struct timespec timeout;
-
-        // Long timeout, but if we fail to meet this timeout it's probably a cache
-        // corruption (and if we take 8 seconds then it should be much much quicker
-        // the next time anyways since we'd be paged back in from disk)
-        timeout.tv_sec = 10 + ::time(NULL); // Absolute time, so 10 seconds from now
-        timeout.tv_nsec = 0;
-
-        if (shm->shmLock.type == SharedMemory::LOCKTYPE_MUTEX) {
-            return pthread_mutex_timedlock(&shm->shmLock.mutex, &timeout) >= 0;
+        if (KDE_ISLIKELY(shm->shmLock.type == m_expectedType)) {
+            return m_lock->lock();
         }
-        else if (shm->shmLock.type == SharedMemory::LOCKTYPE_SEMAPHORE) {
-            return sem_timedwait(&shm->shmLock.semaphore, &timeout) == 0;
-        }
-#else
-        // Some POSIX platforms don't have full support for timeouts. On these typically
-        // there will be no timed(lock|wait), so just don't bother and accept hangs on weird
-        // platforms.
-        if (shm->shmLock.type == SharedMemory::LOCKTYPE_MUTEX) {
-            return pthread_mutex_lock(&shm->shmLock.mutex) == 0;
-        }
-        else if (shm->shmLock.type == SharedMemory::LOCKTYPE_SEMAPHORE) {
-            return sem_wait(&shm->shmLock.semaphore) == 0;
-        }
-#endif
 
-        return false; // Should be unreachable, unless the cache is damaged.
+        return false;
     }
 
     void unlock() const
     {
-        if (shm->shmLock.type == SharedMemory::LOCKTYPE_MUTEX) {
-            pthread_mutex_unlock(&shm->shmLock.mutex);
+        m_lock->unlock();
         }
-#ifdef KSDC_SEMAPHORES_SUPPORTED
-        else if (shm->shmLock.type == SharedMemory::LOCKTYPE_SEMAPHORE) {
-            sem_post(&shm->shmLock.semaphore);
-        }
-#endif
-    }
 
     class CacheLocker
     {
@@ -1235,12 +1114,14 @@
         }
     };
 
-    SharedMemory *shm;
     QString m_cacheName;
     QMutex m_threadLock;
+    SharedMemory *shm;
+    QSharedPointer<KSDCLock> m_lock;
     uint m_mapSize;
     uint m_defaultCacheSize;
     uint m_expectedItemSize;
+    SharedLockId m_expectedType;
 };
 
 // Must be called while the lock is already held!


More information about the kde-mac mailing list