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>
This commit is contained in:
parent
9c765522d1
commit
f0ee4ed0a2
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user