QSemaphore: add QDeadlineTimer API
This removes the last use of QtPrivate::convertToMilliseconds(). Change-Id: I6f518d59e63249ddbf43fffd1759fee2e00d36f4 Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io> Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
This commit is contained in:
parent
fda4da6df9
commit
37f1fb78ee
@ -605,6 +605,7 @@ QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const
|
||||
#if QT_CONFIG(thread)
|
||||
#include "qmutex.h"
|
||||
#include "qreadwritelock.h"
|
||||
#include "qsemaphore.h"
|
||||
#endif
|
||||
|
||||
// #include "qotherheader.h"
|
||||
|
@ -35,6 +35,12 @@ sem.tryAcquire(250, 1000); // sem.available() == 5, waits 1000 milliseconds a
|
||||
sem.tryAcquire(3, 30000); // sem.available() == 2, returns true without waiting
|
||||
//! [3]
|
||||
|
||||
//! [tryAcquire-QDeadlineTimer]
|
||||
QSemaphore sem(5); // sem.available() == 5
|
||||
sem.tryAcquire(250, QDeadlineTimer(1000)); // sem.available() == 5, waits 1000 milliseconds and returns false
|
||||
sem.tryAcquire(3, QDeadlineTimer(30s)); // sem.available() == 2, returns true without waiting
|
||||
//! [tryAcquire-QDeadlineTimer]
|
||||
|
||||
//! [4]
|
||||
// ... do something that may throw or return early
|
||||
sem.release();
|
||||
|
@ -8,10 +8,6 @@
|
||||
#include <QtCore/qatomic.h>
|
||||
#include <QtCore/qdeadlinetimer.h>
|
||||
#include <QtCore/qtsan_impl.h>
|
||||
#include <new>
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -27,32 +23,6 @@ class QMutex;
|
||||
class QRecursiveMutex;
|
||||
class QMutexPrivate;
|
||||
|
||||
namespace QtPrivate
|
||||
{
|
||||
template<class Rep, class Period>
|
||||
static int convertToMilliseconds(std::chrono::duration<Rep, Period> duration)
|
||||
{
|
||||
// N4606 § 30.4.1.3.5 [thread.timedmutex.requirements] specifies that a
|
||||
// duration less than or equal to duration.zero() shall result in a
|
||||
// try_lock, unlike QMutex's tryLock with a negative duration which
|
||||
// results in a lock.
|
||||
|
||||
if (duration <= duration.zero())
|
||||
return 0;
|
||||
|
||||
// when converting from 'duration' to milliseconds, make sure that
|
||||
// the result is not shorter than 'duration':
|
||||
std::chrono::milliseconds wait = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
|
||||
if (wait < duration)
|
||||
wait += std::chrono::milliseconds(1);
|
||||
Q_ASSERT(wait >= duration);
|
||||
const auto ms = wait.count();
|
||||
const auto maxInt = (std::numeric_limits<int>::max)();
|
||||
|
||||
return ms < maxInt ? int(ms) : maxInt;
|
||||
}
|
||||
}
|
||||
|
||||
class Q_CORE_EXPORT QBasicMutex
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(QBasicMutex)
|
||||
|
@ -145,10 +145,10 @@ static QBasicAtomicInteger<quint32> *futexHigh32(QBasicAtomicInteger<quintptr> *
|
||||
}
|
||||
|
||||
template <bool IsTimed> bool
|
||||
futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn, int timeout)
|
||||
futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValue, quintptr nn,
|
||||
QDeadlineTimer timer)
|
||||
{
|
||||
QDeadlineTimer timer(IsTimed ? QDeadlineTimer(timeout) : QDeadlineTimer());
|
||||
qint64 remainingTime = timeout * Q_INT64_C(1000) * 1000;
|
||||
qint64 remainingTime = IsTimed ? timer.remainingTimeNSecs() : -1;
|
||||
int n = int(unsigned(nn));
|
||||
|
||||
// we're called after one testAndSet, so start by waiting first
|
||||
@ -190,8 +190,13 @@ futexSemaphoreTryAcquire_loop(QBasicAtomicInteger<quintptr> &u, quintptr curValu
|
||||
}
|
||||
}
|
||||
|
||||
template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, int timeout)
|
||||
static constexpr QDeadlineTimer::ForeverConstant Expired =
|
||||
QDeadlineTimer::ForeverConstant(1);
|
||||
|
||||
template <typename T> bool
|
||||
futexSemaphoreTryAcquire(QBasicAtomicInteger<quintptr> &u, int n, T timeout)
|
||||
{
|
||||
constexpr bool IsTimed = std::is_same_v<QDeadlineTimer, T>;
|
||||
// Try to acquire without waiting (we still loop because the testAndSet
|
||||
// call can fail).
|
||||
quintptr nn = unsigned(n);
|
||||
@ -205,8 +210,13 @@ template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintp
|
||||
if (u.testAndSetOrdered(curValue, newValue, curValue))
|
||||
return true; // succeeded!
|
||||
}
|
||||
if (timeout == 0)
|
||||
return false;
|
||||
if constexpr (IsTimed) {
|
||||
if (timeout.hasExpired())
|
||||
return false;
|
||||
} else {
|
||||
if (timeout == Expired)
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to wait
|
||||
constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit
|
||||
@ -293,7 +303,7 @@ void QSemaphore::acquire(int n)
|
||||
Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative");
|
||||
|
||||
if (futexAvailable()) {
|
||||
futexSemaphoreTryAcquire<false>(u, n, -1);
|
||||
futexSemaphoreTryAcquire(u, n, QDeadlineTimer::Forever);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -409,7 +419,7 @@ bool QSemaphore::tryAcquire(int n)
|
||||
Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
|
||||
|
||||
if (futexAvailable())
|
||||
return futexSemaphoreTryAcquire<false>(u, n, 0);
|
||||
return futexSemaphoreTryAcquire(u, n, Expired);
|
||||
|
||||
const auto locker = qt_scoped_lock(d->mutex);
|
||||
if (n > d->avail)
|
||||
@ -419,6 +429,8 @@ bool QSemaphore::tryAcquire(int n)
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn QSemaphore::tryAcquire(int n, int timeout)
|
||||
|
||||
Tries to acquire \c n resources guarded by the semaphore and
|
||||
returns \c true on success. If available() < \a n, this call will
|
||||
wait for at most \a timeout milliseconds for resources to become
|
||||
@ -434,26 +446,40 @@ bool QSemaphore::tryAcquire(int n)
|
||||
|
||||
\sa acquire()
|
||||
*/
|
||||
bool QSemaphore::tryAcquire(int n, int timeout)
|
||||
|
||||
/*!
|
||||
\since 6.6
|
||||
|
||||
Tries to acquire \c n resources guarded by the semaphore and returns \c
|
||||
true on success. If available() < \a n, this call will wait until \a timer
|
||||
expires for resources to become available.
|
||||
|
||||
Example:
|
||||
|
||||
\snippet code/src_corelib_thread_qsemaphore.cpp tryAcquire-QDeadlineTimer
|
||||
|
||||
\sa acquire()
|
||||
*/
|
||||
bool QSemaphore::tryAcquire(int n, QDeadlineTimer timer)
|
||||
{
|
||||
if (timeout < 0) {
|
||||
if (timer.isForever()) {
|
||||
acquire(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (timeout == 0)
|
||||
if (timer.hasExpired())
|
||||
return tryAcquire(n);
|
||||
|
||||
Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
|
||||
|
||||
if (futexAvailable())
|
||||
return futexSemaphoreTryAcquire<true>(u, n, timeout);
|
||||
return futexSemaphoreTryAcquire(u, n, timer);
|
||||
|
||||
using namespace std::chrono;
|
||||
const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
|
||||
|
||||
auto locker = qt_unique_lock(d->mutex);
|
||||
if (!d->cond.wait_for(locker, milliseconds{timeout}, sufficientResourcesAvailable))
|
||||
if (!d->cond.wait_until(locker, timer.deadline<steady_clock>(), sufficientResourcesAvailable))
|
||||
return false;
|
||||
d->avail -= n;
|
||||
return true;
|
||||
|
@ -5,16 +5,13 @@
|
||||
#define QSEMAPHORE_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qmutex.h> // for convertToMilliseconds()
|
||||
|
||||
#include <chrono>
|
||||
#include <QtCore/qdeadlinetimer.h>
|
||||
|
||||
QT_REQUIRE_CONFIG(thread);
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QSemaphorePrivate;
|
||||
|
||||
class Q_CORE_EXPORT QSemaphore
|
||||
{
|
||||
public:
|
||||
@ -23,10 +20,19 @@ public:
|
||||
|
||||
void acquire(int n = 1);
|
||||
bool tryAcquire(int n = 1);
|
||||
QT_CORE_INLINE_SINCE(6, 6)
|
||||
bool tryAcquire(int n, int timeout);
|
||||
bool tryAcquire(int n, QDeadlineTimer timeout);
|
||||
#ifndef Q_QDOC
|
||||
// because tryAcquire(n, QDeadlineTimer::Forever) is tryLock(n, 0)
|
||||
bool tryAcquire(int n, QDeadlineTimer::ForeverConstant)
|
||||
{ acquire(n); return true; }
|
||||
#endif
|
||||
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
|
||||
template <typename Rep, typename Period>
|
||||
bool tryAcquire(int n, std::chrono::duration<Rep, Period> timeout)
|
||||
{ return tryAcquire(n, QtPrivate::convertToMilliseconds(timeout)); }
|
||||
{ return tryAcquire(n, QDeadlineTimer(timeout)); }
|
||||
#endif
|
||||
|
||||
void release(int n = 1);
|
||||
|
||||
@ -53,6 +59,13 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
#if QT_CORE_INLINE_IMPL_SINCE(6, 6)
|
||||
bool QSemaphore::tryAcquire(int n, int timeout)
|
||||
{
|
||||
return tryAcquire(n, QDeadlineTimer(timeout));
|
||||
}
|
||||
#endif
|
||||
|
||||
class QSemaphoreReleaser
|
||||
{
|
||||
public:
|
||||
|
@ -29,8 +29,6 @@ public:
|
||||
Q_ENUM(TimeUnit)
|
||||
|
||||
private slots:
|
||||
void convertToMilliseconds_data();
|
||||
void convertToMilliseconds();
|
||||
void tryLock_non_recursive();
|
||||
void try_lock_for_non_recursive();
|
||||
void try_lock_until_non_recursive();
|
||||
@ -71,104 +69,6 @@ enum {
|
||||
|
||||
static constexpr std::chrono::milliseconds waitTimeAsDuration(waitTime);
|
||||
|
||||
void tst_QMutex::convertToMilliseconds_data()
|
||||
{
|
||||
QTest::addColumn<TimeUnit>("unit");
|
||||
QTest::addColumn<double>("doubleValue");
|
||||
QTest::addColumn<qint64>("intValue");
|
||||
QTest::addColumn<qint64>("expected");
|
||||
|
||||
auto add = [](TimeUnit unit, double d, long long i, qint64 expected) {
|
||||
const QScopedArrayPointer<char> enumName(QTest::toString(unit));
|
||||
QTest::addRow("%s:%f:%lld", enumName.data(), d, i)
|
||||
<< unit << d << qint64(i) << expected;
|
||||
};
|
||||
|
||||
auto forAllUnitsAdd = [=](double d, long long i, qint64 expected) {
|
||||
for (auto unit : {TimeUnit::Nanoseconds, TimeUnit::Microseconds, TimeUnit::Milliseconds, TimeUnit::Seconds})
|
||||
add(unit, d, i, expected);
|
||||
};
|
||||
|
||||
forAllUnitsAdd(-0.5, -1, 0); // all negative values result in 0
|
||||
|
||||
forAllUnitsAdd(0, 0, 0);
|
||||
|
||||
add(TimeUnit::Nanoseconds, 1, 1, 1);
|
||||
add(TimeUnit::Nanoseconds, 1000 * 1000, 1000 * 1000, 1);
|
||||
add(TimeUnit::Nanoseconds, 1000 * 1000 + 0.5, 1000 * 1000 + 1, 2);
|
||||
|
||||
add(TimeUnit::Microseconds, 1, 1, 1);
|
||||
add(TimeUnit::Microseconds, 1000, 1000, 1);
|
||||
add(TimeUnit::Microseconds, 1000 + 0.5, 1000 + 1, 2);
|
||||
|
||||
add(TimeUnit::Milliseconds, 1, 1, 1);
|
||||
add(TimeUnit::Milliseconds, 1.5, 2, 2);
|
||||
|
||||
add(TimeUnit::Seconds, 0.9991, 1, 1000);
|
||||
|
||||
//
|
||||
// overflowing int results in INT_MAX (equivalent to a spurious wakeup after ~24 days); check it:
|
||||
//
|
||||
|
||||
// spot on:
|
||||
add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000, INT_MAX * Q_INT64_C(1000) * 1000, INT_MAX);
|
||||
add(TimeUnit::Microseconds, INT_MAX * 1000., INT_MAX * Q_INT64_C(1000), INT_MAX);
|
||||
add(TimeUnit::Milliseconds, INT_MAX, INT_MAX, INT_MAX);
|
||||
|
||||
// minimally above:
|
||||
add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000 + 1, INT_MAX * Q_INT64_C(1000) * 1000 + 1, INT_MAX);
|
||||
add(TimeUnit::Microseconds, INT_MAX * 1000. + 1, INT_MAX * Q_INT64_C(1000) + 1, INT_MAX);
|
||||
add(TimeUnit::Milliseconds, INT_MAX + 1., INT_MAX + Q_INT64_C(1), INT_MAX);
|
||||
add(TimeUnit::Seconds, INT_MAX / 1000. + 1, INT_MAX / 1000 + 1, INT_MAX);
|
||||
|
||||
// minimally below:
|
||||
add(TimeUnit::Nanoseconds, INT_MAX * 1000. * 1000 - 1, INT_MAX * Q_INT64_C(1000) * 1000 - 1, INT_MAX);
|
||||
add(TimeUnit::Microseconds, INT_MAX * 1000. - 1, INT_MAX * Q_INT64_C(1000) - 1, INT_MAX);
|
||||
add(TimeUnit::Milliseconds, INT_MAX - 0.1, INT_MAX , INT_MAX);
|
||||
|
||||
}
|
||||
|
||||
void tst_QMutex::convertToMilliseconds()
|
||||
{
|
||||
QFETCH(TimeUnit, unit);
|
||||
QFETCH(double, doubleValue);
|
||||
QFETCH(qint64, intValue);
|
||||
QFETCH(qint64, expected);
|
||||
|
||||
constexpr qint64 maxShort = std::numeric_limits<short>::max();
|
||||
constexpr qint64 maxInt = std::numeric_limits<int>::max();
|
||||
constexpr qint64 maxUInt = std::numeric_limits<uint>::max();
|
||||
|
||||
switch (unit) {
|
||||
#define CASE(Unit, Period) \
|
||||
case TimeUnit::Unit: \
|
||||
DO(double, Period, doubleValue); \
|
||||
if (intValue < maxShort) \
|
||||
DO(short, Period, short(intValue)); \
|
||||
if (intValue < maxInt) \
|
||||
DO(int, Period, int(intValue)); \
|
||||
DO(qint64, Period, intValue); \
|
||||
if (intValue >= 0) { \
|
||||
if (intValue < maxUInt) \
|
||||
DO(uint, Period, uint(intValue)); \
|
||||
DO(quint64, Period, quint64(intValue)); \
|
||||
} \
|
||||
break
|
||||
#define DO(Rep, Period, val) \
|
||||
do { \
|
||||
const std::chrono::duration<Rep, Period> wait((val)); \
|
||||
QCOMPARE(QtPrivate::convertToMilliseconds(wait), expected); \
|
||||
} while (0)
|
||||
|
||||
CASE(Nanoseconds, std::nano);
|
||||
CASE(Microseconds, std::micro);
|
||||
CASE(Milliseconds, std::milli);
|
||||
CASE(Seconds, std::ratio<1>);
|
||||
#undef DO
|
||||
#undef CASE
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QMutex::tryLock_non_recursive()
|
||||
{
|
||||
class Thread : public QThread
|
||||
|
Loading…
x
Reference in New Issue
Block a user