1 /*
2 SPDX-FileCopyrightText: 2010 David Nolden <david.nolden.kdevelop@art-master.de>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "foregroundlock.h"
8
9 #include <QCoreApplication>
10 #include <QMutex>
11 #include <QThread>
12
13 using namespace KDevelop;
14
15 namespace {
16
17 QMutex internalMutex;
18 QMutex tryLockMutex;
19 QMutex waitMutex;
20 QMutex finishMutex;
21 QWaitCondition condition;
22
23 volatile QThread* holderThread = nullptr;
24 volatile int recursion = 0;
25
lockForegroundMutexInternal()26 void lockForegroundMutexInternal()
27 {
28 if (holderThread == QThread::currentThread()) {
29 // We already have the mutex
30 ++recursion;
31 } else {
32 internalMutex.lock();
33 Q_ASSERT(recursion == 0 && holderThread == nullptr);
34 holderThread = QThread::currentThread();
35 recursion = 1;
36 }
37 }
38
tryLockForegroundMutexInternal(int interval=0)39 bool tryLockForegroundMutexInternal(int interval = 0)
40 {
41 if (holderThread == QThread::currentThread()) {
42 // We already have the mutex
43 ++recursion;
44 return true;
45 } else {
46 if (internalMutex.tryLock(interval)) {
47 Q_ASSERT(recursion == 0 && holderThread == nullptr);
48 holderThread = QThread::currentThread();
49 recursion = 1;
50 return true;
51 } else {
52 return false;
53 }
54 }
55 }
56
unlockForegroundMutexInternal(bool duringDestruction=false)57 void unlockForegroundMutexInternal(bool duringDestruction = false)
58 {
59 /// Note: QThread::currentThread() might already be invalid during destruction.
60 if (!duringDestruction) {
61 Q_ASSERT(holderThread == QThread::currentThread());
62 }
63
64 Q_ASSERT(recursion > 0);
65 recursion -= 1;
66 if (recursion == 0) {
67 holderThread = nullptr;
68 internalMutex.unlock();
69 }
70 }
71 }
72
ForegroundLock(bool lock)73 ForegroundLock::ForegroundLock(bool lock)
74 {
75 if (lock)
76 relock();
77 }
78
relock()79 void KDevelop::ForegroundLock::relock()
80 {
81 Q_ASSERT(!m_locked);
82
83 if (!QCoreApplication::instance() || // Initialization isn't complete yet
84 QThread::currentThread() == QCoreApplication::instance()->thread()
85 || // We're the main thread (deadlock might happen if we'd enter the trylock loop)
86 holderThread == QThread::currentThread()) { // We already have the foreground lock (deadlock might happen if
87 // we'd enter the trylock loop)
88 lockForegroundMutexInternal();
89 } else {
90 QMutexLocker lock(&tryLockMutex);
91
92 while (!tryLockForegroundMutexInternal(10)) {
93 // In case an additional event-loop was started from within the foreground, we send
94 // events to the foreground to temporarily release the lock.
95
96 class ForegroundReleaser : public DoInForeground
97 {
98 public:
99 void doInternal() override
100 {
101 // By locking the mutex, we make sure that the requester is actually waiting for the condition
102 waitMutex.lock();
103 // Now we release the foreground lock
104 TemporarilyReleaseForegroundLock release;
105 // And signalize to the requester that we've released it
106 condition.wakeAll();
107 // Allow the requester to actually wake up, by unlocking m_waitMutex
108 waitMutex.unlock();
109 // Now wait until the requester is ready
110 QMutexLocker lock(&finishMutex);
111 }
112 };
113
114 static ForegroundReleaser releaser;
115
116 QMutexLocker lockWait(&waitMutex);
117 QMutexLocker lockFinish(&finishMutex);
118
119 QMetaObject::invokeMethod(&releaser, "doInternalSlot", Qt::QueuedConnection);
120 // We limit the waiting time here, because sometimes it may happen that the foreground-lock is released,
121 // and the foreground is waiting without an event-loop running. (For example through TemporarilyReleaseForegroundLock)
122 condition.wait(&waitMutex, 30);
123
124 if (tryLockForegroundMutexInternal()) {
125 //success
126 break;
127 } else {
128 //Probably a third thread has creeped in and
129 //got the foreground lock before us. Just try again.
130 }
131 }
132 }
133 m_locked = true;
134 Q_ASSERT(holderThread == QThread::currentThread());
135 Q_ASSERT(recursion > 0);
136 }
137
isLockedForThread()138 bool KDevelop::ForegroundLock::isLockedForThread()
139 {
140 return QThread::currentThread() == holderThread
141 || QThread::currentThread() == QCoreApplication::instance()->thread();
142 }
143
tryLock()144 bool KDevelop::ForegroundLock::tryLock()
145 {
146 if (tryLockForegroundMutexInternal()) {
147 m_locked = true;
148 return true;
149 }
150 return false;
151 }
152
unlock()153 void KDevelop::ForegroundLock::unlock()
154 {
155 Q_ASSERT(m_locked);
156 unlockForegroundMutexInternal();
157 m_locked = false;
158 }
159
TemporarilyReleaseForegroundLock()160 TemporarilyReleaseForegroundLock::TemporarilyReleaseForegroundLock()
161 {
162 Q_ASSERT(holderThread == QThread::currentThread());
163
164 m_recursion = 0;
165
166 while (holderThread == QThread::currentThread()) {
167 unlockForegroundMutexInternal();
168 ++m_recursion;
169 }
170 }
171
~TemporarilyReleaseForegroundLock()172 TemporarilyReleaseForegroundLock::~TemporarilyReleaseForegroundLock()
173 {
174 for (int a = 0; a < m_recursion; ++a)
175 lockForegroundMutexInternal();
176
177 Q_ASSERT(recursion == m_recursion && holderThread == QThread::currentThread());
178 }
179
~ForegroundLock()180 KDevelop::ForegroundLock::~ForegroundLock()
181 {
182 if (m_locked)
183 unlock();
184 }
185
isLocked() const186 bool KDevelop::ForegroundLock::isLocked() const
187 {
188 return m_locked;
189 }
190
191 namespace KDevelop {
doIt()192 void DoInForeground::doIt()
193 {
194 if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
195 // We're already in the foreground, just call the handler code
196 doInternal();
197 } else {
198 QMutexLocker lock(&m_mutex);
199 QMetaObject::invokeMethod(this, "doInternalSlot", Qt::QueuedConnection);
200 m_wait.wait(&m_mutex);
201 }
202 }
203
~DoInForeground()204 DoInForeground::~DoInForeground()
205 {
206 }
207
DoInForeground()208 DoInForeground::DoInForeground()
209 {
210 moveToThread(QCoreApplication::instance()->thread());
211 }
212
doInternalSlot()213 void DoInForeground::doInternalSlot()
214 {
215 VERIFY_FOREGROUND_LOCKED
216 doInternal();
217 QMutexLocker lock(&m_mutex);
218 m_wait.wakeAll();
219 }
220 }
221
222 // Important: The foreground lock has to be held by default, so lock it during static initialization
223 static struct StaticLock
224 {
StaticLockStaticLock225 StaticLock()
226 {
227 lockForegroundMutexInternal();
228 }
~StaticLockStaticLock229 ~StaticLock()
230 {
231 unlockForegroundMutexInternal(true);
232 }
233 private:
234 Q_DISABLE_COPY(StaticLock)
235 } staticLock;
236