diff options
author | Marc Mutz <marc.mutz@kdab.com> | 2017-02-11 10:10:18 +0100 |
---|---|---|
committer | Marc Mutz <marc.mutz@kdab.com> | 2017-02-14 06:46:03 +0000 |
commit | f0ee4ed0a28d274c7a8efeb9e6ac8642e020e2cc (patch) | |
tree | 5e7d7d42888b948d62241b60d0418bea0009b28e | |
parent | 9c765522d1c4f8090b5f5d391b1740fc4bd67664 (diff) |
Long live QSemaphoreReleaser!
This is a simple RAII class that makes semaphore releasing
reliable in the face of exceptions and early returns.
This code originates from KDTools' KDSemaphoreReleaser[1], but
has been extensively reworked to support C++11 move semantics.
[1] https://docs.kdab.com/kdtools/2.3.0/class_k_d_semaphore_releaser.html
[ChangeLog][QtCore][QSemaphore] Added a new RAII class, QSemaphoreReleaser,
to reliably perform release() calls.
Change-Id: I6aff64d37cc0882b17c4419817bde60b542f34d9
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
Reviewed-by: David Faure <david.faure@kdab.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r-- | src/corelib/thread/qsemaphore.cpp | 153 | ||||
-rw-r--r-- | src/corelib/thread/qsemaphore.h | 39 | ||||
-rw-r--r-- | tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp | 50 |
3 files changed, 240 insertions, 2 deletions
diff --git a/src/corelib/thread/qsemaphore.cpp b/src/corelib/thread/qsemaphore.cpp index 8427b0e696..397d6203aa 100644 --- a/src/corelib/thread/qsemaphore.cpp +++ b/src/corelib/thread/qsemaphore.cpp @@ -94,7 +94,7 @@ QT_BEGIN_NAMESPACE seated (taking the available seats to 5, making the party of 10 people wait longer). - \sa QMutex, QWaitCondition, QThread, {Semaphores Example} + \sa QSemaphoreReleaser, QMutex, QWaitCondition, QThread, {Semaphores Example} */ class QSemaphorePrivate { @@ -152,7 +152,10 @@ void QSemaphore::acquire(int n) \snippet code/src_corelib_thread_qsemaphore.cpp 1 - \sa acquire(), available() + QSemaphoreReleaser is a \l{http://en.cppreference.com/w/cpp/language/raii}{RAII} + wrapper around this function. + + \sa acquire(), available(), QSemaphoreReleaser */ void QSemaphore::release(int n) { @@ -234,6 +237,152 @@ bool QSemaphore::tryAcquire(int n, int timeout) } +/*! + \class QSemaphoreReleaser + \brief The QSemaphoreReleaser class provides exception-safe deferral of a QSemaphore::release() call + \since 5.10 + \ingroup thread + \inmodule QtCore + + \reentrant + + QSemaphoreReleaser can be used wherever you would otherwise use + QSemaphore::release(). Constructing a QSemaphoreReleaser defers the + release() call on the semaphore until the QSemaphoreReleaser is + destroyed (see + \l{http://en.cppreference.com/w/cpp/language/raii}{RAII pattern}). + + You can use this to reliably release a semaphore to avoid dead-lock + in the face of exceptions or early returns: + + \code + // ... do something that may throw or return early + sem.release(); + \endcode + + If an early return is taken or an exception is thrown before the + \c{sem.release()} call is reached, the semaphore is not released, + possibly preventing the thread waiting in the corresponding + \c{sem.acquire()} call from ever continuing execution. + + When using RAII instead: + + \code + const QSemaphoreReleaser releaser(sem); + // ... do something that may throw or early return + // implicitly calls sem.release() here and at every other return in between + \endcode + + this can no longer happen, because the compiler will make sure that + the QSemaphoreReleaser destructor is always called, and therefore + the semaphore is always released. + + QSemaphoreReleaser is move-enabled and can therefore be returned + from functions to transfer responsibility for releasing a semaphore + out of a function or a scope: + + \code + { // some scope + QSemaphoreReleaser releaser; // does nothing + // ... + if (someCondition) { + releaser = QSemaphoreReleaser(sem); + // ... + } + // ... + } // conditionally calls sem.release(), depending on someCondition + \endcode + + A QSemaphoreReleaser can be canceled by a call to cancel(). A canceled + semaphore releaser will no longer call QSemaphore::release() in its + destructor. + + \sa QMutexLocker +*/ + +/*! + \fn QSemaphoreReleaser::QSemaphoreReleaser() + + Default constructor. Creates a QSemaphoreReleaser that does nothing. +*/ + +/*! + \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphore &sem, int n) + + Constructor. Stores the arguments and calls \a{sem}.release(\a{n}) + in the destructor. +*/ + +/*! + \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphore *sem, int n) + + Constructor. Stores the arguments and calls \a{sem}->release(\a{n}) + in the destructor. +*/ + +/*! + \fn QSemaphoreReleaser::QSemaphoreReleaser(QSemaphoreReleaser &&other) + + Move constructor. Takes over responsibility to call QSemaphore::release() + from \a other, which in turn is canceled. + + \sa cancel() +*/ + +/*! + \fn QSemaphoreReleaser::operator=(QSemaphoreReleaser &&other) + + Move assignment operator. Takes over responsibility to call QSemaphore::release() + from \a other, which in turn is canceled. + + If this semaphore releaser had the responsibility to call some QSemaphore::release() + itself, it performs the call before taking over from \a other. + + \sa cancel() +*/ + +/*! + \fn QSemaphoreReleaser::~QSemaphoreReleaser() + + Unless canceled, calls QSemaphore::release() with the arguments provided + to the constructor, or by the last move assignment. +*/ + +/*! + \fn QSemaphoreReleaser::swap(QSemaphoreReleaser &other) + + Exchanges the responsibilites of \c{*this} and \a other. + + Unlike move assignment, neither of the two objects ever releases its + semaphore, if any, as a consequence of swapping. + + Therefore this function is very fast and never fails. +*/ + +/*! + \fn QSemaphoreReleaser::semaphore() const + + Returns a pointer to the QSemaphore object provided to the constructor, + or by the last move assignment, if any. Otherwise, returns \c nullptr. +*/ + +/*! + \fn QSemaphoreReleaser::cancel() + + Cancels this QSemaphoreReleaser such that the destructor will no longer + call \c{semaphore()->release()}. Returns the value of semaphore() + before this call. After this call, semaphore() will return \c nullptr. + + To enable again, assign a new QSemaphoreReleaser: + + \code + releaser.cancel(); // avoid releasing old semaphore() + releaser = QSemaphoreReleaser(sem, 42); + // now will call sem.release(42) when 'releaser' is destroyed + \endcode +*/ + + QT_END_NAMESPACE #endif // QT_NO_THREAD diff --git a/src/corelib/thread/qsemaphore.h b/src/corelib/thread/qsemaphore.h index adb9d73e50..a92740c8ce 100644 --- a/src/corelib/thread/qsemaphore.h +++ b/src/corelib/thread/qsemaphore.h @@ -69,6 +69,45 @@ private: QSemaphorePrivate *d; }; +class QSemaphoreReleaser +{ + QSemaphore *m_sem = nullptr; + int m_n; +public: + QSemaphoreReleaser() = default; + explicit QSemaphoreReleaser(QSemaphore &sem, int n = 1) Q_DECL_NOTHROW + : m_sem(&sem), m_n(n) {} + explicit QSemaphoreReleaser(QSemaphore *sem, int n = 1) Q_DECL_NOTHROW + : m_sem(sem), m_n(n) {} + QSemaphoreReleaser(QSemaphoreReleaser &&other) Q_DECL_NOTHROW + : m_sem(other.m_sem), m_n(other.m_n) + { other.m_sem = nullptr; } + QSemaphoreReleaser &operator=(QSemaphoreReleaser &&other) Q_DECL_NOTHROW + { QSemaphoreReleaser moved(std::move(other)); swap(moved); return *this; } + + ~QSemaphoreReleaser() + { + if (m_sem) + m_sem->release(m_n); + } + + void swap(QSemaphoreReleaser &other) Q_DECL_NOTHROW + { + qSwap(m_sem, other.m_sem); + qSwap(m_n, other.m_n); + } + + QSemaphore *semaphore() const Q_DECL_NOTHROW + { return m_sem; } + + QSemaphore *cancel() Q_DECL_NOTHROW + { + QSemaphore *old = m_sem; + m_sem = nullptr; + return old; + } +}; + #endif // QT_NO_THREAD QT_END_NAMESPACE diff --git a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp index f1eb32a282..a33417c7da 100644 --- a/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp +++ b/tests/auto/corelib/thread/qsemaphore/tst_qsemaphore.cpp @@ -42,6 +42,7 @@ private slots: void tryAcquireWithTimeout(); void tryAcquireWithTimeoutStarvation(); void producerConsumer(); + void raii(); }; static QSemaphore *semaphore = 0; @@ -415,5 +416,54 @@ void tst_QSemaphore::producerConsumer() consumer.wait(); } +void tst_QSemaphore::raii() +{ + QSemaphore sem; + + QCOMPARE(sem.available(), 0); + + // basic operation: + { + QSemaphoreReleaser r0; + const QSemaphoreReleaser r1(sem); + const QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(r0.semaphore(), nullptr); + QCOMPARE(r1.semaphore(), &sem); + QCOMPARE(r2.semaphore(), &sem); + } + + QCOMPARE(sem.available(), 3); + + // cancel: + { + const QSemaphoreReleaser r1(sem); + QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(r2.cancel(), &sem); + QCOMPARE(r2.semaphore(), nullptr); + } + + QCOMPARE(sem.available(), 4); + + // move-assignment: + { + const QSemaphoreReleaser r1(sem); + QSemaphoreReleaser r2(sem, 2); + + QCOMPARE(sem.available(), 4); + + r2 = QSemaphoreReleaser(); + + QCOMPARE(sem.available(), 6); + + r2 = QSemaphoreReleaser(sem, 42); + + QCOMPARE(sem.available(), 6); + } + + QCOMPARE(sem.available(), 49); +} + QTEST_MAIN(tst_QSemaphore) #include "tst_qsemaphore.moc" |