Fix for leak in QFuture
To avoid leaking when converting a QFuture<T> to a QFuture<void> we need to have a separate ref. counter for QFuture<T>. When the last QFuture<T> goes out of scope, we need to clean out the result data. Task-number: QTBUG-27224 Change-Id: I965a64a11fffbb191ab979cdd030a9aafd4436c2 Reviewed-by: Jędrzej Nowacki <jedrzej.nowacki@digia.com>
This commit is contained in:
parent
d45cebbf4d
commit
731ba8ed08
@ -419,6 +419,16 @@ bool QFutureInterfaceBase::referenceCountIsOne() const
|
||||
return d->refCount.load() == 1;
|
||||
}
|
||||
|
||||
bool QFutureInterfaceBase::refT() const
|
||||
{
|
||||
return d->refCount.refT();
|
||||
}
|
||||
|
||||
bool QFutureInterfaceBase::derefT() const
|
||||
{
|
||||
return d->refCount.derefT();
|
||||
}
|
||||
|
||||
QFutureInterfaceBasePrivate::QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState)
|
||||
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
|
||||
state(initialState), pendingResults(0),
|
||||
|
@ -130,6 +130,8 @@ public:
|
||||
|
||||
protected:
|
||||
bool referenceCountIsOne() const;
|
||||
bool refT() const;
|
||||
bool derefT() const;
|
||||
public:
|
||||
|
||||
#ifndef QFUTURE_TEST
|
||||
@ -148,13 +150,17 @@ class QFutureInterface : public QFutureInterfaceBase
|
||||
public:
|
||||
QFutureInterface(State initialState = NoState)
|
||||
: QFutureInterfaceBase(initialState)
|
||||
{ }
|
||||
{
|
||||
refT();
|
||||
}
|
||||
QFutureInterface(const QFutureInterface &other)
|
||||
: QFutureInterfaceBase(other)
|
||||
{ }
|
||||
{
|
||||
refT();
|
||||
}
|
||||
~QFutureInterface()
|
||||
{
|
||||
if (referenceCountIsOne())
|
||||
if (!derefT())
|
||||
resultStore().clear();
|
||||
}
|
||||
|
||||
@ -163,7 +169,8 @@ public:
|
||||
|
||||
QFutureInterface &operator=(const QFutureInterface &other)
|
||||
{
|
||||
if (referenceCountIsOne())
|
||||
other.refT();
|
||||
if (!derefT())
|
||||
resultStore().clear();
|
||||
QFutureInterfaceBase::operator=(other);
|
||||
return *this;
|
||||
|
@ -129,7 +129,31 @@ class QFutureInterfaceBasePrivate
|
||||
public:
|
||||
QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState);
|
||||
|
||||
QAtomicInt refCount;
|
||||
// When the last QFuture<T> reference is removed, we need to make
|
||||
// sure that data stored in the ResultStore is cleaned out.
|
||||
// Since QFutureInterfaceBasePrivate can be shared between QFuture<T>
|
||||
// and QFuture<void> objects, we use a separate ref. counter
|
||||
// to keep track of QFuture<T> objects.
|
||||
class RefCount
|
||||
{
|
||||
public:
|
||||
inline RefCount(int r = 0, int rt = 0)
|
||||
: m_refCount(r), m_refCountT(rt) {}
|
||||
// Default ref counter for QFIBP
|
||||
inline bool ref() { return m_refCount.ref(); }
|
||||
inline bool deref() { return m_refCount.deref(); }
|
||||
inline int load() const { return m_refCount.load(); }
|
||||
// Ref counter for type T
|
||||
inline bool refT() { return m_refCountT.ref(); }
|
||||
inline bool derefT() { return m_refCountT.deref(); }
|
||||
inline int loadT() const { return m_refCountT.load(); }
|
||||
|
||||
private:
|
||||
QAtomicInt m_refCount;
|
||||
QAtomicInt m_refCountT;
|
||||
};
|
||||
|
||||
RefCount refCount;
|
||||
mutable QMutex m_mutex;
|
||||
QWaitCondition waitCondition;
|
||||
QList<QFutureCallOutInterface *> outputConnections;
|
||||
|
@ -43,6 +43,7 @@
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
@ -75,6 +76,7 @@ private slots:
|
||||
void stlContainers();
|
||||
void qFutureAssignmentLeak();
|
||||
void stressTest();
|
||||
void persistentResultTest();
|
||||
public slots:
|
||||
void throttling();
|
||||
};
|
||||
@ -2395,5 +2397,46 @@ void tst_QtConcurrentMap::stressTest()
|
||||
}
|
||||
}
|
||||
|
||||
struct LockedCounter
|
||||
{
|
||||
LockedCounter(QMutex *mutex, QAtomicInt *ai)
|
||||
: mtx(mutex),
|
||||
ref(ai) {}
|
||||
|
||||
typedef int result_type;
|
||||
int operator()(int x)
|
||||
{
|
||||
QMutexLocker locker(mtx);
|
||||
ref->ref();
|
||||
return ++x;
|
||||
}
|
||||
|
||||
QMutex *mtx;
|
||||
QAtomicInt *ref;
|
||||
};
|
||||
|
||||
// The Thread engine holds the last reference
|
||||
// to the QFuture, so this should not leak
|
||||
// or fail.
|
||||
void tst_QtConcurrentMap::persistentResultTest()
|
||||
{
|
||||
QFuture<void> voidFuture;
|
||||
QMutex mtx;
|
||||
QAtomicInt ref;
|
||||
LockedCounter lc(&mtx, &ref);
|
||||
QList<int> list;
|
||||
{
|
||||
list << 1 << 2 << 3;
|
||||
mtx.lock();
|
||||
QFuture<int> future = QtConcurrent::mapped(list
|
||||
,lc);
|
||||
voidFuture = future;
|
||||
}
|
||||
QCOMPARE(ref.loadAcquire(), 0);
|
||||
mtx.unlock(); // Unblock
|
||||
voidFuture.waitForFinished();
|
||||
QCOMPARE(ref.loadAcquire(), 3);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QtConcurrentMap)
|
||||
#include "tst_qtconcurrentmap.moc"
|
||||
|
@ -1251,18 +1251,32 @@ void tst_QFuture::throttling()
|
||||
|
||||
void tst_QFuture::voidConversions()
|
||||
{
|
||||
QFutureInterface<int> iface;
|
||||
iface.reportStarted();
|
||||
{
|
||||
QFutureInterface<int> iface;
|
||||
iface.reportStarted();
|
||||
|
||||
QFuture<int> intFuture(&iface);
|
||||
QFuture<int> intFuture(&iface);
|
||||
int value = 10;
|
||||
iface.reportFinished(&value);
|
||||
|
||||
int value = 10;
|
||||
iface.reportFinished(&value);
|
||||
QFuture<void> voidFuture(intFuture);
|
||||
voidFuture = intFuture;
|
||||
|
||||
QFuture<void> voidFuture(intFuture);
|
||||
voidFuture = intFuture;
|
||||
QVERIFY(voidFuture == intFuture);
|
||||
}
|
||||
|
||||
QVERIFY(voidFuture == intFuture);
|
||||
{
|
||||
QFuture<void> voidFuture;
|
||||
{
|
||||
QFutureInterface<QList<int> > iface;
|
||||
iface.reportStarted();
|
||||
|
||||
QFuture<QList<int> > listFuture(&iface);
|
||||
iface.reportResult(QList<int>() << 1 << 2 << 3);
|
||||
voidFuture = listFuture;
|
||||
}
|
||||
QCOMPARE(voidFuture.resultCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user