QtBase  v6.3.1
qreadwritelock.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2016 Intel Corporation.
5 ** Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
6 ** Contact: https://www.qt.io/licensing/
7 **
8 ** This file is part of the QtCore module of the Qt Toolkit.
9 **
10 ** $QT_BEGIN_LICENSE:LGPL$
11 ** Commercial License Usage
12 ** Licensees holding valid commercial Qt licenses may use this file in
13 ** accordance with the commercial license agreement provided with the
14 ** Software or, alternatively, in accordance with the terms contained in
15 ** a written agreement between you and The Qt Company. For licensing terms
16 ** and conditions see https://www.qt.io/terms-conditions. For further
17 ** information use the contact form at https://www.qt.io/contact-us.
18 **
19 ** GNU Lesser General Public License Usage
20 ** Alternatively, this file may be used under the terms of the GNU Lesser
21 ** General Public License version 3 as published by the Free Software
22 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
23 ** packaging of this file. Please review the following information to
24 ** ensure the GNU Lesser General Public License version 3 requirements
25 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26 **
27 ** GNU General Public License Usage
28 ** Alternatively, this file may be used under the terms of the GNU
29 ** General Public License version 2.0 or (at your option) the GNU General
30 ** Public license version 3 or any later version approved by the KDE Free
31 ** Qt Foundation. The licenses are as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33 ** included in the packaging of this file. Please review the following
34 ** information to ensure the GNU General Public License requirements will
35 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36 ** https://www.gnu.org/licenses/gpl-3.0.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qplatformdefs.h"
43 #include "qreadwritelock.h"
44 
45 #include "qmutex.h"
46 #include "qthread.h"
47 #include "qwaitcondition.h"
48 #include "qreadwritelock_p.h"
49 #include "qelapsedtimer.h"
50 #include "private/qfreelist_p.h"
51 #include "private/qlocking_p.h"
52 
53 #include <algorithm>
54 
56 
57 /*
58  * Implementation details of QReadWriteLock:
59  *
60  * Depending on the valued of d_ptr, the lock is in the following state:
61  * - when d_ptr == 0x0: Unlocked (no readers, no writers) and non-recursive.
62  * - when d_ptr & 0x1: If the least significant bit is set, we are locked for read.
63  * In that case, d_ptr>>4 represents the number of reading threads minus 1. No writers
64  * are waiting, and the lock is not recursive.
65  * - when d_ptr == 0x2: We are locked for write and nobody is waiting. (no contention)
66  * - In any other case, d_ptr points to an actual QReadWriteLockPrivate.
67  */
68 
69 namespace {
70 
71 using ms = std::chrono::milliseconds;
72 
73 enum {
74  StateMask = 0x3,
77 };
81 { return quintptr(d) & StateMask; }
82 }
83 
149 QReadWriteLock::QReadWriteLock(RecursionMode recursionMode)
150  : d_ptr(recursionMode == Recursive ? new QReadWriteLockPrivate(true) : nullptr)
151 {
152  Q_ASSERT_X(!(quintptr(d_ptr.loadRelaxed()) & StateMask), "QReadWriteLock::QReadWriteLock", "bad d_ptr alignment");
153 }
154 
162 {
163  auto d = d_ptr.loadAcquire();
164  if (isUncontendedLocked(d)) {
165  qWarning("QReadWriteLock: destroying locked QReadWriteLock");
166  return;
167  }
168  delete d;
169 }
170 
181 {
182  if (d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead))
183  return;
184  tryLockForRead(-1);
185 }
186 
204 {
205  return tryLockForRead(0);
206 }
207 
228 {
229  // Fast case: non contended:
231  if (d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
232  return true;
233 
234  while (true) {
235  if (d == nullptr) {
236  if (!d_ptr.testAndSetAcquire(nullptr, dummyLockedForRead, d))
237  continue;
238  return true;
239  }
240 
241  if ((quintptr(d) & StateMask) == StateLockedForRead) {
242  // locked for read, increase the counter
243  const auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) + (1U<<4));
244  Q_ASSERT_X(quintptr(val) > (1U<<4), "QReadWriteLock::tryLockForRead()",
245  "Overflow in lock counter");
246  if (!d_ptr.testAndSetAcquire(d, val, d))
247  continue;
248  return true;
249  }
250 
251  if (d == dummyLockedForWrite) {
252  if (!timeout)
253  return false;
254 
255  // locked for write, assign a d_ptr and wait.
257  val->writerCount = 1;
258  if (!d_ptr.testAndSetOrdered(d, val, d)) {
259  val->writerCount = 0;
260  val->release();
261  continue;
262  }
263  d = val;
264  }
266  // d is an actual pointer;
267 
268  if (d->recursive)
269  return d->recursiveLockForRead(timeout);
270 
271  auto lock = qt_unique_lock(d->mutex);
272  if (d != d_ptr.loadRelaxed()) {
273  // d_ptr has changed: this QReadWriteLock was unlocked before we had
274  // time to lock d->mutex.
275  // We are holding a lock to a mutex within a QReadWriteLockPrivate
276  // that is already released (or even is already re-used). That's ok
277  // because the QFreeList never frees them.
278  // Just unlock d->mutex (at the end of the scope) and retry.
279  d = d_ptr.loadAcquire();
280  continue;
281  }
282  return d->lockForRead(lock, timeout);
283  }
284 }
285 
298 {
299  tryLockForWrite(-1);
300 }
301 
318 {
319  return tryLockForWrite(0);
320 }
321 
342 {
343  // Fast case: non contended:
345  if (d_ptr.testAndSetAcquire(nullptr, dummyLockedForWrite, d))
346  return true;
347 
348  while (true) {
349  if (d == nullptr) {
350  if (!d_ptr.testAndSetAcquire(d, dummyLockedForWrite, d))
351  continue;
352  return true;
353  }
354 
355  if (isUncontendedLocked(d)) {
356  if (!timeout)
357  return false;
358 
359  // locked for either read or write, assign a d_ptr and wait.
361  if (d == dummyLockedForWrite)
362  val->writerCount = 1;
363  else
364  val->readerCount = (quintptr(d) >> 4) + 1;
365  if (!d_ptr.testAndSetOrdered(d, val, d)) {
366  val->writerCount = val->readerCount = 0;
367  val->release();
368  continue;
369  }
370  d = val;
371  }
373  // d is an actual pointer;
374 
375  if (d->recursive)
376  return d->recursiveLockForWrite(timeout);
377 
378  auto lock = qt_unique_lock(d->mutex);
379  if (d != d_ptr.loadRelaxed()) {
380  // The mutex was unlocked before we had time to lock the mutex.
381  // We are holding to a mutex within a QReadWriteLockPrivate that is already released
382  // (or even is already re-used) but that's ok because the QFreeList never frees them.
383  d = d_ptr.loadAcquire();
384  continue;
385  }
386  return d->lockForWrite(lock, timeout);
387  }
388 }
389 
399 {
400  QReadWriteLockPrivate *d = d_ptr.loadAcquire();
401  while (true) {
402  Q_ASSERT_X(d, "QReadWriteLock::unlock()", "Cannot unlock an unlocked lock");
403 
404  // Fast case: no contention: (no waiters, no other readers)
405  if (quintptr(d) <= 2) { // 1 or 2 (StateLockedForRead or StateLockedForWrite)
406  if (!d_ptr.testAndSetOrdered(d, nullptr, d))
407  continue;
408  return;
409  }
410 
411  if ((quintptr(d) & StateMask) == StateLockedForRead) {
412  Q_ASSERT(quintptr(d) > (1U<<4)); //otherwise that would be the fast case
413  // Just decrease the reader's count.
414  auto val = reinterpret_cast<QReadWriteLockPrivate *>(quintptr(d) - (1U<<4));
415  if (!d_ptr.testAndSetOrdered(d, val, d))
416  continue;
417  return;
418  }
419 
421 
422  if (d->recursive) {
423  d->recursiveUnlock();
424  return;
425  }
426 
427  const auto lock = qt_scoped_lock(d->mutex);
428  if (d->writerCount) {
429  Q_ASSERT(d->writerCount == 1);
430  Q_ASSERT(d->readerCount == 0);
431  d->writerCount = 0;
432  } else {
433  Q_ASSERT(d->readerCount > 0);
434  d->readerCount--;
435  if (d->readerCount > 0)
436  return;
437  }
438 
439  if (d->waitingReaders || d->waitingWriters) {
440  d->unlock();
441  } else {
442  Q_ASSERT(d_ptr.loadRelaxed() == d); // should not change when we still hold the mutex
443  d_ptr.storeRelease(nullptr);
444  d->release();
445  }
446  return;
447  }
448 }
449 
451 QReadWriteLock::StateForWaitCondition QReadWriteLock::stateForWaitCondition() const
452 {
453  QReadWriteLockPrivate *d = d_ptr.loadAcquire();
454  switch (quintptr(d) & StateMask) {
455  case StateLockedForRead: return LockedForRead;
456  case StateLockedForWrite: return LockedForWrite;
457  }
458 
459  if (!d)
460  return Unlocked;
461  const auto lock = qt_scoped_lock(d->mutex);
462  if (d->writerCount > 1)
463  return RecursivelyLocked;
464  else if (d->writerCount == 1)
465  return LockedForWrite;
466  return LockedForRead;
467 
468 }
469 
470 bool QReadWriteLockPrivate::lockForRead(std::unique_lock<QtPrivate::mutex> &lock, int timeout)
471 {
472  Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
473 
475  if (timeout > 0)
476  t.start();
477 
478  while (waitingWriters || writerCount) {
479  if (timeout == 0)
480  return false;
481  if (timeout > 0) {
482  auto elapsed = t.elapsed();
483  if (elapsed > timeout)
484  return false;
485  waitingReaders++;
486  readerCond.wait_for(lock, ms{timeout - elapsed});
487  } else {
488  waitingReaders++;
490  }
491  waitingReaders--;
492  }
493  readerCount++;
494  Q_ASSERT(writerCount == 0);
495  return true;
496 }
497 
498 bool QReadWriteLockPrivate::lockForWrite(std::unique_lock<QtPrivate::mutex> &lock, int timeout)
499 {
500  Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
501 
503  if (timeout > 0)
504  t.start();
505 
506  while (readerCount || writerCount) {
507  if (timeout == 0)
508  return false;
509  if (timeout > 0) {
510  auto elapsed = t.elapsed();
511  if (elapsed > timeout) {
513  // We timed out and now there is no more writers or waiting writers, but some
514  // readers were queued (probably because of us). Wake the waiting readers.
516  }
517  return false;
518  }
519  waitingWriters++;
520  writerCond.wait_for(lock, ms{timeout - elapsed});
521  } else {
522  waitingWriters++;
524  }
525  waitingWriters--;
526  }
527 
528  Q_ASSERT(writerCount == 0);
529  Q_ASSERT(readerCount == 0);
530  writerCount = 1;
531  return true;
532 }
533 
535 {
536  Q_ASSERT(!mutex.try_lock()); // mutex must be locked when entering this function
537  if (waitingWriters)
539  else if (waitingReaders)
541 }
542 
543 static auto handleEquals(Qt::HANDLE handle)
544 {
545  return [handle](QReadWriteLockPrivate::Reader reader) { return reader.handle == handle; };
546 }
547 
549 {
551  auto lock = qt_unique_lock(mutex);
552 
554 
555  auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
556  handleEquals(self));
557  if (it != currentReaders.end()) {
558  ++it->recursionLevel;
559  return true;
560  }
561 
562  if (!lockForRead(lock, timeout))
563  return false;
564 
565  Reader r = {self, 1};
566  currentReaders.append(std::move(r));
567  return true;
568 }
569 
571 {
573  auto lock = qt_unique_lock(mutex);
574 
576  if (currentWriter == self) {
577  writerCount++;
578  return true;
579  }
580 
581  if (!lockForWrite(lock, timeout))
582  return false;
583 
584  currentWriter = self;
585  return true;
586 }
587 
589 {
591  auto lock = qt_unique_lock(mutex);
592 
594  if (self == currentWriter) {
595  if (--writerCount > 0)
596  return;
597  currentWriter = nullptr;
598  } else {
599  auto it = std::find_if(currentReaders.begin(), currentReaders.end(),
600  handleEquals(self));
601  if (it == currentReaders.end()) {
602  qWarning("QReadWriteLock::unlock: unlocking from a thread that did not lock");
603  return;
604  } else {
605  if (--it->recursionLevel <= 0) {
606  currentReaders.erase(it);
607  readerCount--;
608  }
609  if (readerCount)
610  return;
611  }
612  }
613 
614  unlock();
615 }
616 
617 // The freelist management
618 namespace {
619 struct FreeListConstants : QFreeListDefaultConstants {
620  enum { BlockCount = 4, MaxIndex=0xffff };
621  static const int Sizes[BlockCount];
622 };
623 const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
624  16,
625  128,
626  1024,
627  FreeListConstants::MaxIndex - (16 + 128 + 1024)
628 };
629 
631 Q_GLOBAL_STATIC(FreeList, freelist);
632 }
633 
635 {
636  int i = freelist->next();
637  QReadWriteLockPrivate *d = &(*freelist)[i];
638  d->id = i;
639  Q_ASSERT(!d->recursive);
640  Q_ASSERT(!d->waitingReaders && !d->waitingWriters && !d->readerCount && !d->writerCount);
641  return d;
642 }
643 
645 {
648  freelist->release(id);
649 }
650 
small capitals from c petite p scientific i
[1]
Definition: afcover.h:80
The QElapsedTimer class provides a fast way to calculate elapsed times.
Definition: qelapsedtimer.h:49
void lockForRead() noexcept
bool tryLockForWrite() noexcept
QReadWriteLock(RecursionMode=NonRecursive) noexcept
bool tryLockForRead() noexcept
void unlock() noexcept
void lockForWrite() noexcept
QtPrivate::condition_variable readerCond
bool lockForWrite(std::unique_lock< QtPrivate::mutex > &lock, int timeout)
bool recursiveLockForRead(int timeout)
QtPrivate::condition_variable writerCond
bool recursiveLockForWrite(int timeout)
QtPrivate::mutex mutex
static QReadWriteLockPrivate * allocate()
bool lockForRead(std::unique_lock< QtPrivate::mutex > &lock, int timeout)
QVarLengthArray< Reader, 16 > currentReaders
static Qt::HANDLE currentThreadId() noexcept Q_DECL_PURE_FUNCTION
Definition: qthread.h:187
cv_status wait_for(std::unique_lock< QtPrivate::mutex > &lock, const std::chrono::duration< Rep, Period > &d)
void wait(std::unique_lock< QtPrivate::mutex > &lock)
#define true
Definition: ftrandom.c:51
std::chrono::milliseconds ms
Lock qt_scoped_lock(Mutex &mutex)
Definition: qlocking_p.h:94
Lock qt_unique_lock(Mutex &mutex)
Definition: qlocking_p.h:100
bool isUncontendedLocked(const QReadWriteLockPrivate *d)
const auto dummyLockedForRead
const auto dummyLockedForWrite
void * HANDLE
Definition: qnamespace.h:1561
QImageReader reader("image.png")
[1]
size_t quintptr
Definition: qglobal.h:310
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition: qlogging.h:179
GLuint64 GLenum void * handle
GLboolean r
[2]
GLbitfield GLuint64 timeout
[4]
GLuint GLfloat * val
Definition: qopenglext.h:1513
GLdouble GLdouble t
[9]
Definition: qopenglext.h:243
#define Q_ASSERT(cond)
Definition: qrandom.cpp:84
#define Q_ASSERT_X(cond, x, msg)
Definition: qrandom.cpp:85
QObject::connect nullptr
QReadWriteLock lock
[0]
QStringList::Iterator it