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