File indexing completed on 2024-05-05 04:38:44

0001 /*
0002     SPDX-FileCopyrightText: 2010 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "foregroundlock.h"
0008 
0009 #include <QCoreApplication>
0010 #include <QMutex>
0011 #include <QThread>
0012 
0013 using namespace KDevelop;
0014 
0015 namespace {
0016 
0017 QMutex internalMutex;
0018 QMutex tryLockMutex;
0019 QMutex waitMutex;
0020 QMutex finishMutex;
0021 QWaitCondition condition;
0022 
0023 volatile QThread* holderThread = nullptr;
0024 volatile int recursion = 0;
0025 
0026 void lockForegroundMutexInternal()
0027 {
0028     if (holderThread == QThread::currentThread()) {
0029         // We already have the mutex
0030         ++recursion;
0031     } else {
0032         internalMutex.lock();
0033         Q_ASSERT(recursion == 0 && holderThread == nullptr);
0034         holderThread = QThread::currentThread();
0035         recursion = 1;
0036     }
0037 }
0038 
0039 bool tryLockForegroundMutexInternal(int interval = 0)
0040 {
0041     if (holderThread == QThread::currentThread()) {
0042         // We already have the mutex
0043         ++recursion;
0044         return true;
0045     } else {
0046         if (internalMutex.tryLock(interval)) {
0047             Q_ASSERT(recursion == 0 && holderThread == nullptr);
0048             holderThread = QThread::currentThread();
0049             recursion = 1;
0050             return true;
0051         } else {
0052             return false;
0053         }
0054     }
0055 }
0056 
0057 void unlockForegroundMutexInternal(bool duringDestruction = false)
0058 {
0059     /// Note: QThread::currentThread() might already be invalid during destruction.
0060     if (!duringDestruction) {
0061         Q_ASSERT(holderThread == QThread::currentThread());
0062     }
0063 
0064     Q_ASSERT(recursion > 0);
0065     recursion -= 1;
0066     if (recursion == 0) {
0067         holderThread = nullptr;
0068         internalMutex.unlock();
0069     }
0070 }
0071 }
0072 
0073 ForegroundLock::ForegroundLock(bool lock)
0074 {
0075     if (lock)
0076         relock();
0077 }
0078 
0079 void KDevelop::ForegroundLock::relock()
0080 {
0081     Q_ASSERT(!m_locked);
0082 
0083     if (!QCoreApplication::instance() || // Initialization isn't complete yet
0084         QThread::currentThread() == QCoreApplication::instance()->thread()
0085         || // We're the main thread (deadlock might happen if we'd enter the trylock loop)
0086         holderThread == QThread::currentThread()) { // We already have the foreground lock (deadlock might happen if
0087                                                     // we'd enter the trylock loop)
0088         lockForegroundMutexInternal();
0089     } else {
0090         QMutexLocker lock(&tryLockMutex);
0091 
0092         while (!tryLockForegroundMutexInternal(10)) {
0093             // In case an additional event-loop was started from within the foreground, we send
0094             // events to the foreground to temporarily release the lock.
0095 
0096             class ForegroundReleaser : public DoInForeground
0097             {
0098 public:
0099                 void doInternal() override
0100                 {
0101                     // By locking the mutex, we make sure that the requester is actually waiting for the condition
0102                     waitMutex.lock();
0103                     // Now we release the foreground lock
0104                     TemporarilyReleaseForegroundLock release;
0105                     // And signalize to the requester that we've released it
0106                     condition.wakeAll();
0107                     // Allow the requester to actually wake up, by unlocking m_waitMutex
0108                     waitMutex.unlock();
0109                     // Now wait until the requester is ready
0110                     QMutexLocker lock(&finishMutex);
0111                 }
0112             };
0113 
0114             static ForegroundReleaser releaser;
0115 
0116             QMutexLocker lockWait(&waitMutex);
0117             QMutexLocker lockFinish(&finishMutex);
0118 
0119             QMetaObject::invokeMethod(&releaser, "doInternalSlot", Qt::QueuedConnection);
0120             // We limit the waiting time here, because sometimes it may happen that the foreground-lock is released,
0121             // and the foreground is waiting without an event-loop running. (For example through TemporarilyReleaseForegroundLock)
0122             condition.wait(&waitMutex, 30);
0123 
0124             if (tryLockForegroundMutexInternal()) {
0125                 //success
0126                 break;
0127             } else {
0128                 //Probably a third thread has creeped in and
0129                 //got the foreground lock before us. Just try again.
0130             }
0131         }
0132     }
0133     m_locked = true;
0134     Q_ASSERT(holderThread == QThread::currentThread());
0135     Q_ASSERT(recursion > 0);
0136 }
0137 
0138 bool KDevelop::ForegroundLock::isLockedForThread()
0139 {
0140     return QThread::currentThread() == holderThread
0141         || QThread::currentThread() == QCoreApplication::instance()->thread();
0142 }
0143 
0144 bool KDevelop::ForegroundLock::tryLock()
0145 {
0146     if (tryLockForegroundMutexInternal()) {
0147         m_locked = true;
0148         return true;
0149     }
0150     return false;
0151 }
0152 
0153 void KDevelop::ForegroundLock::unlock()
0154 {
0155     Q_ASSERT(m_locked);
0156     unlockForegroundMutexInternal();
0157     m_locked = false;
0158 }
0159 
0160 TemporarilyReleaseForegroundLock::TemporarilyReleaseForegroundLock()
0161 {
0162     Q_ASSERT(holderThread == QThread::currentThread());
0163 
0164     m_recursion = 0;
0165 
0166     while (holderThread == QThread::currentThread()) {
0167         unlockForegroundMutexInternal();
0168         ++m_recursion;
0169     }
0170 }
0171 
0172 TemporarilyReleaseForegroundLock::~TemporarilyReleaseForegroundLock()
0173 {
0174     for (int a = 0; a < m_recursion; ++a)
0175         lockForegroundMutexInternal();
0176 
0177     Q_ASSERT(recursion == m_recursion && holderThread == QThread::currentThread());
0178 }
0179 
0180 KDevelop::ForegroundLock::~ForegroundLock()
0181 {
0182     if (m_locked)
0183         unlock();
0184 }
0185 
0186 bool KDevelop::ForegroundLock::isLocked() const
0187 {
0188     return m_locked;
0189 }
0190 
0191 namespace KDevelop {
0192 void DoInForeground::doIt()
0193 {
0194     if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
0195         // We're already in the foreground, just call the handler code
0196         doInternal();
0197     } else {
0198         QMutexLocker lock(&m_mutex);
0199         QMetaObject::invokeMethod(this, "doInternalSlot", Qt::QueuedConnection);
0200         m_wait.wait(&m_mutex);
0201     }
0202 }
0203 
0204 DoInForeground::~DoInForeground()
0205 {
0206 }
0207 
0208 DoInForeground::DoInForeground()
0209 {
0210     moveToThread(QCoreApplication::instance()->thread());
0211 }
0212 
0213 void DoInForeground::doInternalSlot()
0214 {
0215     VERIFY_FOREGROUND_LOCKED
0216         doInternal();
0217     QMutexLocker lock(&m_mutex);
0218     m_wait.wakeAll();
0219 }
0220 }
0221 
0222 // Important: The foreground lock has to be held by default, so lock it during static initialization
0223 static struct StaticLock
0224 {
0225     StaticLock()
0226     {
0227         lockForegroundMutexInternal();
0228     }
0229     ~StaticLock()
0230     {
0231         unlockForegroundMutexInternal(true);
0232     }
0233 private:
0234     Q_DISABLE_COPY(StaticLock)
0235 } staticLock;
0236 
0237 #include "moc_foregroundlock.cpp"