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"