Skip to content

Commit

Permalink
Long live QAtomicScopedValueRollback (private API)!
Browse files Browse the repository at this point in the history
QScopedValueRollback has a few users that apply it on QAtomicInt,
which happens to work as QAtomicInt is copy-constructible and its
ctors are implicit.

But that's of course nonsense. We don't need to store the oldValue in
an atomic, nor do we need to pass the new value into the ctor as an
atomic.

So, add a QAtomicScopedValueRollback which works on std::atomic as
well as the Qt atomics, but distinguishes between the reference (which
is atomic) and the value (which isn't), and use it in one of the
users, tst_QList.

Keep it private until we know whether there's an actual need for this.

The test is a copy of tst_qscopedvaluefallback, so the occasional
oddity (like atomic op*=) should be ignored.

Task-number: QTBUG-103835
Change-Id: I3c05b3e51f465698657a02ca5521ed465386e9a6
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Fabian Kosmale <[email protected]>
  • Loading branch information
marcmutz committed May 31, 2022
1 parent 4fc66d7 commit 5e48a51
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/corelib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 95 additions & 0 deletions src/corelib/tools/qatomicscopedvaluerollback_p.h
Original file line number Diff line number Diff line change
@@ -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 <QtCore/qglobal.h>
#include <QtCore/qatomic.h>

#include <atomic>

QT_BEGIN_NAMESPACE

template <typename T>
class [[nodiscard]] QAtomicScopedValueRollback
{
std::atomic<T> &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<T> &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<T> &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<T> &var,
std::memory_order mo = std::memory_order_seq_cst)
: QAtomicScopedValueRollback(var._q_value, mo) {}

explicit constexpr
QAtomicScopedValueRollback(QBasicAtomicInteger<T> &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<std::remove_pointer_t<T>> &var,
std::memory_order mo = std::memory_order_seq_cst)
: QAtomicScopedValueRollback(var._q_value, mo) {}

explicit constexpr
QAtomicScopedValueRollback(QBasicAtomicPointer<std::remove_pointer_t<T>> &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
1 change: 1 addition & 0 deletions tests/auto/corelib/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Generated from tools.pro.
add_subdirectory(qatomicscopedvaluerollback)
if(NOT INTEGRITY)
add_subdirectory(collections)
endif()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
qt_internal_add_test(tst_qatomicscopedvaluerollback
SOURCES
tst_qatomicscopedvaluerollback.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
)
Original file line number Diff line number Diff line change
@@ -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 <QtCore/private/qatomicscopedvaluerollback_p.h>

#include <QTest>

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<int> &member);
};

void tst_QAtomicScopedValueRollback::leavingScope()
{
QAtomicInt i = 0;
QBasicAtomicInteger<bool> b = false;
std::atomic<bool> 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<int> i = 0;
QAtomicInteger<bool> 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<bool> 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<int> 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<int> &lhs, int rhs)
{
int expected = lhs.load();
while (!lhs.compare_exchange_weak(expected, expected * rhs))
;
}

void tst_QAtomicScopedValueRollback::earlyExitScope_helper(int exitpoint, std::atomic<int>& 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"
2 changes: 2 additions & 0 deletions tests/auto/corelib/tools/qlist/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
qt_internal_add_test(tst_qlist
SOURCES
tst_qlist.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
)

## Scopes:
Expand Down
6 changes: 3 additions & 3 deletions tests/auto/corelib/tools/qlist/tst_qlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <QAtomicInt>
#include <QThread>
#include <QSemaphore>
#include <QScopedValueRollback>
#include <private/qatomicscopedvaluerollback_p.h>
#include <qlist.h>


Expand Down Expand Up @@ -3181,7 +3181,7 @@ void tst_QList::emplaceReturnsIterator()

void tst_QList::emplaceFront() const
{
QScopedValueRollback<QAtomicInt> rollback(Movable::counter, 0);
QAtomicScopedValueRollback rollback(Movable::counter, 0);

QList<Movable> vec;
vec.emplaceFront('b');
Expand All @@ -3206,7 +3206,7 @@ void tst_QList::emplaceFrontReturnsRef() const

void tst_QList::emplaceBack()
{
QScopedValueRollback<QAtomicInt> rollback(Movable::counter, 0);
QAtomicScopedValueRollback rollback(Movable::counter, 0);

QList<Movable> vec;

Expand Down

0 comments on commit 5e48a51

Please sign in to comment.