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