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:
Thiago Macieira 2023-04-27 21:29:46 -07:00
parent fda4da6df9
commit 37f1fb78ee
6 changed files with 64 additions and 148 deletions

View File

@ -605,6 +605,7 @@ QStringView QXmlStreamAttributes::value(QLatin1StringView qualifiedName) const
#if QT_CONFIG(thread) #if QT_CONFIG(thread)
#include "qmutex.h" #include "qmutex.h"
#include "qreadwritelock.h" #include "qreadwritelock.h"
#include "qsemaphore.h"
#endif #endif
// #include "qotherheader.h" // #include "qotherheader.h"

View File

@ -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 sem.tryAcquire(3, 30000); // sem.available() == 2, returns true without waiting
//! [3] //! [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] //! [4]
// ... do something that may throw or return early // ... do something that may throw or return early
sem.release(); sem.release();

View File

@ -8,10 +8,6 @@
#include <QtCore/qatomic.h> #include <QtCore/qatomic.h>
#include <QtCore/qdeadlinetimer.h> #include <QtCore/qdeadlinetimer.h>
#include <QtCore/qtsan_impl.h> #include <QtCore/qtsan_impl.h>
#include <new>
#include <chrono>
#include <limits>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -27,32 +23,6 @@ class QMutex;
class QRecursiveMutex; class QRecursiveMutex;
class QMutexPrivate; 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 class Q_CORE_EXPORT QBasicMutex
{ {
Q_DISABLE_COPY_MOVE(QBasicMutex) Q_DISABLE_COPY_MOVE(QBasicMutex)

View File

@ -145,10 +145,10 @@ static QBasicAtomicInteger<quint32> *futexHigh32(QBasicAtomicInteger<quintptr> *
} }
template <bool IsTimed> bool 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 = IsTimed ? timer.remainingTimeNSecs() : -1;
qint64 remainingTime = timeout * Q_INT64_C(1000) * 1000;
int n = int(unsigned(nn)); int n = int(unsigned(nn));
// we're called after one testAndSet, so start by waiting first // 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 // Try to acquire without waiting (we still loop because the testAndSet
// call can fail). // call can fail).
quintptr nn = unsigned(n); quintptr nn = unsigned(n);
@ -205,8 +210,13 @@ template <bool IsTimed> bool futexSemaphoreTryAcquire(QBasicAtomicInteger<quintp
if (u.testAndSetOrdered(curValue, newValue, curValue)) if (u.testAndSetOrdered(curValue, newValue, curValue))
return true; // succeeded! return true; // succeeded!
} }
if (timeout == 0) if constexpr (IsTimed) {
return false; if (timeout.hasExpired())
return false;
} else {
if (timeout == Expired)
return false;
}
// we need to wait // we need to wait
constexpr quintptr oneWaiter = quintptr(Q_UINT64_C(1) << 32); // zero on 32-bit 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"); Q_ASSERT_X(n >= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative");
if (futexAvailable()) { if (futexAvailable()) {
futexSemaphoreTryAcquire<false>(u, n, -1); futexSemaphoreTryAcquire(u, n, QDeadlineTimer::Forever);
return; return;
} }
@ -409,7 +419,7 @@ bool QSemaphore::tryAcquire(int n)
Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
if (futexAvailable()) if (futexAvailable())
return futexSemaphoreTryAcquire<false>(u, n, 0); return futexSemaphoreTryAcquire(u, n, Expired);
const auto locker = qt_scoped_lock(d->mutex); const auto locker = qt_scoped_lock(d->mutex);
if (n > d->avail) 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 Tries to acquire \c n resources guarded by the semaphore and
returns \c true on success. If available() < \a n, this call will returns \c true on success. If available() < \a n, this call will
wait for at most \a timeout milliseconds for resources to become wait for at most \a timeout milliseconds for resources to become
@ -434,26 +446,40 @@ bool QSemaphore::tryAcquire(int n)
\sa acquire() \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); acquire(n);
return true; return true;
} }
if (timeout == 0) if (timer.hasExpired())
return tryAcquire(n); return tryAcquire(n);
Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative"); Q_ASSERT_X(n >= 0, "QSemaphore::tryAcquire", "parameter 'n' must be non-negative");
if (futexAvailable()) if (futexAvailable())
return futexSemaphoreTryAcquire<true>(u, n, timeout); return futexSemaphoreTryAcquire(u, n, timer);
using namespace std::chrono; using namespace std::chrono;
const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; }; const auto sufficientResourcesAvailable = [this, n] { return d->avail >= n; };
auto locker = qt_unique_lock(d->mutex); 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; return false;
d->avail -= n; d->avail -= n;
return true; return true;

View File

@ -5,16 +5,13 @@
#define QSEMAPHORE_H #define QSEMAPHORE_H
#include <QtCore/qglobal.h> #include <QtCore/qglobal.h>
#include <QtCore/qmutex.h> // for convertToMilliseconds() #include <QtCore/qdeadlinetimer.h>
#include <chrono>
QT_REQUIRE_CONFIG(thread); QT_REQUIRE_CONFIG(thread);
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QSemaphorePrivate; class QSemaphorePrivate;
class Q_CORE_EXPORT QSemaphore class Q_CORE_EXPORT QSemaphore
{ {
public: public:
@ -23,10 +20,19 @@ public:
void acquire(int n = 1); void acquire(int n = 1);
bool tryAcquire(int n = 1); bool tryAcquire(int n = 1);
QT_CORE_INLINE_SINCE(6, 6)
bool tryAcquire(int n, int timeout); 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> template <typename Rep, typename Period>
bool tryAcquire(int n, std::chrono::duration<Rep, Period> timeout) 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); 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 class QSemaphoreReleaser
{ {
public: public:

View File

@ -29,8 +29,6 @@ public:
Q_ENUM(TimeUnit) Q_ENUM(TimeUnit)
private slots: private slots:
void convertToMilliseconds_data();
void convertToMilliseconds();
void tryLock_non_recursive(); void tryLock_non_recursive();
void try_lock_for_non_recursive(); void try_lock_for_non_recursive();
void try_lock_until_non_recursive(); void try_lock_until_non_recursive();
@ -71,104 +69,6 @@ enum {
static constexpr std::chrono::milliseconds waitTimeAsDuration(waitTime); 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() void tst_QMutex::tryLock_non_recursive()
{ {
class Thread : public QThread class Thread : public QThread