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;
|
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)
|
QFutureInterfaceBasePrivate::QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState)
|
||||||
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
|
: refCount(1), m_progressValue(0), m_progressMinimum(0), m_progressMaximum(0),
|
||||||
state(initialState), pendingResults(0),
|
state(initialState), pendingResults(0),
|
||||||
|
@ -130,6 +130,8 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool referenceCountIsOne() const;
|
bool referenceCountIsOne() const;
|
||||||
|
bool refT() const;
|
||||||
|
bool derefT() const;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
#ifndef QFUTURE_TEST
|
#ifndef QFUTURE_TEST
|
||||||
@ -148,13 +150,17 @@ class QFutureInterface : public QFutureInterfaceBase
|
|||||||
public:
|
public:
|
||||||
QFutureInterface(State initialState = NoState)
|
QFutureInterface(State initialState = NoState)
|
||||||
: QFutureInterfaceBase(initialState)
|
: QFutureInterfaceBase(initialState)
|
||||||
{ }
|
{
|
||||||
|
refT();
|
||||||
|
}
|
||||||
QFutureInterface(const QFutureInterface &other)
|
QFutureInterface(const QFutureInterface &other)
|
||||||
: QFutureInterfaceBase(other)
|
: QFutureInterfaceBase(other)
|
||||||
{ }
|
{
|
||||||
|
refT();
|
||||||
|
}
|
||||||
~QFutureInterface()
|
~QFutureInterface()
|
||||||
{
|
{
|
||||||
if (referenceCountIsOne())
|
if (!derefT())
|
||||||
resultStore().clear();
|
resultStore().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +169,8 @@ public:
|
|||||||
|
|
||||||
QFutureInterface &operator=(const QFutureInterface &other)
|
QFutureInterface &operator=(const QFutureInterface &other)
|
||||||
{
|
{
|
||||||
if (referenceCountIsOne())
|
other.refT();
|
||||||
|
if (!derefT())
|
||||||
resultStore().clear();
|
resultStore().clear();
|
||||||
QFutureInterfaceBase::operator=(other);
|
QFutureInterfaceBase::operator=(other);
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -129,7 +129,31 @@ class QFutureInterfaceBasePrivate
|
|||||||
public:
|
public:
|
||||||
QFutureInterfaceBasePrivate(QFutureInterfaceBase::State initialState);
|
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;
|
mutable QMutex m_mutex;
|
||||||
QWaitCondition waitCondition;
|
QWaitCondition waitCondition;
|
||||||
QList<QFutureCallOutInterface *> outputConnections;
|
QList<QFutureCallOutInterface *> outputConnections;
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
|
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
#include <QtTest/QtTest>
|
#include <QtTest/QtTest>
|
||||||
|
|
||||||
@ -75,6 +76,7 @@ private slots:
|
|||||||
void stlContainers();
|
void stlContainers();
|
||||||
void qFutureAssignmentLeak();
|
void qFutureAssignmentLeak();
|
||||||
void stressTest();
|
void stressTest();
|
||||||
|
void persistentResultTest();
|
||||||
public slots:
|
public slots:
|
||||||
void throttling();
|
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)
|
QTEST_MAIN(tst_QtConcurrentMap)
|
||||||
#include "tst_qtconcurrentmap.moc"
|
#include "tst_qtconcurrentmap.moc"
|
||||||
|
@ -1250,12 +1250,12 @@ void tst_QFuture::throttling()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void tst_QFuture::voidConversions()
|
void tst_QFuture::voidConversions()
|
||||||
|
{
|
||||||
{
|
{
|
||||||
QFutureInterface<int> iface;
|
QFutureInterface<int> iface;
|
||||||
iface.reportStarted();
|
iface.reportStarted();
|
||||||
|
|
||||||
QFuture<int> intFuture(&iface);
|
QFuture<int> intFuture(&iface);
|
||||||
|
|
||||||
int value = 10;
|
int value = 10;
|
||||||
iface.reportFinished(&value);
|
iface.reportFinished(&value);
|
||||||
|
|
||||||
@ -1265,6 +1265,20 @@ void tst_QFuture::voidConversions()
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef QT_NO_EXCEPTIONS
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user