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:
parent
f34b7f42e5
commit
6f5db32abe
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user