Don't deadlock when deleting slot objects in QMetaObject::activate()

The slot object was deleted after the mutex was relocked, which caused
a deadlock in case the functor destructor locked the same mutex again.

Change-Id: I5b4fb22fdb4483f91c89915872bfd548c31b0eea
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Thomas McGuire 2014-02-05 16:58:24 +01:00 committed by The Qt Project
parent f34b7f42e5
commit 6f5db32abe
2 changed files with 49 additions and 1 deletions

View File

@ -3561,9 +3561,15 @@ void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_i
const int method_relative = c->method_relative;
if (c->isSlotObject) {
c->slotObj->ref();
const QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
locker.unlock();
obj->call(receiver, argv ? argv : empty_argv);
// Make sure the slot object gets destroyed before the mutex is locked again, as the
// destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
// and that would deadlock if the pool happens to return the same mutex.
obj.reset();
locker.relock();
} else if (callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
//we compare the vtable to make sure we are not in the destructor of the object.

View File

@ -146,6 +146,7 @@ private slots:
void connectFunctorOverloads();
void connectFunctorQueued();
void connectFunctorWithContext();
void connectFunctorDeadlock();
void connectStaticSlotWithObject();
void disconnectDoesNotLeakFunctor();
void contextDoesNotLeakFunctor();
@ -5698,6 +5699,47 @@ void tst_QObject::connectFunctorWithContext()
context->deleteLater();
}
class MyFunctor
{
public:
explicit MyFunctor(QObject *objectToDisconnect)
: m_objectToDisconnect(objectToDisconnect)
{}
~MyFunctor() {
// Do operations that will lock the internal signalSlotLock mutex on many QObjects.
// The more QObjects, the higher the chance that the signalSlotLock mutex used
// is already in use. If the number of objects is higher than the number of mutexes in
// the pool (currently 131), the deadlock should always trigger. Use an even higher number
// to be on the safe side.
const int objectCount = 1024;
SenderObject lotsOfObjects[objectCount];
for (int i = 0; i < objectCount; ++i) {
QObject::connect(&lotsOfObjects[i], &SenderObject::signal1,
&lotsOfObjects[i], &SenderObject::aPublicSlot);
}
}
void operator()() {
// This will cause the slot object associated with this functor to be destroyed after
// this function returns. That in turn will destroy this functor.
// If our dtor runs with the signalSlotLock held, the bunch of connect()
// performed there will deadlock trying to lock that lock again.
m_objectToDisconnect->disconnect();
}
private:
QObject *m_objectToDisconnect;
};
void tst_QObject::connectFunctorDeadlock()
{
SenderObject sender;
MyFunctor functor(&sender);
QObject::connect(&sender, &SenderObject::signal1, functor);
sender.emitSignal1();
}
static int s_static_slot_checker = 1;
class StaticSlotChecker : public QObject