File indexing completed on 2025-04-27 03:58:06

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-01-29
0007  * Description : Intra-process file i/o lock
0008  *
0009  * SPDX-FileCopyrightText: 2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  *
0011  * Parts of this file are based on qreadwritelock.cpp, LGPL,
0012  * SPDX-FileCopyrightText: 2011 Nokia Corporation and/or its subsidiary(-ies).
0013  *
0014  * SPDX-License-Identifier: GPL-2.0-or-later
0015  *
0016  * ============================================================ */
0017 
0018 #include "filereadwritelock.h"
0019 
0020 // Qt includes
0021 
0022 #include <QHash>
0023 #include <QUuid>
0024 #include <QMutex>
0025 #include <QThread>
0026 #include <QFileInfo>
0027 #include <QMutexLocker>
0028 #include <QWaitCondition>
0029 
0030 // Local includes
0031 
0032 #include "digikam_debug.h"
0033 
0034 namespace Digikam
0035 {
0036 
0037 class Q_DECL_HIDDEN FileReadWriteLockPriv
0038 {
0039 public:
0040 
0041     explicit FileReadWriteLockPriv(const QString& filePath)
0042         : filePath          (filePath),
0043           ref               (0),
0044           waitingReaders    (0),
0045           waitingWriters    (0),
0046           accessCount       (0),
0047           writer            (nullptr)
0048     {
0049     }
0050 
0051     bool isFree() const
0052     {
0053         return (readers.isEmpty() &&
0054                 !writer           &&
0055                 !waitingReaders   &&
0056                 !waitingWriters);
0057     }
0058 
0059 public:
0060 
0061     QString                filePath;
0062     int                    ref;
0063     int                    waitingReaders;
0064     int                    waitingWriters;
0065 
0066     int                    accessCount;
0067     Qt::HANDLE             writer;
0068     QHash<Qt::HANDLE, int> readers;
0069 };
0070 
0071 typedef FileReadWriteLockPriv Entry;
0072 
0073 class Q_DECL_HIDDEN FileReadWriteLockStaticPrivate
0074 {
0075 public:
0076 
0077     QMutex                 mutex;
0078     QWaitCondition         readerWait;
0079     QWaitCondition         writerWait;
0080 
0081     QMutex                 tempFileMutex;
0082 
0083     QHash<QString, Entry*> entries;
0084 
0085 public:
0086 
0087     Entry* entry(const QString& filePath);
0088     void   drop(Entry* entry);
0089 
0090     void   lockForRead(Entry* entry);
0091     void   lockForWrite(Entry* entry);
0092     bool   tryLockForRead(Entry* entry);
0093     bool   tryLockForRead(Entry* entry, int timeout);
0094     bool   tryLockForWrite(Entry* entry);
0095     bool   tryLockForWrite(Entry* entry, int timeout);
0096     void   unlock(Entry* entry);
0097 
0098     Entry* entryLockedForRead(const QString& filePath);
0099     Entry* entryLockedForWrite(const QString& filePath);
0100     void   unlockAndDrop(Entry* entry);
0101 
0102 private:
0103 
0104     Entry* entry_locked(const QString& filePath);
0105     void   drop_locked(Entry* entry);
0106     bool   lockForRead_locked(Entry* entry, int mode, int timeout);
0107     bool   lockForWrite_locked(Entry* entry, int mode, int timeout);
0108     void   unlock_locked(Entry* entry);
0109 };
0110 
0111 // --- Entry allocation ---
0112 
0113 Entry* FileReadWriteLockStaticPrivate::entry(const QString& filePath)
0114 {
0115     QMutexLocker lock(&mutex);
0116 
0117     return entry_locked(filePath);
0118 }
0119 
0120 Entry* FileReadWriteLockStaticPrivate::entry_locked(const QString& filePath)
0121 {
0122     QHash<QString, Entry*>::iterator it = entries.find(filePath);
0123 
0124     if (it == entries.end())
0125     {
0126         it = entries.insert(filePath, new Entry(filePath));
0127     }
0128 
0129     (*it)->ref++;
0130 
0131     return *it;
0132 }
0133 
0134 void FileReadWriteLockStaticPrivate::drop(Entry* entry)
0135 {
0136     QMutexLocker lock(&mutex);
0137     drop_locked(entry);
0138 }
0139 
0140 void FileReadWriteLockStaticPrivate::drop_locked(Entry* entry)
0141 {
0142     entry->ref--;
0143 
0144     if ((entry->ref == 0) && entry->isFree())
0145     {
0146         entries.remove(entry->filePath);
0147         delete entry;
0148     }
0149 }
0150 
0151 // --- locking implementation ---
0152 
0153 void FileReadWriteLockStaticPrivate::lockForRead(Entry* entry)
0154 {
0155     QMutexLocker lock(&mutex);
0156     lockForRead_locked(entry, 0, 0);
0157 }
0158 
0159 bool FileReadWriteLockStaticPrivate::tryLockForRead(Entry* entry)
0160 {
0161     QMutexLocker lock(&mutex);
0162 
0163     return lockForRead_locked(entry, 1, 0);
0164 }
0165 
0166 bool FileReadWriteLockStaticPrivate::tryLockForRead(Entry* entry, int timeout)
0167 {
0168     QMutexLocker lock(&mutex);
0169 
0170     return lockForRead_locked(entry, 2, timeout);
0171 }
0172 
0173 bool FileReadWriteLockStaticPrivate::lockForRead_locked(Entry* entry, int mode, int timeout)
0174 {
0175     Qt::HANDLE self = QThread::currentThreadId();
0176 
0177     // already recursively write-locked by this thread?
0178 
0179     if (entry->writer == self)
0180     {
0181         // If we already have the write lock recursively, just add another write lock instead of the read lock.
0182         // This situation is clean and all right.
0183 
0184         --entry->accessCount;
0185 
0186         return true;
0187     }
0188 
0189     // recursive read lock by this thread?
0190 
0191     QHash<Qt::HANDLE, int>::iterator it = entry->readers.find(self);
0192 
0193     if (it != entry->readers.end())
0194     {
0195         ++it.value();
0196         ++entry->accessCount;
0197 
0198         return true;
0199     }
0200 
0201     if (mode == 1)
0202     {
0203         // tryLock
0204 
0205         if (entry->accessCount < 0)
0206         {
0207             return false;
0208         }
0209     }
0210     else
0211     {
0212         while ((entry->accessCount < 0) || entry->waitingWriters)
0213         {
0214             if (mode == 2)
0215             {
0216                 // tryLock with timeout
0217 
0218                 ++entry->waitingReaders;
0219                 bool success = readerWait.wait(&mutex, timeout);
0220                 --entry->waitingReaders;
0221 
0222                 if (!success)
0223                 {
0224                     return false;
0225                 }
0226             }
0227             else
0228             {
0229                 // lock
0230 
0231                 ++entry->waitingReaders;
0232                 readerWait.wait(&mutex);
0233                 --entry->waitingReaders;
0234             }
0235         }
0236     }
0237 
0238     entry->readers.insert(self, 1);
0239     ++entry->accessCount;
0240 
0241     return true;
0242 }
0243 
0244 void FileReadWriteLockStaticPrivate::lockForWrite(Entry* entry)
0245 {
0246     QMutexLocker lock(&mutex);
0247     lockForWrite_locked(entry, 0, 0);
0248 }
0249 
0250 bool FileReadWriteLockStaticPrivate::tryLockForWrite(Entry* entry)
0251 {
0252     QMutexLocker lock(&mutex);
0253 
0254     return lockForWrite_locked(entry, 1, 0);
0255 }
0256 
0257 bool FileReadWriteLockStaticPrivate::tryLockForWrite(Entry* entry, int timeout)
0258 {
0259     QMutexLocker lock(&mutex);
0260 
0261     return lockForRead_locked(entry, 2, timeout);
0262 }
0263 
0264 bool FileReadWriteLockStaticPrivate::lockForWrite_locked(Entry* entry, int mode, int timeout)
0265 {
0266     Qt::HANDLE self = QThread::currentThreadId();
0267 
0268     // recursive write-lock by this thread?
0269 
0270     if (entry->writer == self)
0271     {
0272         --entry->accessCount;
0273 
0274         return true;
0275     }
0276 
0277     // recursive read lock by this thread?
0278 
0279     QHash<Qt::HANDLE, int>::iterator it = entry->readers.find(self);
0280     int recursiveReadLockCount          = 0;
0281 
0282     if (it != entry->readers.end())
0283     {
0284         // We could deadlock, or promote the read locks to write locks
0285 
0286         qCWarning(DIGIKAM_GENERAL_LOG) << "Locking for write, recursively locked for read: Promoting existing read locks to write locks! "
0287                                        << "Avoid this situation.";
0288 
0289         // The lock was locked for read it.value() times by this thread recursively
0290 
0291         recursiveReadLockCount = it.value();
0292         entry->accessCount    -= it.value();
0293         entry->readers.erase(it);
0294     }
0295 
0296     while (entry->accessCount != 0)
0297     {
0298         if      (mode == 1)
0299         {
0300             // tryLock
0301 
0302             return false;
0303         }
0304         else if (mode == 2)
0305         {
0306             // tryLock with timeout
0307 
0308             entry->waitingWriters++;
0309             bool success = writerWait.wait(&mutex, timeout);
0310             entry->waitingWriters--;
0311 
0312             if (!success)
0313             {
0314                 return false;
0315             }
0316         }
0317         else
0318         {
0319             // lock
0320 
0321             entry->waitingWriters++;
0322             writerWait.wait(&mutex);
0323             entry->waitingWriters--;
0324         }
0325     }
0326 
0327     entry->writer = self;
0328     --entry->accessCount;
0329 
0330     // if we had recursive read locks, they are now promoted to write locks
0331 
0332     entry->accessCount -= recursiveReadLockCount;
0333 
0334     return true;
0335 }
0336 
0337 void FileReadWriteLockStaticPrivate::unlock(Entry* entry)
0338 {
0339     QMutexLocker lock(&mutex);
0340     unlock_locked(entry);
0341 }
0342 
0343 void FileReadWriteLockStaticPrivate::unlock_locked(Entry* entry)
0344 {
0345     bool unlocked = false;
0346 
0347     if      (entry->accessCount > 0)
0348     {
0349         // releasing a read lock
0350 
0351         Qt::HANDLE self                     = QThread::currentThreadId();
0352         QHash<Qt::HANDLE, int>::iterator it = entry->readers.find(self);
0353 
0354         if (it != entry->readers.end())
0355         {
0356             if (--it.value() <= 0)
0357             {
0358                 entry->readers.erase(it);
0359             }
0360         }
0361 
0362         unlocked = (--entry->accessCount == 0);
0363     }
0364     else if ((entry->accessCount < 0) && (++entry->accessCount == 0))
0365     {
0366         // released a write lock
0367 
0368         unlocked      = true;
0369         entry->writer = nullptr;
0370     }
0371 
0372     if (unlocked)
0373     {
0374         if      (entry->waitingWriters)
0375         {
0376             // we must wake all as it is one wait condition for all entries
0377 
0378             writerWait.wakeAll();
0379         }
0380         else if (entry->waitingReaders)
0381         {
0382             readerWait.wakeAll();
0383         }
0384     }
0385 }
0386 
0387 // --- Combination methods ---
0388 
0389 Entry* FileReadWriteLockStaticPrivate::entryLockedForRead(const QString& filePath)
0390 {
0391     QMutexLocker lock(&mutex);
0392     Entry* const e = entry_locked(filePath);
0393     lockForRead_locked(e, 0, 0);
0394 
0395     return e;
0396 }
0397 
0398 Entry* FileReadWriteLockStaticPrivate::entryLockedForWrite(const QString& filePath)
0399 {
0400     QMutexLocker lock(&mutex);
0401     Entry* const e = entry_locked(filePath);
0402     lockForWrite_locked(e, 0, 0);
0403 
0404     return e;
0405 }
0406 
0407 void FileReadWriteLockStaticPrivate::unlockAndDrop(Entry* entry)
0408 {
0409     QMutexLocker lock(&mutex);
0410     unlock_locked(entry);
0411     drop_locked(entry);
0412 }
0413 
0414 Q_GLOBAL_STATIC(FileReadWriteLockStaticPrivate, static_d)
0415 
0416 // -------------------------------------------------------------------------
0417 
0418 FileReadWriteLockKey::FileReadWriteLockKey(const QString& filePath)
0419     : d(static_d->entry(filePath))
0420 {
0421 }
0422 
0423 FileReadWriteLockKey::~FileReadWriteLockKey()
0424 {
0425     static_d->drop(d);
0426 }
0427 
0428 void FileReadWriteLockKey::lockForRead()
0429 {
0430     static_d->lockForRead(d);
0431 }
0432 
0433 void FileReadWriteLockKey::lockForWrite()
0434 {
0435     static_d->lockForWrite(d);
0436 }
0437 
0438 bool FileReadWriteLockKey::tryLockForRead()
0439 {
0440     return static_d->tryLockForRead(d);
0441 }
0442 
0443 bool FileReadWriteLockKey::tryLockForRead(int timeout)
0444 {
0445     return static_d->tryLockForRead(d, timeout);
0446 }
0447 
0448 bool FileReadWriteLockKey::tryLockForWrite()
0449 {
0450     return static_d->tryLockForWrite(d);
0451 }
0452 
0453 bool FileReadWriteLockKey::tryLockForWrite(int timeout)
0454 {
0455     return static_d->tryLockForWrite(d, timeout);
0456 }
0457 
0458 void FileReadWriteLockKey::unlock()
0459 {
0460     static_d->unlock(d);
0461 }
0462 
0463 // -------------------------------------------------------------------------
0464 
0465 FileReadLocker::FileReadLocker(const QString& filePath)
0466     : d(static_d->entryLockedForRead(filePath))
0467 {
0468 }
0469 
0470 FileReadLocker::~FileReadLocker()
0471 {
0472     static_d->unlockAndDrop(d);
0473 }
0474 
0475 FileWriteLocker::FileWriteLocker(const QString& filePath)
0476     : d(static_d->entryLockedForWrite(filePath))
0477 {
0478 }
0479 
0480 FileWriteLocker::~FileWriteLocker()
0481 {
0482     static_d->unlockAndDrop(d);
0483 }
0484 
0485 // -------------------------------------------------------------------------
0486 
0487 SafeTemporaryFile::SafeTemporaryFile()
0488 {
0489 }
0490 
0491 SafeTemporaryFile::SafeTemporaryFile(const QString& templ)
0492     : QTemporaryFile(nullptr),
0493       m_templ       (templ)
0494 {
0495     QString random = QUuid::createUuid().toString().mid(1, 8);
0496     m_templ.replace(QLatin1String("XXXXXX"),
0497                     QLatin1String("XXXXXX-") + random);
0498     setFileTemplate(m_templ);
0499 }
0500 
0501 bool SafeTemporaryFile::open()
0502 {
0503     return open(QIODevice::ReadWrite);
0504 }
0505 
0506 bool SafeTemporaryFile::open(QIODevice::OpenMode mode)
0507 {
0508     QMutexLocker lock(&static_d->tempFileMutex);
0509 
0510     return QTemporaryFile::open(mode);
0511 }
0512 
0513  // Workaround for Qt-Bug 74291 with UNC paths
0514 
0515 QString SafeTemporaryFile::safeFilePath() const
0516 {
0517     QFileInfo orgInfo(m_templ);
0518     QFileInfo tmpInfo(fileName());
0519 
0520     return (orgInfo.path() + QLatin1Char('/') + tmpInfo.fileName());
0521 }
0522 
0523 } // namespace Digikam
0524 
0525 #include "moc_filereadwritelock.cpp"