diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 5fd60b5ac9..e9ac7b9428 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -226,6 +226,7 @@ qt_internal_add_module(Core tools/qarraydata.cpp tools/qarraydata.h tools/qarraydataops.h tools/qarraydatapointer.h + tools/qatomicscopedvaluerollback_p.h tools/qbitarray.cpp tools/qbitarray.h tools/qcache.h tools/qcontainerfwd.h diff --git a/src/corelib/tools/qatomicscopedvaluerollback_p.h b/src/corelib/tools/qatomicscopedvaluerollback_p.h new file mode 100644 index 0000000000..c802244d2e --- /dev/null +++ b/src/corelib/tools/qatomicscopedvaluerollback_p.h @@ -0,0 +1,95 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QATOMICSCOPEDVALUEROLLBACK_P_H +#define QATOMICSCOPEDVALUEROLLBACK_P_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +template +class [[nodiscard]] QAtomicScopedValueRollback +{ + std::atomic &m_atomic; + T m_value; + std::memory_order m_mo; + + Q_DISABLE_COPY_MOVE(QAtomicScopedValueRollback) + + constexpr std::memory_order store_part(std::memory_order mo) noexcept + { + switch (mo) { + case std::memory_order_relaxed: + case std::memory_order_consume: + case std::memory_order_acquire: return std::memory_order_relaxed; + case std::memory_order_release: + case std::memory_order_acq_rel: return std::memory_order_release; + case std::memory_order_seq_cst: return std::memory_order_seq_cst; + } + // GCC 8.x does not tread __builtin_unreachable() as constexpr +#if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900) + Q_UNREACHABLE(); +#endif + return std::memory_order_seq_cst; + } +public: + // + // std::atomic: + // + explicit constexpr + QAtomicScopedValueRollback(std::atomic &var, + std::memory_order mo = std::memory_order_seq_cst) + : m_atomic(var), m_value(var.load(mo)), m_mo(mo) {} + + explicit constexpr + QAtomicScopedValueRollback(std::atomic &var, T value, + std::memory_order mo = std::memory_order_seq_cst) + : m_atomic(var), m_value(var.exchange(value, mo)), m_mo(mo) {} + + // + // Q(Basic)AtomicInteger: + // + explicit constexpr + QAtomicScopedValueRollback(QBasicAtomicInteger &var, + std::memory_order mo = std::memory_order_seq_cst) + : QAtomicScopedValueRollback(var._q_value, mo) {} + + explicit constexpr + QAtomicScopedValueRollback(QBasicAtomicInteger &var, T value, + std::memory_order mo = std::memory_order_seq_cst) + : QAtomicScopedValueRollback(var._q_value, value, mo) {} + + // + // Q(Basic)AtomicPointer: + // + explicit constexpr + QAtomicScopedValueRollback(QBasicAtomicPointer> &var, + std::memory_order mo = std::memory_order_seq_cst) + : QAtomicScopedValueRollback(var._q_value, mo) {} + + explicit constexpr + QAtomicScopedValueRollback(QBasicAtomicPointer> &var, T value, + std::memory_order mo = std::memory_order_seq_cst) + : QAtomicScopedValueRollback(var._q_value, value, mo) {} + +#if __cpp_constexpr >= 201907L + constexpr +#endif + ~QAtomicScopedValueRollback() + { + m_atomic.store(m_value, store_part(m_mo)); + } + + constexpr void commit() + { + m_value = m_atomic.load(m_mo); + } +}; + +QT_END_NAMESPACE + +#endif // QATOMICASCOPEDVALUEROLLBACK_P_H diff --git a/tests/auto/corelib/tools/CMakeLists.txt b/tests/auto/corelib/tools/CMakeLists.txt index 185a3e1bcc..d238219485 100644 --- a/tests/auto/corelib/tools/CMakeLists.txt +++ b/tests/auto/corelib/tools/CMakeLists.txt @@ -1,4 +1,5 @@ # Generated from tools.pro. +add_subdirectory(qatomicscopedvaluerollback) if(NOT INTEGRITY) add_subdirectory(collections) endif() diff --git a/tests/auto/corelib/tools/qatomicscopedvaluerollback/CMakeLists.txt b/tests/auto/corelib/tools/qatomicscopedvaluerollback/CMakeLists.txt new file mode 100644 index 0000000000..3ba7cb9a6b --- /dev/null +++ b/tests/auto/corelib/tools/qatomicscopedvaluerollback/CMakeLists.txt @@ -0,0 +1,6 @@ +qt_internal_add_test(tst_qatomicscopedvaluerollback + SOURCES + tst_qatomicscopedvaluerollback.cpp + PUBLIC_LIBRARIES + Qt::CorePrivate +) diff --git a/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp new file mode 100644 index 0000000000..81d8242f71 --- /dev/null +++ b/tests/auto/corelib/tools/qatomicscopedvaluerollback/tst_qatomicscopedvaluerollback.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include + +class tst_QAtomicScopedValueRollback : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void leavingScope(); + void leavingScopeAfterCommit(); + void rollbackToPreviousCommit(); + void exceptions(); + void earlyExitScope(); +private: + void earlyExitScope_helper(int exitpoint, std::atomic &member); +}; + +void tst_QAtomicScopedValueRollback::leavingScope() +{ + QAtomicInt i = 0; + QBasicAtomicInteger b = false; + std::atomic b2 = false; + + //test rollback on going out of scope + { + QAtomicScopedValueRollback ri(i); + QAtomicScopedValueRollback rb(b); + QAtomicScopedValueRollback rb2(b2, true); + QCOMPARE(b.loadRelaxed(), false); + QCOMPARE(b2, true); + QCOMPARE(i.loadRelaxed(), 0); + b.storeRelaxed(true); + i.storeRelaxed(1); + QCOMPARE(b.loadRelaxed(), true); + QCOMPARE(i.loadRelaxed(), 1); + } + QCOMPARE(b.loadRelaxed(), false); + QCOMPARE(b2, false); + QCOMPARE(i.loadRelaxed(), 0); +} + +void tst_QAtomicScopedValueRollback::leavingScopeAfterCommit() +{ + std::atomic i = 0; + QAtomicInteger b = false; + + //test rollback on going out of scope + { + QAtomicScopedValueRollback ri(i); + QAtomicScopedValueRollback rb(b); + QCOMPARE(b.loadRelaxed(), false); + QCOMPARE(i, 0); + b.storeRelaxed(true); + i = 1; + QCOMPARE(b.loadRelaxed(), true); + QCOMPARE(i, 1); + ri.commit(); + rb.commit(); + } + QCOMPARE(b.loadRelaxed(), true); + QCOMPARE(i, 1); +} + +void tst_QAtomicScopedValueRollback::rollbackToPreviousCommit() +{ + QBasicAtomicInt i = 0; + { + QAtomicScopedValueRollback ri(i); + i++; + ri.commit(); + i++; + } + QCOMPARE(i.loadRelaxed(), 1); + { + QAtomicScopedValueRollback ri1(i); + i++; + ri1.commit(); + i++; + ri1.commit(); + i++; + } + QCOMPARE(i.loadRelaxed(), 3); +} + +void tst_QAtomicScopedValueRollback::exceptions() +{ + std::atomic b = false; + bool caught = false; + QT_TRY + { + QAtomicScopedValueRollback rb(b); + b = true; + QT_THROW(std::bad_alloc()); //if Qt compiled without exceptions this is noop + rb.commit(); //if Qt compiled without exceptions, true is committed + } + QT_CATCH(...) + { + caught = true; + } + QCOMPARE(b, !caught); //expect false if exception was thrown, true otherwise +} + +void tst_QAtomicScopedValueRollback::earlyExitScope() +{ + QAtomicInt ai = 0; + std::atomic aj = 0; + while (true) { + QAtomicScopedValueRollback ri(ai); + ++ai; + aj = ai.loadRelaxed(); + if (ai.loadRelaxed() > 8) break; + ri.commit(); + } + QCOMPARE(ai.loadRelaxed(), 8); + QCOMPARE(aj.load(), 9); + + for (int i = 0; i < 5; ++i) { + aj = 1; + earlyExitScope_helper(i, aj); + QCOMPARE(aj.load(), 1 << i); + } +} + +static void operator*=(std::atomic &lhs, int rhs) +{ + int expected = lhs.load(); + while (!lhs.compare_exchange_weak(expected, expected * rhs)) + ; +} + +void tst_QAtomicScopedValueRollback::earlyExitScope_helper(int exitpoint, std::atomic& member) +{ + QAtomicScopedValueRollback r(member); + member *= 2; + if (exitpoint == 0) + return; + r.commit(); + member *= 2; + if (exitpoint == 1) + return; + r.commit(); + member *= 2; + if (exitpoint == 2) + return; + r.commit(); + member *= 2; + if (exitpoint == 3) + return; + r.commit(); +} + +QTEST_MAIN(tst_QAtomicScopedValueRollback) +#include "tst_qatomicscopedvaluerollback.moc" diff --git a/tests/auto/corelib/tools/qlist/CMakeLists.txt b/tests/auto/corelib/tools/qlist/CMakeLists.txt index 89b92ab305..46281a0db8 100644 --- a/tests/auto/corelib/tools/qlist/CMakeLists.txt +++ b/tests/auto/corelib/tools/qlist/CMakeLists.txt @@ -7,6 +7,8 @@ qt_internal_add_test(tst_qlist SOURCES tst_qlist.cpp + PUBLIC_LIBRARIES + Qt::CorePrivate ) ## Scopes: diff --git a/tests/auto/corelib/tools/qlist/tst_qlist.cpp b/tests/auto/corelib/tools/qlist/tst_qlist.cpp index fef30647e3..e797b76f69 100644 --- a/tests/auto/corelib/tools/qlist/tst_qlist.cpp +++ b/tests/auto/corelib/tools/qlist/tst_qlist.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include @@ -3181,7 +3181,7 @@ void tst_QList::emplaceReturnsIterator() void tst_QList::emplaceFront() const { - QScopedValueRollback rollback(Movable::counter, 0); + QAtomicScopedValueRollback rollback(Movable::counter, 0); QList vec; vec.emplaceFront('b'); @@ -3206,7 +3206,7 @@ void tst_QList::emplaceFrontReturnsRef() const void tst_QList::emplaceBack() { - QScopedValueRollback rollback(Movable::counter, 0); + QAtomicScopedValueRollback rollback(Movable::counter, 0); QList vec;