Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qreadwritelock.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5
6#include "qplatformdefs.h"
7#include "qreadwritelock.h"
8
9#include "qthread.h"
10#include "qreadwritelock_p.h"
11#include "private/qfreelist_p.h"
12#include "private/qlocking_p.h"
13
14#include <algorithm>
15
17
18/*
19 * Implementation details of QReadWriteLock:
20 *
21 * Depending on the valued of d_ptr, the lock is in the following state:
22 * - when d_ptr == 0x0: Unlocked (no readers, no writers) and non-recursive.
23 * - when d_ptr & 0x1: If the least significant bit is set, we are locked for read.
24 * In that case, d_ptr>>4 represents the number of reading threads minus 1. No writers
25 * are waiting, and the lock is not recursive.
26 * - when d_ptr == 0x2: We are locked for write and nobody is waiting. (no contention)
27 * - In any other case, d_ptr points to an actual QReadWriteLockPrivate.
28 */
29
30using namespace QReadWriteLockStates;
31namespace {
32
33using steady_clock = std::chrono::steady_clock;
34
35const auto dummyLockedForRead = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForRead));
36const auto dummyLockedForWrite = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForWrite));
37inline bool isUncontendedLocked(const QReadWriteLockPrivate *d)
38{ return quintptr(d) & StateMask; }
39}
40
41static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
43static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
45
112QReadWriteLockPrivate *QReadWriteLock::initRecursive()
113{
114 auto d = new QReadWriteLockPrivate(true);
115 Q_ASSERT_X(!(quintptr(d) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment");
116 return d;
117}
118
126void QReadWriteLock::destroyRecursive(QReadWriteLockPrivate *d)
127{
128 if (isUncontendedLocked(d)) {
129 qWarning("QReadWriteLock: destroying locked QReadWriteLock");
130 return;
131 }
132 delete d;
133}
134
184bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout)
185{
186 // Fast case: non contended:
187 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
188 if (d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
189 return true;
190 return contendedTryLockForRead(d_ptr, timeout, d);
191}
192
193Q_NEVER_INLINE static bool contendedTryLockForRead(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
195{
196 while (true) {
197 if (d == nullptr) {
198 if (!d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
199 continue;
200 return true;
201 }
202
204 // locked for read, increase the counter
205 const auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) + (1U<<4));
206 Q_ASSERT_X(quintptr(val) > (1U<<4), "QReadWriteLock::tryLockForRead()",
207 "Overflow in lock counter");
208 if (!d_ptr.testAndSetAcquire(d, val, d))
209 continue;
210 return true;
211 }
212
213 if (d == dummyLockedForWrite) {
214 if (timeout.hasExpired())
215 return false;
216
217 // locked for write, assign a d_ptr and wait.
219 val->writerCount = 1;
220 if (!d_ptr.testAndSetOrdered(d, val, d)) {
221 val->writerCount = 0;
222 val->release();
223 continue;
224 }
225 d = val;
226 }
227 Q_ASSERT(!isUncontendedLocked(d));
228 // d is an actual pointer;
229
230 if (d->recursive)
231 return d->recursiveLockForRead(timeout);
232
233 auto lock = qt_unique_lock(d->mutex);
234 if (d != d_ptr.loadRelaxed()) {
235 // d_ptr has changed: this QReadWriteLock was unlocked before we had
236 // time to lock d->mutex.
237 // We are holding a lock to a mutex within a QReadWriteLockPrivate
238 // that is already released (or even is already re-used). That's ok
239 // because the QFreeList never frees them.
240 // Just unlock d->mutex (at the end of the scope) and retry.
241 d = d_ptr.loadAcquire();
242 continue;
243 }
244 return d->lockForRead(lock, timeout);
245 }
246}
247
299bool QReadWriteLock::tryLockForWrite(QDeadlineTimer timeout)
300{
301 // Fast case: non contended:
302 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
303 if (d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyLockedForWrite, d))
304 return true;
305 return contendedTryLockForWrite(d_ptr, timeout, d);
306}
307
308Q_NEVER_INLINE static bool contendedTryLockForWrite(QAtomicPointer<QReadWriteLockPrivate> &d_ptr,
310{
311 while (true) {
312 if (d == nullptr) {
313 if (!d_ptr.testAndSetAcquire(d, dummyLockedForWrite, d))
314 continue;
315 return true;
316 }
317
318 if (isUncontendedLocked(d)) {
319 if (timeout.hasExpired())
320 return false;
321
322 // locked for either read or write, assign a d_ptr and wait.
324 if (d == dummyLockedForWrite)
325 val->writerCount = 1;
326 else
327 val->readerCount = (quintptr(d) >> 4) + 1;
328 if (!d_ptr.testAndSetOrdered(d, val, d)) {
329 val->writerCount = val->readerCount = 0;
330 val->release();
331 continue;
332 }
333 d = val;
334 }
335 Q_ASSERT(!isUncontendedLocked(d));
336 // d is an actual pointer;
337
338 if (d->recursive)
339 return d->recursiveLockForWrite(timeout);
340
341 auto lock = qt_unique_lock(d->mutex);
342 if (d != d_ptr.loadRelaxed()) {
343 // The mutex was unlocked before we had time to lock the mutex.
344 // We are holding to a mutex within a QReadWriteLockPrivate that is already released
345 // (or even is already re-used) but that's ok because the QFreeList never frees them.
346 d = d_ptr.loadAcquire();
347 continue;
348 }
349 return d->lockForWrite(lock, timeout);
350 }
351}
352
361void QReadWriteLock::unlock()
362{
363 QReadWriteLockPrivate *d = d_ptr.loadAcquire();
364 while (true) {
365 Q_ASSERT_X(d, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
366
367 // Fast case: no contention: (no waiters, no other readers)
368 if (quintptr(d) <= 2) { // 1 or 2 (StateLockedForRead or StateLockedForWrite)
369 if (!d_ptr.testAndSetOrdered(d, nullptr, d))
370 continue;
371 return;
372 }
373
375 Q_ASSERT(quintptr(d) > (1U<<4)); //otherwise that would be the fast case
376 // Just decrease the reader's count.
377 auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) - (1U<<4));
378 if (!d_ptr.testAndSetOrdered(d, val, d))
379 continue;
380 return;
381 }
382
383 Q_ASSERT(!isUncontendedLocked(d));
384
385 if (d->recursive) {
386 d->recursiveUnlock();
387 return;
388 }
389
390 const auto lock = qt_scoped_lock(d->mutex);
391 if (d->writerCount) {
392 Q_ASSERT(d->writerCount == 1);
393 Q_ASSERT(d->readerCount == 0);
394 d->writerCount = 0;
395 } else {
396 Q_ASSERT(d->readerCount > 0);
397 d->readerCount--;
398 if (d->readerCount > 0)
399 return;
400 }
401
402 if (d->waitingReaders || d->waitingWriters) {
403 d->unlock();
404 } else {
405 Q_ASSERT(d_ptr.loadRelaxed() == d); // should not change when we still hold the mutex
406 d_ptr.storeRelease(nullptr);
407 d->release();
408 }
409 return;
410 }
411}
412
414{
415 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
416
417 while (waitingWriters || writerCount) {
418 if (timeout.hasExpired())
419 return false;
420 if (!timeout.isForever()) {
422 readerCond.wait_until(lock, timeout.deadline<steady_clock>());
423 } else {
425 readerCond.wait(lock);
426 }
428 }
429 readerCount++;
430 Q_ASSERT(writerCount == 0);
431 return true;
432}
433
435{
436 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
437
438 while (readerCount || writerCount) {
439 if (timeout.hasExpired()) {
441 // We timed out and now there is no more writers or waiting writers, but some
442 // readers were queued (probably because of us). Wake the waiting readers.
443 readerCond.notify_all();
444 }
445 return false;
446 }
447 if (!timeout.isForever()) {
449 writerCond.wait_until(lock, timeout.deadline<steady_clock>());
450 } else {
452 writerCond.wait(lock);
453 }
455 }
456
457 Q_ASSERT(writerCount == 0);
458 Q_ASSERT(readerCount == 0);
459 writerCount = 1;
460 return true;
461}
462
464{
465 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
466 if (waitingWriters)
467 writerCond.notify_one();
468 else if (waitingReaders)
469 readerCond.notify_all();
470}
471
473{
474 return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
475}
476
478{
480 auto lock = qt_unique_lock(mutex);
481
483
484 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
485 handleEquals(self));
486 if (it != currentReaders.end()) {
487 ++it->recursionLevel;
488 return true;
489 }
490
491 if (!lockForRead(lock, timeout))
492 return false;
493
494 Reader r = {self, 1};
495 currentReaders.append(std::move(r));
496 return true;
497}
498
500{
502 auto lock = qt_unique_lock(mutex);
503
505 if (currentWriter == self) {
506 writerCount++;
507 return true;
508 }
509
511 return false;
512
513 currentWriter = self;
514 return true;
515}
516
518{
520 auto lock = qt_unique_lock(mutex);
521
523 if (self == currentWriter) {
524 if (--writerCount > 0)
525 return;
526 currentWriter = nullptr;
527 } else {
528 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
529 handleEquals(self));
530 if (it == currentReaders.end()) {
531 qWarning("QReadWriteLock::unlock: unlocking from a thread that did not lock");
532 return;
533 } else {
534 if (--it->recursionLevel <= 0) {
535 currentReaders.erase(it);
536 readerCount--;
537 }
538 if (readerCount)
539 return;
540 }
541 }
542
543 unlock();
544}
545
546// The freelist management
547namespace {
548struct QReadWriteLockFreeListConstants : QFreeListDefaultConstants
549{
550 enum { BlockCount = 4, MaxIndex=0xffff };
551 static const int Sizes[BlockCount];
552};
553Q_CONSTINIT const int
554 QReadWriteLockFreeListConstants::Sizes[QReadWriteLockFreeListConstants::BlockCount] = {
555 16, 128, 1024, QReadWriteLockFreeListConstants::MaxIndex - (16 + 128 + 1024)
556 };
557
558typedef QFreeList<QReadWriteLockPrivate, QReadWriteLockFreeListConstants> QReadWriteLockFreeList;
559Q_GLOBAL_STATIC(QReadWriteLockFreeList, qrwl_freelist);
560}
561
563{
564 int i = qrwl_freelist->next();
565 QReadWriteLockPrivate *d = &(*qrwl_freelist)[i];
566 d->id = i;
567 Q_ASSERT(!d->recursive);
568 Q_ASSERT(!d->waitingReaders && !d->waitingWriters && !d->readerCount && !d->writerCount);
569 return d;
570}
571
573{
576 qrwl_freelist->release(id);
577}
578
\inmodule QtCore
std::condition_variable readerCond
bool lockForRead(std::unique_lock< std::mutex > &lock, QDeadlineTimer timeout)
bool lockForWrite(std::unique_lock< std::mutex > &lock, QDeadlineTimer timeout)
bool recursiveLockForRead(QDeadlineTimer timeout)
bool recursiveLockForWrite(QDeadlineTimer timeout)
static QReadWriteLockPrivate * allocate()
std::condition_variable writerCond
QVarLengthArray< Reader, 16 > currentReaders
static Qt::HANDLE currentThreadId() noexcept Q_DECL_PURE_FUNCTION
Definition qthread.h:149
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Lock qt_scoped_lock(Mutex &mutex)
Definition qlocking_p.h:58
void * HANDLE
#define Q_NEVER_INLINE
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:166
GLuint64 GLenum void * handle
GLboolean r
[2]
GLbitfield GLuint64 timeout
[4]
GLuint GLfloat * val
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
static bool contendedTryLockForWrite(QAtomicPointer< QReadWriteLockPrivate > &d_ptr, QDeadlineTimer timeout, QReadWriteLockPrivate *d)
static bool contendedTryLockForRead(QAtomicPointer< QReadWriteLockPrivate > &d_ptr, QDeadlineTimer timeout, QReadWriteLockPrivate *d)
static auto handleEquals(Qt::HANDLE handle)
size_t quintptr
Definition qtypes.h:167
QReadWriteLock lock
[0]