1/*
2 * This file is part of the KDE project.
3 *
4 * SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
5 * SPDX-License-Identifier: LGPL-2.0-only
6 */
7
8#ifndef KSDCLOCK_P_H
9#define KSDCLOCK_P_H
10
11#include <qbasicatomic.h>
12
13#include <sched.h> // sched_yield
14#include <unistd.h> // Check for sched_yield
15
16// Mac OS X, for all its POSIX compliance, does not support timeouts on its
17// mutexes, which is kind of a disaster for cross-process support. However
18// synchronization primitives still work, they just might hang if the cache is
19// corrupted, so keep going.
20#if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L))
21#define KSDC_TIMEOUTS_SUPPORTED 1
22#endif
23
24#if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED)
25#warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt"
26#endif
27
28#if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__)
29#include <pthread.h>
30#define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1
31#endif
32
33#if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L))
34#include <semaphore.h>
35#define KSDC_SEMAPHORES_SUPPORTED 1
36#endif
37
38#if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
39#warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless."
40#endif
41
42/**
43 * This class defines an interface used by KSharedDataCache::Private to offload
44 * proper locking and unlocking depending on what the platform supports at
45 * runtime and compile-time.
46 */
47class KSDCLock
48{
49public:
50 virtual ~KSDCLock()
51 {
52 }
53
54 // Return value indicates if the mutex was properly initialized (including
55 // threads-only as a fallback).
56 virtual bool initialize(bool &processSharingSupported)
57 {
58 processSharingSupported = false;
59 return false;
60 }
61
62 virtual bool lock()
63 {
64 return false;
65 }
66
67 virtual void unlock()
68 {
69 }
70};
71
72/**
73 * This is a very basic lock that should work on any system where GCC atomic
74 * intrinsics are supported. It can waste CPU so better primitives should be
75 * used if available on the system.
76 */
77class simpleSpinLock : public KSDCLock
78{
79public:
80 simpleSpinLock(QBasicAtomicInt &spinlock)
81 : m_spinlock(spinlock)
82 {
83 }
84
85 bool initialize(bool &processSharingSupported) override
86 {
87 // Clear the spinlock
88 m_spinlock.storeRelaxed(0);
89 processSharingSupported = true;
90 return true;
91 }
92
93 bool lock() override
94 {
95 // Spin a few times attempting to gain the lock, as upper-level code won't
96 // attempt again without assuming the cache is corrupt.
97 for (unsigned i = 50; i > 0; --i) {
98 if (m_spinlock.testAndSetAcquire(0, 1)) {
99 return true;
100 }
101
102 // Don't steal the processor and starve the thread we're waiting
103 // on.
104 loopSpinPause();
105 }
106
107 return false;
108 }
109
110 void unlock() override
111 {
112 m_spinlock.testAndSetRelease(1, 0);
113 }
114
115private:
116#ifdef Q_CC_GNU
117 __attribute__((always_inline,
118 gnu_inline
119#if !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG)
120 ,
121 artificial
122#endif
123 ))
124#endif
125 static inline void
126 loopSpinPause()
127 {
128// TODO: Spinning might be better in multi-core systems... but that means
129// figuring how to find numbers of CPUs in a cross-platform way.
130#ifdef _POSIX_PRIORITY_SCHEDULING
131 sched_yield();
132#else
133 // Sleep for shortest possible time (nanosleep should round-up).
134 struct timespec wait_time = {0 /* sec */, 100 /* ns */};
135 ::nanosleep(&wait_time, static_cast<struct timespec *>(0));
136#endif
137 }
138
139 QBasicAtomicInt &m_spinlock;
140};
141
142#ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED
143class pthreadLock : public KSDCLock
144{
145public:
146 pthreadLock(pthread_mutex_t &mutex)
147 : m_mutex(mutex)
148 {
149 }
150
151 bool initialize(bool &processSharingSupported) override
152 {
153 // Setup process-sharing.
154 pthread_mutexattr_t mutexAttr;
155 processSharingSupported = false;
156
157 // Initialize attributes, enable process-shared primitives, and setup
158 // the mutex.
159 if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(attr: &mutexAttr) == 0) {
160 if (pthread_mutexattr_setpshared(attr: &mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && pthread_mutex_init(mutex: &m_mutex, mutexattr: &mutexAttr) == 0) {
161 processSharingSupported = true;
162 }
163 pthread_mutexattr_destroy(attr: &mutexAttr);
164 }
165
166 // Attempt to setup for thread-only synchronization.
167 if (!processSharingSupported && pthread_mutex_init(mutex: &m_mutex, mutexattr: nullptr) != 0) {
168 return false;
169 }
170
171 return true;
172 }
173
174 bool lock() override
175 {
176 return pthread_mutex_lock(mutex: &m_mutex) == 0;
177 }
178
179 void unlock() override
180 {
181 pthread_mutex_unlock(mutex: &m_mutex);
182 }
183
184protected:
185 pthread_mutex_t &m_mutex;
186};
187#endif // KSDC_THREAD_PROCESS_SHARED_SUPPORTED
188
189#if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
190class pthreadTimedLock : public pthreadLock
191{
192public:
193 pthreadTimedLock(pthread_mutex_t &mutex)
194 : pthreadLock(mutex)
195 {
196 }
197
198 bool lock() override
199 {
200 struct timespec timeout;
201
202 // Long timeout, but if we fail to meet this timeout it's probably a cache
203 // corruption (and if we take 8 seconds then it should be much much quicker
204 // the next time anyways since we'd be paged back in from disk)
205 timeout.tv_sec = 10 + ::time(timer: nullptr); // Absolute time, so 10 seconds from now
206 timeout.tv_nsec = 0;
207
208 return pthread_mutex_timedlock(mutex: &m_mutex, abstime: &timeout) == 0;
209 }
210};
211#endif // defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
212
213#ifdef KSDC_SEMAPHORES_SUPPORTED
214class semaphoreLock : public KSDCLock
215{
216public:
217 semaphoreLock(sem_t &semaphore)
218 : m_semaphore(semaphore)
219 {
220 }
221
222 bool initialize(bool &processSharingSupported) override
223 {
224 processSharingSupported = false;
225 if (::sysconf(_SC_SEMAPHORES) < 200112L) {
226 return false;
227 }
228
229 // sem_init sets up process-sharing for us.
230 if (sem_init(sem: &m_semaphore, pshared: 1, value: 1) == 0) {
231 processSharingSupported = true;
232 }
233 // If not successful try falling back to thread-shared.
234 else if (sem_init(sem: &m_semaphore, pshared: 0, value: 1) != 0) {
235 return false;
236 }
237
238 return true;
239 }
240
241 bool lock() override
242 {
243 return sem_wait(sem: &m_semaphore) == 0;
244 }
245
246 void unlock() override
247 {
248 sem_post(sem: &m_semaphore);
249 }
250
251protected:
252 sem_t &m_semaphore;
253};
254#endif // KSDC_SEMAPHORES_SUPPORTED
255
256#if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
257class semaphoreTimedLock : public semaphoreLock
258{
259public:
260 semaphoreTimedLock(sem_t &semaphore)
261 : semaphoreLock(semaphore)
262 {
263 }
264
265 bool lock() override
266 {
267 struct timespec timeout;
268
269 // Long timeout, but if we fail to meet this timeout it's probably a cache
270 // corruption (and if we take 8 seconds then it should be much much quicker
271 // the next time anyways since we'd be paged back in from disk)
272 timeout.tv_sec = 10 + ::time(timer: nullptr); // Absolute time, so 10 seconds from now
273 timeout.tv_nsec = 0;
274
275 return sem_timedwait(sem: &m_semaphore, abstime: &timeout) == 0;
276 }
277};
278#endif // defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED)
279
280// This enum controls the type of the locking used for the cache to allow
281// for as much portability as possible. This value will be stored in the
282// cache and used by multiple processes, therefore you should consider this
283// a versioned field, do not re-arrange.
284enum SharedLockId {
285 LOCKTYPE_INVALID = 0,
286 LOCKTYPE_MUTEX = 1, // pthread_mutex
287 LOCKTYPE_SEMAPHORE = 2, // sem_t
288 LOCKTYPE_SPINLOCK = 3, // atomic int in shared memory
289};
290
291// This type is a union of all possible lock types, with a SharedLockId used
292// to choose which one is actually in use.
293struct SharedLock {
294 union {
295#if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED)
296 pthread_mutex_t mutex;
297#endif
298#if defined(KSDC_SEMAPHORES_SUPPORTED)
299 sem_t semaphore;
300#endif
301 QBasicAtomicInt spinlock;
302
303 // It would be highly unfortunate if a simple glibc upgrade or kernel
304 // addition caused this structure to change size when an existing
305 // lock was thought present, so reserve enough size to cover any
306 // reasonable locking structure
307 char unused[64];
308 };
309
310 SharedLockId type;
311};
312
313/**
314 * This is a method to determine the best lock type to use for a
315 * shared cache, based on local support. An identifier to the appropriate
316 * SharedLockId is returned, which can be passed to createLockFromId().
317 */
318SharedLockId findBestSharedLock();
319
320KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock);
321
322#endif /* KSDCLOCK_P_H */
323

source code of kcoreaddons/src/lib/caching/ksdclock_p.h