KDECore
kshareddatacache_p.h
Go to the documentation of this file.
00001 /* 00002 * This file is part of the KDE project. 00003 * Copyright © 2010 Michael Pyne <mpyne@kde.org> 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Library General Public 00007 * License version 2 as published by the Free Software Foundation. 00008 * 00009 * This library is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 * Library General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU Library General Public License 00015 * along with this library; see the file COPYING.LIB. If not, write to 00016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 * Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #ifndef KSHAREDDATACACHE_P_H 00021 #define KSHAREDDATACACHE_P_H 00022 00023 #include <config.h> // HAVE_SYS_MMAN_H 00024 00025 #include <QtCore/QSharedPointer> 00026 #include <QtCore/QBasicAtomicInt> 00027 00028 #include <unistd.h> // Check for sched_yield 00029 #include <sched.h> // sched_yield 00030 #include <errno.h> 00031 #include <fcntl.h> 00032 #include <time.h> 00033 00034 #include <kdebug.h> 00035 00036 // Our debug area, disabled by default 00037 int ksdcArea(); 00038 00039 // Mac OS X, for all its POSIX compliance, does not support timeouts on its 00040 // mutexes, which is kind of a disaster for cross-process support. However 00041 // synchronization primitives still work, they just might hang if the cache is 00042 // corrupted, so keep going. 00043 #if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L)) 00044 #define KSDC_TIMEOUTS_SUPPORTED 1 00045 #endif 00046 00047 #if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED) 00048 #warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt" 00049 #endif 00050 00051 #if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__) 00052 #include <pthread.h> 00053 #define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1 00054 #endif 00055 00056 #if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L)) 00057 #include <semaphore.h> 00058 #define KSDC_SEMAPHORES_SUPPORTED 1 00059 #endif 00060 00061 #if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) 00062 #warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless." 00063 #endif 00064 00065 #if defined(_POSIX_MAPPED_FILES) && ((_POSIX_MAPPED_FILES == 0) || (_POSIX_MAPPED_FILES >= 200112L)) 00066 #define KSDC_MAPPED_FILES_SUPPORTED 1 00067 #endif 00068 00069 #if defined(_POSIX_SYNCHRONIZED_IO) && ((_POSIX_SYNCHRONIZED_IO == 0) || (_POSIX_SYNCHRONIZED_IO >= 200112L)) 00070 #define KSDC_SYNCHRONIZED_IO_SUPPORTED 1 00071 #endif 00072 00073 // msync(2) requires both MAPPED_FILES and SYNCHRONIZED_IO POSIX options 00074 #if defined(KSDC_MAPPED_FILES_SUPPORTED) && defined(KSDC_SYNCHRONIZED_IO_SUPPORTED) 00075 #define KSDC_MSYNC_SUPPORTED 00076 #endif 00077 00078 // posix_fallocate is used to ensure that the file used for the cache is 00079 // actually fully committed to disk before attempting to use the file. 00080 #if defined(_POSIX_ADVISORY_INFO) && ((_POSIX_ADVISORY_INFO == 0) || (_POSIX_ADVISORY_INFO >= 200112L)) 00081 #define KSDC_POSIX_FALLOCATE_SUPPORTED 1 00082 #endif 00083 00084 // BSD/Mac OS X compat 00085 #ifdef HAVE_SYS_MMAN_H 00086 #include <sys/mman.h> 00087 #endif 00088 #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) 00089 #define MAP_ANONYMOUS MAP_ANON 00090 #endif 00091 00097 class KSDCLock { 00098 public: 00099 virtual ~KSDCLock() 00100 { 00101 } 00102 00103 // Return value indicates if the mutex was properly initialized (including 00104 // threads-only as a fallback). 00105 virtual bool initialize(bool &processSharingSupported) 00106 { 00107 processSharingSupported = false; 00108 return false; 00109 } 00110 00111 virtual bool lock() 00112 { 00113 return false; 00114 } 00115 00116 virtual void unlock() 00117 { 00118 } 00119 }; 00120 00126 class simpleSpinLock : public KSDCLock 00127 { 00128 public: 00129 simpleSpinLock(QBasicAtomicInt &spinlock) 00130 : m_spinlock(spinlock) 00131 { 00132 } 00133 00134 virtual bool initialize(bool &processSharingSupported) 00135 { 00136 // Clear the spinlock 00137 m_spinlock = 0; 00138 processSharingSupported = true; 00139 return true; 00140 } 00141 00142 virtual bool lock() 00143 { 00144 // Spin a few times attempting to gain the lock, as upper-level code won't 00145 // attempt again without assuming the cache is corrupt. 00146 for (unsigned i = 50; i > 0; --i) { 00147 if (m_spinlock.testAndSetAcquire(0, 1)) { 00148 return true; 00149 } 00150 00151 // Don't steal the processor and starve the thread we're waiting 00152 // on. 00153 loopSpinPause(); 00154 } 00155 00156 return false; 00157 } 00158 00159 virtual void unlock() 00160 { 00161 m_spinlock.testAndSetRelease(1, 0); 00162 } 00163 00164 private: 00165 #ifdef Q_CC_GNU 00166 __attribute__((always_inline, gnu_inline, artificial)) 00167 #endif 00168 static inline void loopSpinPause() 00169 { 00170 // TODO: Spinning might be better in multi-core systems... but that means 00171 // figuring how to find numbers of CPUs in a cross-platform way. 00172 #ifdef _POSIX_PRIORITY_SCHEDULING 00173 sched_yield(); 00174 #else 00175 // Sleep for shortest possible time (nanosleep should round-up). 00176 struct timespec wait_time = { 0 /* sec */, 100 /* ns */ }; 00177 ::nanosleep(&wait_time, static_cast<struct timespec*>(0)); 00178 #endif 00179 } 00180 00181 QBasicAtomicInt &m_spinlock; 00182 }; 00183 00184 #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED 00185 class pthreadLock : public KSDCLock 00186 { 00187 public: 00188 pthreadLock(pthread_mutex_t &mutex) 00189 : m_mutex(mutex) 00190 { 00191 } 00192 00193 virtual bool initialize(bool &processSharingSupported) 00194 { 00195 // Setup process-sharing. 00196 pthread_mutexattr_t mutexAttr; 00197 processSharingSupported = false; 00198 00199 // Initialize attributes, enable process-shared primitives, and setup 00200 // the mutex. 00201 if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(&mutexAttr) == 0) { 00202 if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && 00203 pthread_mutex_init(&m_mutex, &mutexAttr) == 0) 00204 { 00205 processSharingSupported = true; 00206 } 00207 pthread_mutexattr_destroy(&mutexAttr); 00208 } 00209 00210 // Attempt to setup for thread-only synchronization. 00211 if (!processSharingSupported && pthread_mutex_init(&m_mutex, NULL) != 0) { 00212 return false; 00213 } 00214 00215 return true; 00216 } 00217 00218 virtual bool lock() 00219 { 00220 return pthread_mutex_lock(&m_mutex) == 0; 00221 } 00222 00223 virtual void unlock() 00224 { 00225 pthread_mutex_unlock(&m_mutex); 00226 } 00227 00228 protected: 00229 pthread_mutex_t &m_mutex; 00230 }; 00231 #endif 00232 00233 #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) 00234 class pthreadTimedLock : public pthreadLock 00235 { 00236 public: 00237 pthreadTimedLock(pthread_mutex_t &mutex) 00238 : pthreadLock(mutex) 00239 { 00240 } 00241 00242 virtual bool lock() 00243 { 00244 struct timespec timeout; 00245 00246 // Long timeout, but if we fail to meet this timeout it's probably a cache 00247 // corruption (and if we take 8 seconds then it should be much much quicker 00248 // the next time anyways since we'd be paged back in from disk) 00249 timeout.tv_sec = 10 + ::time(NULL); // Absolute time, so 10 seconds from now 00250 timeout.tv_nsec = 0; 00251 00252 return pthread_mutex_timedlock(&m_mutex, &timeout) == 0; 00253 } 00254 }; 00255 #endif 00256 00257 #ifdef KSDC_SEMAPHORES_SUPPORTED 00258 class semaphoreLock : public KSDCLock 00259 { 00260 public: 00261 semaphoreLock(sem_t &semaphore) 00262 : m_semaphore(semaphore) 00263 { 00264 } 00265 00266 virtual bool initialize(bool &processSharingSupported) 00267 { 00268 processSharingSupported = false; 00269 if (::sysconf(_SC_SEMAPHORES) < 200112L) { 00270 return false; 00271 } 00272 00273 // sem_init sets up process-sharing for us. 00274 if (sem_init(&m_semaphore, 1, 1) == 0) { 00275 processSharingSupported = true; 00276 } 00277 // If not successful try falling back to thread-shared. 00278 else if (sem_init(&m_semaphore, 0, 1) != 0) { 00279 return false; 00280 } 00281 00282 return true; 00283 } 00284 00285 virtual bool lock() 00286 { 00287 return sem_wait(&m_semaphore) == 0; 00288 } 00289 00290 virtual void unlock() 00291 { 00292 sem_post(&m_semaphore); 00293 } 00294 00295 protected: 00296 sem_t &m_semaphore; 00297 }; 00298 #endif 00299 00300 #if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) 00301 class semaphoreTimedLock : public semaphoreLock 00302 { 00303 public: 00304 semaphoreTimedLock(sem_t &semaphore) 00305 : semaphoreLock(semaphore) 00306 { 00307 } 00308 00309 virtual bool lock() 00310 { 00311 struct timespec timeout; 00312 00313 // Long timeout, but if we fail to meet this timeout it's probably a cache 00314 // corruption (and if we take 8 seconds then it should be much much quicker 00315 // the next time anyways since we'd be paged back in from disk) 00316 timeout.tv_sec = 10 + ::time(NULL); // Absolute time, so 10 seconds from now 00317 timeout.tv_nsec = 0; 00318 00319 return sem_timedwait(&m_semaphore, &timeout) == 0; 00320 } 00321 }; 00322 #endif 00323 00324 // This enum controls the type of the locking used for the cache to allow 00325 // for as much portability as possible. This value will be stored in the 00326 // cache and used by multiple processes, therefore you should consider this 00327 // a versioned field, do not re-arrange. 00328 enum SharedLockId { 00329 LOCKTYPE_INVALID = 0, 00330 LOCKTYPE_MUTEX = 1, // pthread_mutex 00331 LOCKTYPE_SEMAPHORE = 2, // sem_t 00332 LOCKTYPE_SPINLOCK = 3 // atomic int in shared memory 00333 }; 00334 00335 // This type is a union of all possible lock types, with a SharedLockId used 00336 // to choose which one is actually in use. 00337 struct SharedLock 00338 { 00339 union 00340 { 00341 #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) 00342 pthread_mutex_t mutex; 00343 #endif 00344 #if defined(KSDC_SEMAPHORES_SUPPORTED) 00345 sem_t semaphore; 00346 #endif 00347 QBasicAtomicInt spinlock; 00348 00349 // It would be highly unfortunate if a simple glibc upgrade or kernel 00350 // addition caused this structure to change size when an existing 00351 // lock was thought present, so reserve enough size to cover any 00352 // reasonable locking structure 00353 char unused[64]; 00354 }; 00355 00356 SharedLockId type; 00357 }; 00358 00364 static SharedLockId findBestSharedLock() 00365 { 00366 // We would prefer a process-shared capability that also supports 00367 // timeouts. Failing that, process-shared is preferred over timeout 00368 // support. Failing that we'll go thread-local 00369 bool timeoutsSupported = false; 00370 bool pthreadsProcessShared = false; 00371 bool semaphoresProcessShared = false; 00372 00373 #ifdef KSDC_TIMEOUTS_SUPPORTED 00374 timeoutsSupported = ::sysconf(_SC_TIMEOUTS) >= 200112L; 00375 #endif 00376 00377 // Now that we've queried timeouts, try actually creating real locks and 00378 // seeing if there's issues with that. 00379 #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED 00380 { 00381 pthread_mutex_t tempMutex; 00382 QSharedPointer<KSDCLock> tempLock(0); 00383 if (timeoutsSupported) { 00384 #ifdef KSDC_TIMEOUTS_SUPPORTED 00385 tempLock = QSharedPointer<KSDCLock>(new pthreadTimedLock(tempMutex)); 00386 #endif 00387 } 00388 else { 00389 tempLock = QSharedPointer<KSDCLock>(new pthreadLock(tempMutex)); 00390 } 00391 00392 tempLock->initialize(pthreadsProcessShared); 00393 } 00394 #endif 00395 00396 // Our first choice is pthread_mutex_t for compatibility. 00397 if(timeoutsSupported && pthreadsProcessShared) { 00398 return LOCKTYPE_MUTEX; 00399 } 00400 00401 #ifdef KSDC_SEMAPHORES_SUPPORTED 00402 { 00403 sem_t tempSemaphore; 00404 QSharedPointer<KSDCLock> tempLock(0); 00405 if (timeoutsSupported) { 00406 tempLock = QSharedPointer<KSDCLock>(new semaphoreTimedLock(tempSemaphore)); 00407 } 00408 else { 00409 tempLock = QSharedPointer<KSDCLock>(new semaphoreLock(tempSemaphore)); 00410 } 00411 00412 tempLock->initialize(semaphoresProcessShared); 00413 } 00414 #endif 00415 00416 if(timeoutsSupported && semaphoresProcessShared) { 00417 return LOCKTYPE_SEMAPHORE; 00418 } 00419 else if(pthreadsProcessShared) { 00420 return LOCKTYPE_MUTEX; 00421 } 00422 else if(semaphoresProcessShared) { 00423 return LOCKTYPE_SEMAPHORE; 00424 } 00425 00426 // Fallback to a dumb-simple but possibly-CPU-wasteful solution. 00427 return LOCKTYPE_SPINLOCK; 00428 } 00429 00430 static KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock) 00431 { 00432 switch(id) { 00433 #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED 00434 case LOCKTYPE_MUTEX: 00435 #ifdef KSDC_TIMEOUTS_SUPPORTED 00436 if (::sysconf(_SC_TIMEOUTS) >= 200112L) { 00437 return new pthreadTimedLock(lock.mutex); 00438 } 00439 #endif 00440 return new pthreadLock(lock.mutex); 00441 00442 break; 00443 #endif 00444 00445 #ifdef KSDC_SEMAPHORES_SUPPORTED 00446 case LOCKTYPE_SEMAPHORE: 00447 #ifdef KSDC_TIMEOUTS_SUPPORTED 00448 if (::sysconf(_SC_SEMAPHORES) >= 200112L) { 00449 return new semaphoreTimedLock(lock.semaphore); 00450 } 00451 #endif 00452 return new semaphoreLock(lock.semaphore); 00453 00454 break; 00455 #endif 00456 00457 case LOCKTYPE_SPINLOCK: 00458 return new simpleSpinLock(lock.spinlock); 00459 break; 00460 00461 default: 00462 kError(ksdcArea()) << "Creating shell of a lock!"; 00463 return new KSDCLock; 00464 } 00465 } 00466 00467 static bool ensureFileAllocated(int fd, size_t fileSize) 00468 { 00469 #ifdef KSDC_POSIX_FALLOCATE_SUPPORTED 00470 int result; 00471 while ((result = ::posix_fallocate(fd, 0, fileSize)) == EINTR) { 00472 ; 00473 } 00474 00475 if (result < 0) { 00476 kError(ksdcArea()) << "The operating system is unable to promise" 00477 << fileSize 00478 << "bytes for mapped cache, " 00479 "abandoning the cache for crash-safety."; 00480 return false; 00481 } 00482 00483 return true; 00484 #else 00485 00486 #ifdef __GNUC__ 00487 #warning "This system does not seem to support posix_fallocate, which is needed to ensure KSharedDataCache's underlying files are fully committed to disk to avoid crashes with low disk space." 00488 #endif 00489 kWarning(ksdcArea()) << "This system misses support for posix_fallocate()" 00490 " -- ensure this partition has room for at least" 00491 << fileSize << "bytes."; 00492 00493 // TODO: It's possible to emulate the functionality, but doing so 00494 // overwrites the data in the file so we don't do this. If you were to add 00495 // this emulation, you must ensure it only happens on initial creation of a 00496 // new file and not just mapping an existing cache. 00497 00498 return true; 00499 #endif 00500 } 00501 00502 #endif /* KSHAREDDATACACHE_P_H */
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:28:12 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:28:12 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.