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:
Christian Strømme 2012-10-25 12:32:52 +02:00 committed by The Qt Project
parent d45cebbf4d
commit 731ba8ed08
5 changed files with 111 additions and 13 deletions

View File

@ -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),

View File

@ -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;

View File

@ -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;

View File

@ -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"

View File

@ -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