File indexing completed on 2024-05-19 03:56:18

0001 /*
0002  *    This file is part of the KDE project.
0003  *
0004  *    SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
0005  *    SPDX-License-Identifier: LGPL-2.0-only
0006  */
0007 
0008 #ifndef KSDCLOCK_P_H
0009 #define KSDCLOCK_P_H
0010 
0011 #include <qbasicatomic.h>
0012 
0013 #include <sched.h> // sched_yield
0014 #include <unistd.h> // Check for sched_yield
0015 
0016 // Mac OS X, for all its POSIX compliance, does not support timeouts on its
0017 // mutexes, which is kind of a disaster for cross-process support. However
0018 // synchronization primitives still work, they just might hang if the cache is
0019 // corrupted, so keep going.
0020 #if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L))
0021 #define KSDC_TIMEOUTS_SUPPORTED 1
0022 #endif
0023 
0024 #if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED)
0025 #warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt"
0026 #endif
0027 
0028 #if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__)
0029 #include <pthread.h>
0030 #define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1
0031 #endif
0032 
0033 #if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L))
0034 #include <semaphore.h>
0035 #define KSDC_SEMAPHORES_SUPPORTED 1
0036 #endif
0037 
0038 #if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
0039 #warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless."
0040 #endif
0041 
0042 /**
0043  * This class defines an interface used by KSharedDataCache::Private to offload
0044  * proper locking and unlocking depending on what the platform supports at
0045  * runtime and compile-time.
0046  */
0047 class KSDCLock
0048 {
0049 public:
0050     virtual ~KSDCLock()
0051     {
0052     }
0053 
0054     // Return value indicates if the mutex was properly initialized (including
0055     // threads-only as a fallback).
0056     virtual bool initialize(bool &processSharingSupported)
0057     {
0058         processSharingSupported = false;
0059         return false;
0060     }
0061 
0062     virtual bool lock()
0063     {
0064         return false;
0065     }
0066 
0067     virtual void unlock()
0068     {
0069     }
0070 };
0071 
0072 /**
0073  * This is a very basic lock that should work on any system where GCC atomic
0074  * intrinsics are supported. It can waste CPU so better primitives should be
0075  * used if available on the system.
0076  */
0077 class simpleSpinLock : public KSDCLock
0078 {
0079 public:
0080     simpleSpinLock(QBasicAtomicInt &spinlock)
0081         : m_spinlock(spinlock)
0082     {
0083     }
0084 
0085     bool initialize(bool &processSharingSupported) override
0086     {
0087         // Clear the spinlock
0088         m_spinlock.storeRelaxed(0);
0089         processSharingSupported = true;
0090         return true;
0091     }
0092 
0093     bool lock() override
0094     {
0095         // Spin a few times attempting to gain the lock, as upper-level code won't
0096         // attempt again without assuming the cache is corrupt.
0097         for (unsigned i = 50; i > 0; --i) {
0098             if (m_spinlock.testAndSetAcquire(0, 1)) {
0099                 return true;
0100             }
0101 
0102             // Don't steal the processor and starve the thread we're waiting
0103             // on.
0104             loopSpinPause();
0105         }
0106 
0107         return false;
0108     }
0109 
0110     void unlock() override
0111     {
0112         m_spinlock.testAndSetRelease(1, 0);
0113     }
0114 
0115 private:
0116 #ifdef Q_CC_GNU
0117     __attribute__((always_inline,
0118                    gnu_inline
0119 #if !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG)
0120                    ,
0121                    artificial
0122 #endif
0123                    ))
0124 #endif
0125     static inline void
0126     loopSpinPause()
0127     {
0128 // TODO: Spinning might be better in multi-core systems... but that means
0129 // figuring how to find numbers of CPUs in a cross-platform way.
0130 #ifdef _POSIX_PRIORITY_SCHEDULING
0131         sched_yield();
0132 #else
0133         // Sleep for shortest possible time (nanosleep should round-up).
0134         struct timespec wait_time = {0 /* sec */, 100 /* ns */};
0135         ::nanosleep(&wait_time, static_cast<struct timespec *>(0));
0136 #endif
0137     }
0138 
0139     QBasicAtomicInt &m_spinlock;
0140 };
0141 
0142 #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
0143 class pthreadLock : public KSDCLock
0144 {
0145 public:
0146     pthreadLock(pthread_mutex_t &mutex)
0147         : m_mutex(mutex)
0148     {
0149     }
0150 
0151     bool initialize(bool &processSharingSupported) override
0152     {
0153         // Setup process-sharing.
0154         pthread_mutexattr_t mutexAttr;
0155         processSharingSupported = false;
0156 
0157         // Initialize attributes, enable process-shared primitives, and setup
0158         // the mutex.
0159         if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(&mutexAttr) == 0) {
0160             if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && pthread_mutex_init(&m_mutex, &mutexAttr) == 0) {
0161                 processSharingSupported = true;
0162             }
0163             pthread_mutexattr_destroy(&mutexAttr);
0164         }
0165 
0166         // Attempt to setup for thread-only synchronization.
0167         if (!processSharingSupported && pthread_mutex_init(&m_mutex, nullptr) != 0) {
0168             return false;
0169         }
0170 
0171         return true;
0172     }
0173 
0174     bool lock() override
0175     {
0176         return pthread_mutex_lock(&m_mutex) == 0;
0177     }
0178 
0179     void unlock() override
0180     {
0181         pthread_mutex_unlock(&m_mutex);
0182     }
0183 
0184 protected:
0185     pthread_mutex_t &m_mutex;
0186 };
0187 #endif // KSDC_THREAD_PROCESS_SHARED_SUPPORTED
0188 
0189 #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
0190 class pthreadTimedLock : public pthreadLock
0191 {
0192 public:
0193     pthreadTimedLock(pthread_mutex_t &mutex)
0194         : pthreadLock(mutex)
0195     {
0196     }
0197 
0198     bool lock() override
0199     {
0200         struct timespec timeout;
0201 
0202         // Long timeout, but if we fail to meet this timeout it's probably a cache
0203         // corruption (and if we take 8 seconds then it should be much much quicker
0204         // the next time anyways since we'd be paged back in from disk)
0205         timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now
0206         timeout.tv_nsec = 0;
0207 
0208         return pthread_mutex_timedlock(&m_mutex, &timeout) == 0;
0209     }
0210 };
0211 #endif // defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
0212 
0213 #ifdef KSDC_SEMAPHORES_SUPPORTED
0214 class semaphoreLock : public KSDCLock
0215 {
0216 public:
0217     semaphoreLock(sem_t &semaphore)
0218         : m_semaphore(semaphore)
0219     {
0220     }
0221 
0222     bool initialize(bool &processSharingSupported) override
0223     {
0224         processSharingSupported = false;
0225         if (::sysconf(_SC_SEMAPHORES) < 200112L) {
0226             return false;
0227         }
0228 
0229         // sem_init sets up process-sharing for us.
0230         if (sem_init(&m_semaphore, 1, 1) == 0) {
0231             processSharingSupported = true;
0232         }
0233         // If not successful try falling back to thread-shared.
0234         else if (sem_init(&m_semaphore, 0, 1) != 0) {
0235             return false;
0236         }
0237 
0238         return true;
0239     }
0240 
0241     bool lock() override
0242     {
0243         return sem_wait(&m_semaphore) == 0;
0244     }
0245 
0246     void unlock() override
0247     {
0248         sem_post(&m_semaphore);
0249     }
0250 
0251 protected:
0252     sem_t &m_semaphore;
0253 };
0254 #endif // KSDC_SEMAPHORES_SUPPORTED
0255 
0256 #if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
0257 class semaphoreTimedLock : public semaphoreLock
0258 {
0259 public:
0260     semaphoreTimedLock(sem_t &semaphore)
0261         : semaphoreLock(semaphore)
0262     {
0263     }
0264 
0265     bool lock() override
0266     {
0267         struct timespec timeout;
0268 
0269         // Long timeout, but if we fail to meet this timeout it's probably a cache
0270         // corruption (and if we take 8 seconds then it should be much much quicker
0271         // the next time anyways since we'd be paged back in from disk)
0272         timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now
0273         timeout.tv_nsec = 0;
0274 
0275         return sem_timedwait(&m_semaphore, &timeout) == 0;
0276     }
0277 };
0278 #endif // defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
0279 
0280 // This enum controls the type of the locking used for the cache to allow
0281 // for as much portability as possible. This value will be stored in the
0282 // cache and used by multiple processes, therefore you should consider this
0283 // a versioned field, do not re-arrange.
0284 enum SharedLockId {
0285     LOCKTYPE_INVALID = 0,
0286     LOCKTYPE_MUTEX = 1, // pthread_mutex
0287     LOCKTYPE_SEMAPHORE = 2, // sem_t
0288     LOCKTYPE_SPINLOCK = 3, // atomic int in shared memory
0289 };
0290 
0291 // This type is a union of all possible lock types, with a SharedLockId used
0292 // to choose which one is actually in use.
0293 struct SharedLock {
0294     union {
0295 #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
0296         pthread_mutex_t mutex;
0297 #endif
0298 #if defined(KSDC_SEMAPHORES_SUPPORTED)
0299         sem_t semaphore;
0300 #endif
0301         QBasicAtomicInt spinlock;
0302 
0303         // It would be highly unfortunate if a simple glibc upgrade or kernel
0304         // addition caused this structure to change size when an existing
0305         // lock was thought present, so reserve enough size to cover any
0306         // reasonable locking structure
0307         char unused[64];
0308     };
0309 
0310     SharedLockId type;
0311 };
0312 
0313 /**
0314  * This is a method to determine the best lock type to use for a
0315  * shared cache, based on local support. An identifier to the appropriate
0316  * SharedLockId is returned, which can be passed to createLockFromId().
0317  */
0318 SharedLockId findBestSharedLock();
0319 
0320 KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock);
0321 
0322 #endif /* KSDCLOCK_P_H */