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 */