diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index 1212aef1cb2..13a162135a1 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -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" diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp index a46e10c7b08..d7c9b900afc 100644 --- a/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp +++ b/src/corelib/doc/snippets/code/src_corelib_thread_qsemaphore.cpp @@ -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(); diff --git a/src/corelib/thread/qmutex.h b/src/corelib/thread/qmutex.h index 18ffdd2cf49..8a75db012d3 100644 --- a/src/corelib/thread/qmutex.h +++ b/src/corelib/thread/qmutex.h @@ -8,10 +8,6 @@ #include #include #include -#include - -#include -#include QT_BEGIN_NAMESPACE @@ -27,32 +23,6 @@ class QMutex; class QRecursiveMutex; class QMutexPrivate; -namespace QtPrivate -{ - template - static int convertToMilliseconds(std::chrono::duration 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(duration); - if (wait < duration) - wait += std::chrono::milliseconds(1); - Q_ASSERT(wait >= duration); - const auto ms = wait.count(); - const auto maxInt = (std::numeric_limits::max)(); - - return ms < maxInt ? int(ms) : maxInt; - } -} - class Q_CORE_EXPORT QBasicMutex { Q_DISABLE_COPY_MOVE(QBasicMutex) diff --git a/src/corelib/thread/qsemaphore.cpp b/src/corelib/thread/qsemaphore.cpp index 803aa30bef3..af34a7bb399 100644 --- a/src/corelib/thread/qsemaphore.cpp +++ b/src/corelib/thread/qsemaphore.cpp @@ -145,10 +145,10 @@ static QBasicAtomicInteger *futexHigh32(QBasicAtomicInteger * } template bool -futexSemaphoreTryAcquire_loop(QBasicAtomicInteger &u, quintptr curValue, quintptr nn, int timeout) +futexSemaphoreTryAcquire_loop(QBasicAtomicInteger &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 &u, quintptr curValu } } -template bool futexSemaphoreTryAcquire(QBasicAtomicInteger &u, int n, int timeout) +static constexpr QDeadlineTimer::ForeverConstant Expired = + QDeadlineTimer::ForeverConstant(1); + +template bool +futexSemaphoreTryAcquire(QBasicAtomicInteger &u, int n, T timeout) { + constexpr bool IsTimed = std::is_same_v; // Try to acquire without waiting (we still loop because the testAndSet // call can fail). quintptr nn = unsigned(n); @@ -205,8 +210,13 @@ template bool futexSemaphoreTryAcquire(QBasicAtomicInteger= 0, "QSemaphore::acquire", "parameter 'n' must be non-negative"); if (futexAvailable()) { - futexSemaphoreTryAcquire(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(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(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(), sufficientResourcesAvailable)) return false; d->avail -= n; return true; diff --git a/src/corelib/thread/qsemaphore.h b/src/corelib/thread/qsemaphore.h index 8823468dbcd..f20bef42882 100644 --- a/src/corelib/thread/qsemaphore.h +++ b/src/corelib/thread/qsemaphore.h @@ -5,16 +5,13 @@ #define QSEMAPHORE_H #include -#include // for convertToMilliseconds() - -#include +#include 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 bool tryAcquire(int n, std::chrono::duration 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: diff --git a/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp b/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp index 121af38ec65..d2c0a38fd08 100644 --- a/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp +++ b/tests/auto/corelib/thread/qmutex/tst_qmutex.cpp @@ -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("unit"); - QTest::addColumn("doubleValue"); - QTest::addColumn("intValue"); - QTest::addColumn("expected"); - - auto add = [](TimeUnit unit, double d, long long i, qint64 expected) { - const QScopedArrayPointer 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::max(); - constexpr qint64 maxInt = std::numeric_limits::max(); - constexpr qint64 maxUInt = std::numeric_limits::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 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