1 /*
2 SPDX-FileCopyrightText: 2007 Kris Wong <kris.p.wong@gmail.com>
3 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
4 SPDX-FileCopyrightText: 2007-2009 David Nolden <david.nolden.kdevelop@art-master.de>
5 SPDX-FileCopyrightText: 2013 Milian Wolff <mail@milianw.de>
6
7 SPDX-License-Identifier: LGPL-2.0-only
8 */
9
10 #include "duchainlock.h"
11 #include "duchain.h"
12
13 #include <QThread>
14 #include <QThreadStorage>
15 #include <QElapsedTimer>
16
17 ///@todo Always prefer exactly that lock that is requested by the thread that has the foreground mutex,
18 /// to reduce the amount of UI blocking.
19
20 //Microseconds to sleep when waiting for a lock
21 const uint uSleepTime = 500;
22
23 namespace KDevelop {
24 class DUChainLockPrivate
25 {
26 public:
DUChainLockPrivate()27 DUChainLockPrivate()
28 : m_writer(nullptr)
29 , m_writerRecursion(0)
30 , m_totalReaderRecursion(0)
31 { }
32
ownReaderRecursion() const33 int ownReaderRecursion() const
34 {
35 return m_readerRecursion.localData();
36 }
37
changeOwnReaderRecursion(int difference)38 void changeOwnReaderRecursion(int difference)
39 {
40 m_readerRecursion.localData() += difference;
41 Q_ASSERT(m_readerRecursion.localData() >= 0);
42 m_totalReaderRecursion.fetchAndAddOrdered(difference);
43 }
44
45 ///Holds the writer that currently has the write-lock, or zero. Is protected by m_writerRecursion.
46 QAtomicPointer<QThread> m_writer;
47
48 ///How often is the chain write-locked by the writer? This value protects m_writer,
49 ///m_writer may only be changed by the thread that successfully increases this value from 0 to 1
50 QAtomicInt m_writerRecursion;
51 ///How often is the chain read-locked recursively by all readers? Should be sum of all m_readerRecursion values
52 QAtomicInt m_totalReaderRecursion;
53
54 QThreadStorage<int> m_readerRecursion;
55 };
56
DUChainLock()57 DUChainLock::DUChainLock()
58 : d_ptr(new DUChainLockPrivate)
59 {
60 }
61
62 DUChainLock::~DUChainLock() = default;
63
lockForRead(unsigned int timeout)64 bool DUChainLock::lockForRead(unsigned int timeout)
65 {
66 Q_D(DUChainLock);
67
68 ///Step 1: Increase the own reader-recursion. This will make sure no further write-locks will succeed
69 d->changeOwnReaderRecursion(1);
70
71 QThread* w = d->m_writer.loadAcquire();
72 if (w == nullptr || w == QThread::currentThread()) {
73 //Successful lock: Either there is no writer, or we hold the write-lock by ourselves
74 } else {
75 ///Step 2: Start spinning until there is no writer any more
76
77 QElapsedTimer t;
78 if (timeout) {
79 t.start();
80 }
81
82 while (d->m_writer.loadAcquire()) {
83 if (!timeout || t.elapsed() < timeout) {
84 QThread::usleep(uSleepTime);
85 } else {
86 //Fail!
87 d->changeOwnReaderRecursion(-1);
88 return false;
89 }
90 }
91 }
92
93 return true;
94 }
95
releaseReadLock()96 void DUChainLock::releaseReadLock()
97 {
98 Q_D(DUChainLock);
99
100 d->changeOwnReaderRecursion(-1);
101 }
102
currentThreadHasReadLock()103 bool DUChainLock::currentThreadHasReadLock()
104 {
105 Q_D(DUChainLock);
106
107 return ( bool )d->ownReaderRecursion();
108 }
109
lockForWrite(uint timeout)110 bool DUChainLock::lockForWrite(uint timeout)
111 {
112 Q_D(DUChainLock);
113
114 //It is not allowed to acquire a write-lock while holding read-lock
115
116 Q_ASSERT(d->ownReaderRecursion() == 0);
117
118 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
119 if (d->m_writer.loadRelaxed() == QThread::currentThread()) {
120 #else
121 if (d->m_writer.load() == QThread::currentThread()) {
122 #endif
123 //We already hold the write lock, just increase the recursion count and return
124 d->m_writerRecursion.fetchAndAddRelaxed(1);
125 return true;
126 }
127
128 QElapsedTimer t;
129 if (timeout) {
130 t.start();
131 }
132
133 while (1) {
134 //Try acquiring the write-lcok
135 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
136 if (d->m_totalReaderRecursion.loadRelaxed() == 0 &&
137 #else
138 if (d->m_totalReaderRecursion.load() == 0 &&
139 #endif
140 d->m_writerRecursion.testAndSetOrdered(0, 1)) {
141 //Now we can be sure that there is no other writer, as we have increased m_writerRecursion from 0 to 1
142 d->m_writer = QThread::currentThread();
143 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
144 if (d->m_totalReaderRecursion.loadRelaxed() == 0) {
145 #else
146 if (d->m_totalReaderRecursion.load() == 0) {
147 #endif
148 //There is still no readers, we have successfully acquired a write-lock
149 return true;
150 } else {
151 //There may be readers.. we have to continue spinning
152 d->m_writer = nullptr;
153 d->m_writerRecursion = 0;
154 }
155 }
156
157 if (!timeout || t.elapsed() < timeout) {
158 QThread::usleep(uSleepTime);
159 } else {
160 //Fail!
161 return false;
162 }
163 }
164
165 return false;
166 }
167
168 void DUChainLock::releaseWriteLock()
169 {
170 Q_D(DUChainLock);
171
172 Q_ASSERT(currentThreadHasWriteLock());
173
174 //The order is important here, m_writerRecursion protects m_writer
175
176 //TODO: could testAndSet here
177 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
178 if (d->m_writerRecursion.loadRelaxed() == 1) {
179 #else
180 if (d->m_writerRecursion.load() == 1) {
181 #endif
182 d->m_writer = nullptr;
183 d->m_writerRecursion = 0;
184 } else {
185 d->m_writerRecursion.fetchAndAddOrdered(-1);
186 }
187 }
188
189 bool DUChainLock::currentThreadHasWriteLock() const
190 {
191 Q_D(const DUChainLock);
192
193 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
194 return d->m_writer.loadRelaxed() == QThread::currentThread();
195 #else
196 return d->m_writer.load() == QThread::currentThread();
197 #endif
198 }
199
200 DUChainReadLocker::DUChainReadLocker(DUChainLock* duChainLock, uint timeout)
201 : m_lock(duChainLock ? duChainLock : DUChain::lock())
202 , m_locked(false)
203 , m_timeout(timeout)
204 {
205 lock();
206 }
207
208 DUChainReadLocker::~DUChainReadLocker()
209 {
210 unlock();
211 }
212
213 bool DUChainReadLocker::locked() const
214 {
215 return m_locked;
216 }
217
218 bool DUChainReadLocker::lock()
219 {
220 if (m_locked) {
221 return true;
222 }
223
224 bool l = false;
225 if (m_lock) {
226 l = m_lock->lockForRead(m_timeout);
227 Q_ASSERT(m_timeout || l);
228 }
229 ;
230
231 m_locked = l;
232
233 return l;
234 }
235
236 void DUChainReadLocker::unlock()
237 {
238 if (m_locked && m_lock) {
239 m_lock->releaseReadLock();
240 m_locked = false;
241 }
242 }
243
244 DUChainWriteLocker::DUChainWriteLocker(DUChainLock* duChainLock, uint timeout)
245 : m_lock(duChainLock ? duChainLock : DUChain::lock())
246 , m_locked(false)
247 , m_timeout(timeout)
248 {
249 lock();
250 }
251
252 DUChainWriteLocker::~DUChainWriteLocker()
253 {
254 unlock();
255 }
256
257 bool DUChainWriteLocker::lock()
258 {
259 if (m_locked) {
260 return true;
261 }
262
263 bool l = false;
264 if (m_lock) {
265 l = m_lock->lockForWrite(m_timeout);
266 Q_ASSERT(m_timeout || l);
267 }
268 ;
269
270 m_locked = l;
271
272 return l;
273 }
274
275 bool DUChainWriteLocker::locked() const
276 {
277 return m_locked;
278 }
279
280 void DUChainWriteLocker::unlock()
281 {
282 if (m_locked && m_lock) {
283 m_lock->releaseWriteLock();
284 m_locked = false;
285 }
286 }
287 }
288