Search code examples
qtthread-safetysignals-slots

Is there a guarantee that no signals are delivered from a different thread after QObject::disconnect()?


I am not thinking of the problem that queued signals are still delivered in the receiving thread after disconnect(), instead:

Consider the case that a Sender object is generating signals in thread 1 and there is a Receiver object in thread 2 which has a slot that is connected to Sender's signal via a Qt::DirectConnection.

Now, in Receiver::~Receiver(), I need to make sure that no signals are still delivered while the object is already (maybe partially) destructed. Because the connection is direct, the slot could be invoked in thread 1 at any time and could in particular happen between destruction of Receiver's specific attributes and destruction of the base QObject which will also disconnect signals. So, my question boils down to:

Is it enough to disconnect Sender and Receiver objects in thread 2 before destruction of the Receiver object, or do I need to make sure that no signals are emitted in thread 1 during the disconnect() call?

I am thinking of the case where thread 1 is in the middle of emitting the signal, e.g. right at the top of executing the receiving slot and right at that moment, in thread 2, the disconnect() call is done. If disconnect() waits (via a mutex) for thread 1 to finish delivering the signal before doing the disconnect and blocking further signal deliveries, everything would be fine but I am not sure that's the case.


Solution

  • Yes disconnect() and connect() are using mutexes for protection.

    These are the first lines of the disconnect() function:

    bool QMetaObjectPrivate::disconnect(const QObject *sender, int signal_index,
                                        const QObject *receiver, int method_index,
                                        DisconnectType disconnectType)
    {
        if (!sender)
            return false;
    
        QObject *s = const_cast<QObject *>(sender);
    
        QMutex *senderMutex = signalSlotLock(sender);
        QMutex *receiverMutex = receiver ? signalSlotLock(receiver) : 0;
        QOrderedMutexLocker locker(senderMutex, receiverMutex);
    

    And here are the first lines of the connect() function:

    bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index,
                                     const QObject *receiver, int method_index, int type, int *types)
    {
        QObject *s = const_cast<QObject *>(sender);
        QObject *r = const_cast<QObject *>(receiver);
    
        QOrderedMutexLocker locker(signalSlotLock(sender),
                                   signalSlotLock(receiver));
    

    If you check the QObject documentation you can see that :

    Note: All functions in this class are reentrant, but connect(), connect(), disconnect(), and disconnect() are also thread-safe.

    EDIT

    When a signal is emitted the QMetaObject::activate function is called which locks the sender's object mutex:

    void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                               void **argv)
    {
         ...
         QMutexLocker locker(signalSlotLock(sender));