QThread: Clean up bindingStatusOrList if object gets deleted

Deal with the case that the object gets deleted between a call to
moveToThread and the start of the thread by removing the object from the
list in that case.

Fixes: QTBUG-104014
Pick-to: 6.4
Change-Id: Ib249b6e8e8dfbc4d1332bb99a57fa9d3cff16465
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Fabian Kosmale 2022-06-13 12:16:26 +02:00
parent 6db91c0df1
commit 2c81ba2df9
5 changed files with 50 additions and 0 deletions

View File

@ -50,6 +50,7 @@ public:
~QBindingStorage();
bool isEmpty() { return !d; }
bool isValid() const noexcept { return bindingStatus; }
const QBindingStatus *status(QtPrivate::QBindingStatusAccessToken) const;

View File

@ -970,6 +970,16 @@ QObject::~QObject()
d->wasDeleted = true;
d->blockSig = 0; // unblock signals so we always emit destroyed()
if (!d->bindingStorage.isValid()) {
// this might be the case after an incomplete thread-move
// remove this object from the pending list in that case
if (QThread *ownThread = thread()) {
auto *privThread = static_cast<QThreadPrivate *>(
QObjectPrivate::get(ownThread));
privThread->removeObjectWithPendingBindingStatusChange(this);
}
}
// If we reached this point, we need to clear the binding data
// as the corresponding properties are no longer useful
d->clearBindingStorage();

View File

@ -605,6 +605,19 @@ QBindingStatus *QtPrivate::BindingStatusOrList::addObjectUnlessAlreadyStatus(QOb
return nullptr;
}
/*!
\internal
If BindingStatusOrList is a list, remove \a object from it
*/
void QtPrivate::BindingStatusOrList::removeObject(QObject *object)
{
List *objectList = list();
if (!objectList)
return;
auto it = std::remove(objectList->begin(), objectList->end(), object);
objectList->erase(it, objectList->end());
}
QBindingStatus *QThreadPrivate::addObjectWithPendingBindingStatusChange(QObject *obj)
{
if (auto status = m_statusOrPendingObjects.bindingStatus())
@ -613,6 +626,15 @@ QBindingStatus *QThreadPrivate::addObjectWithPendingBindingStatusChange(QObject
return m_statusOrPendingObjects.addObjectUnlessAlreadyStatus(obj);
}
void QThreadPrivate::removeObjectWithPendingBindingStatusChange(QObject *obj)
{
if (m_statusOrPendingObjects.bindingStatus())
return;
QMutexLocker lock(&mutex);
m_statusOrPendingObjects.removeObject(obj);
}
/*!
\threadsafe
Tells the thread's event loop to exit with a return code.

View File

@ -108,6 +108,7 @@ public:
// requires external synchronization:
QBindingStatus *addObjectUnlessAlreadyStatus(QObject *object);
void removeObject(QObject *object);
void setStatusAndClearList(QBindingStatus *status) noexcept;
@ -237,6 +238,7 @@ public:
if that one has been set in the meantime
*/
QBindingStatus *addObjectWithPendingBindingStatusChange(QObject *obj);
void removeObjectWithPendingBindingStatusChange(QObject *obj);
// manipulating m_statusOrPendingObjects requires mutex to be locked
QtPrivate::BindingStatusOrList m_statusOrPendingObjects = {};
@ -263,6 +265,7 @@ public:
QBindingStatus* bindingStatus() { return m_bindingStatus; }
QBindingStatus *addObjectWithPendingBindingStatusChange(QObject *) { return nullptr; }
void removeObjectWithPendingBindingStatusChange(QObject *) {}
static void setCurrentThread(QThread *) { }
static QAbstractEventDispatcher *createEventDispatcher(QThreadData *data);

View File

@ -97,6 +97,8 @@ private slots:
void terminateAndPrematureDestruction();
void terminateAndDoubleDestruction();
void bindingListCleanupAfterDelete();
};
enum { one_minute = 60 * 1000, five_minutes = 5 * one_minute };
@ -1830,5 +1832,17 @@ void tst_QThread::terminateAndDoubleDestruction()
TestObject obj;
}
void tst_QThread::bindingListCleanupAfterDelete()
{
QThread t;
auto optr = std::make_unique<QObject>();
optr->moveToThread(&t);
auto threadPriv = static_cast<QThreadPrivate *>(QObjectPrivate::get(&t));
auto list = threadPriv->m_statusOrPendingObjects.list();
QVERIFY(list);
optr.reset();
QVERIFY(list->empty());
}
QTEST_MAIN(tst_QThread)
#include "tst_qthread.moc"