Qt 6.x
The Qt SDK
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 "qelapsedtimer.h"
12#include "private/qfreelist_p.h"
13#include "private/qlocking_p.h"
14
15#include <algorithm>
16
18
19/*
20 * Implementation details of QReadWriteLock:
21 *
22 * Depending on the valued of d_ptr, the lock is in the following state:
23 * - when d_ptr == 0x0: Unlocked (no readers, no writers) and non-recursive.
24 * - when d_ptr & 0x1: If the least significant bit is set, we are locked for read.
25 * In that case, d_ptr>>4 represents the number of reading threads minus 1. No writers
26 * are waiting, and the lock is not recursive.
27 * - when d_ptr == 0x2: We are locked for write and nobody is waiting. (no contention)
28 * - In any other case, d_ptr points to an actual QReadWriteLockPrivate.
29 */
30
31using namespace QReadWriteLockStates;
32namespace {
33
34using steady_clock = std::chrono::steady_clock;
35
36const auto dummyLockedForRead = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForRead));
37const auto dummyLockedForWrite = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(StateLockedForWrite));
38inline bool isUncontendedLocked(const QReadWriteLockPrivate *d)
39{ return quintptr(d) & StateMask; }
40}
41
46
113QReadWriteLockPrivate *QReadWriteLock::initRecursive()
114{
115 auto d = new QReadWriteLockPrivate(true);
116 Q_ASSERT_X(!(quintptr(d) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment");
117 return d;
118}
119
127void QReadWriteLock::destroyRecursive(QReadWriteLockPrivate *d)
128{
129 if (isUncontendedLocked(d)) {
130 qWarning("QReadWriteLock: destroying locked QReadWriteLock");
131 return;
132 }
133 delete d;
134}
135
185bool QReadWriteLock::tryLockForRead(QDeadlineTimer timeout)
186{
187 // Fast case: non contended:
188 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
189 if (d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
190 return true;
191 return contendedTryLockForRead(d_ptr, timeout, d);
192}
193
196{
197 while (true) {
198 if (d == nullptr) {
199 if (!d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
200 continue;
201 return true;
202 }
203
205 // locked for read, increase the counter
206 const auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) + (1U<<4));
207 Q_ASSERT_X(quintptr(val) > (1U<<4), "QReadWriteLock::tryLockForRead()",
208 "Overflow in lock counter");
209 if (!d_ptr.testAndSetAcquire(d, val, d))
210 continue;
211 return true;
212 }
213
214 if (d == dummyLockedForWrite) {
215 if (timeout.hasExpired())
216 return false;
217
218 // locked for write, assign a d_ptr and wait.
220 val->writerCount = 1;
221 if (!d_ptr.testAndSetOrdered(d, val, d)) {
222 val->writerCount = 0;
223 val->release();
224 continue;
225 }
226 d = val;
227 }
228 Q_ASSERT(!isUncontendedLocked(d));
229 // d is an actual pointer;
230
231 if (d->recursive)
232 return d->recursiveLockForRead(timeout);
233
234 auto lock = qt_unique_lock(d->mutex);
235 if (d != d_ptr.loadRelaxed()) {
236 // d_ptr has changed: this QReadWriteLock was unlocked before we had
237 // time to lock d->mutex.
238 // We are holding a lock to a mutex within a QReadWriteLockPrivate
239 // that is already released (or even is already re-used). That's ok
240 // because the QFreeList never frees them.
241 // Just unlock d->mutex (at the end of the scope) and retry.
242 d = d_ptr.loadAcquire();
243 continue;
244 }
245 return d->lockForRead(lock, timeout);
246 }
247}
248
300bool QReadWriteLock::tryLockForWrite(QDeadlineTimer timeout)
301{
302 // Fast case: non contended:
303 QReadWriteLockPrivate *d = d_ptr.loadRelaxed();
304 if (d == nullptr && d_ptr.testAndSetAcquire(nullptr, dummyLockedForWrite, d))
305 return true;
306 return contendedTryLockForWrite(d_ptr, timeout, d);
307}
308
311{
312 while (true) {
313 if (d == nullptr) {
314 if (!d_ptr.testAndSetAcquire(d, dummyLockedForWrite, d))
315 continue;
316 return true;
317 }
318
319 if (isUncontendedLocked(d)) {
320 if (timeout.hasExpired())
321 return false;
322
323 // locked for either read or write, assign a d_ptr and wait.
325 if (d == dummyLockedForWrite)
326 val->writerCount = 1;
327 else
328 val->readerCount = (quintptr(d) >> 4) + 1;
329 if (!d_ptr.testAndSetOrdered(d, val, d)) {
330 val->writerCount = val->readerCount = 0;
331 val->release();
332 continue;
333 }
334 d = val;
335 }
336 Q_ASSERT(!isUncontendedLocked(d));
337 // d is an actual pointer;
338
339 if (d->recursive)
340 return d->recursiveLockForWrite(timeout);
341
342 auto lock = qt_unique_lock(d->mutex);
343 if (d != d_ptr.loadRelaxed()) {
344 // The mutex was unlocked before we had time to lock the mutex.
345 // We are holding to a mutex within a QReadWriteLockPrivate that is already released
346 // (or even is already re-used) but that's ok because the QFreeList never frees them.
347 d = d_ptr.loadAcquire();
348 continue;
349 }
350 return d->lockForWrite(lock, timeout);
351 }
352}
353
362void QReadWriteLock::unlock()
363{
364 QReadWriteLockPrivate *d = d_ptr.loadAcquire();
365 while (true) {
366 Q_ASSERT_X(d, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
367
368 // Fast case: no contention: (no waiters, no other readers)
369 if (quintptr(d) <= 2) { // 1 or 2 (StateLockedForRead or StateLockedForWrite)
370 if (!d_ptr.testAndSetOrdered(d, nullptr, d))
371 continue;
372 return;
373 }
374
376 Q_ASSERT(quintptr(d) > (1U<<4)); //otherwise that would be the fast case
377 // Just decrease the reader's count.
378 auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) - (1U<<4));
379 if (!d_ptr.testAndSetOrdered(d, val, d))
380 continue;
381 return;
382 }
383
384 Q_ASSERT(!isUncontendedLocked(d));
385
386 if (d->recursive) {
387 d->recursiveUnlock();
388 return;
389 }
390
391 const auto lock = qt_scoped_lock(d->mutex);
392 if (d->writerCount) {
393 Q_ASSERT(d->writerCount == 1);
394 Q_ASSERT(d->readerCount == 0);
395 d->writerCount = 0;
396 } else {
397 Q_ASSERT(d->readerCount > 0);
398 d->readerCount--;
399 if (d->readerCount > 0)
400 return;
401 }
402
403 if (d->waitingReaders || d->waitingWriters) {
404 d->unlock();
405 } else {
406 Q_ASSERT(d_ptr.loadRelaxed() == d); // should not change when we still hold the mutex
407 d_ptr.storeRelease(nullptr);
408 d->release();
409 }
410 return;
411 }
412}
413
415{
416 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
417
418 while (waitingWriters || writerCount) {
419 if (timeout.hasExpired())
420 return false;
421 if (!timeout.isForever()) {
423 readerCond.wait_until(lock, timeout.deadline<steady_clock>());
424 } else {
426 readerCond.wait(lock);
427 }
429 }
430 readerCount++;
431 Q_ASSERT(writerCount == 0);
432 return true;
433}
434
436{
437 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
438
439 while (readerCount || writerCount) {
440 if (timeout.hasExpired()) {
442 // We timed out and now there is no more writers or waiting writers, but some
443 // readers were queued (probably because of us). Wake the waiting readers.
444 readerCond.notify_all();
445 }
446 return false;
447 }
448 if (!timeout.isForever()) {
450 writerCond.wait_until(lock, timeout.deadline<steady_clock>());
451 } else {
453 writerCond.wait(lock);
454 }
456 }
457
458 Q_ASSERT(writerCount == 0);
459 Q_ASSERT(readerCount == 0);
460 writerCount = 1;
461 return true;
462}
463
465{
466 Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
467 if (waitingWriters)
468 writerCond.notify_one();
469 else if (waitingReaders)
470 readerCond.notify_all();
471}
472
474{
475 return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
476}
477
479{
481 auto lock = qt_unique_lock(mutex);
482
484
485 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
486 handleEquals(self));
487 if (it != currentReaders.end()) {
488 ++it->recursionLevel;
489 return true;
490 }
491
492 if (!lockForRead(lock, timeout))
493 return false;
494
495 Reader r = {self, 1};
496 currentReaders.append(std::move(r));
497 return true;
498}
499
501{
503 auto lock = qt_unique_lock(mutex);
504
506 if (currentWriter == self) {
507 writerCount++;
508 return true;
509 }
510
512 return false;
513
514 currentWriter = self;
515 return true;
516}
517
519{
521 auto lock = qt_unique_lock(mutex);
522
524 if (self == currentWriter) {
525 if (--writerCount > 0)
526 return;
527 currentWriter = nullptr;
528 } else {
529 auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
530 handleEquals(self));
531 if (it == currentReaders.end()) {
532 qWarning("QReadWriteLock::unlock: unlocking from a thread that did not lock");
533 return;
534 } else {
535 if (--it->recursionLevel <= 0) {
536 currentReaders.erase(it);
537 readerCount--;
538 }
539 if (readerCount)
540 return;
541 }
542 }
543
544 unlock();
545}
546
547// The freelist management
548namespace {
549struct QReadWriteLockFreeListConstants : QFreeListDefaultConstants
550{
551 enum { BlockCount = 4, MaxIndex=0xffff };
552 static const int Sizes[BlockCount];
553};
554Q_CONSTINIT const int
555 QReadWriteLockFreeListConstants::Sizes[QReadWriteLockFreeListConstants::BlockCount] = {
556 16, 128, 1024, QReadWriteLockFreeListConstants::MaxIndex - (16 + 128 + 1024)
557 };
558
560Q_GLOBAL_STATIC(QReadWriteLockFreeList, qrwl_freelist);
561}
562
564{
565 int i = qrwl_freelist->next();
566 QReadWriteLockPrivate *d = &(*qrwl_freelist)[i];
567 d->id = i;
568 Q_ASSERT(!d->recursive);
569 Q_ASSERT(!d->waitingReaders && !d->waitingWriters && !d->readerCount && !d->writerCount);
570 return d;
571}
572
574{
577 qrwl_freelist->release(id);
578}
579
\macro Q_ATOMIC_INTnn_IS_SUPPORTED
Definition qatomic.h:123
bool testAndSetAcquire(Type expectedValue, Type newValue) noexcept
Type loadAcquire() const noexcept
bool testAndSetOrdered(Type expectedValue, Type newValue) noexcept
Type loadRelaxed() const noexcept
\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:154
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:162
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:72
QReadWriteLock lock
[0]