2

Despite not using std::thread or QThread anywhere, still getting following problems:

  1. Always a runtime debug error log from Qt:

    QObject::connect: Cannot queue arguments of type 'QAbstractSocket::SocketError'
    (Make sure 'QAbstractSocket::SocketError' is registered using qRegisterMetaType().)

  2. Intermittent crash on TcpSocket::flush() method;
    I use this method to make sure that the TCP is written immediately; Now sometimes the app crashes exactly at this method with SIGPIPE

Upon searching internet, found that people suggest that to fix 1st problem (i.e. the meta error), I need to register using qRegisterMetaType(), when we have multiple threads.
Same multithreading is referred as a cause for the 2nd problem as well; see this and this.

But I don't have more than 1 thread!
My socket code looks like below:

struct Socket : public QSslSocket
{
  Q_OBJECT public:

  void ConnectSlots ()
  {
    const auto connectionType = Qt::QueuedConnection;
    connect(this, SIGNAL(readyRead()), this, SLOT(ReceiveData()), connectionType);
    connect(this, SIGNAL(disconnected()), this, SLOT(Disconnected()), connectionType);
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(Error(QAbstractSocket::SocketError)), connectionType);
    //                           ^^^^^^^ error comes whether I comment this or not
  }

  public slots:
  void ReceiveData () { ... }
  void Disconnected () { ... }
  void Error () { ... }
}

Question: Is Qt creating any internal thread by itself for read/write purpose? (I hope not). How to fix above 2 issues?

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • The first thing that came into my mind is using `int` instead of enum type in your signal/slot connection. I.e. `...SLOT(Error(int)...` if you don't want to bother yourself with Qt meta system, and properly cast parameters to enum values in your slot. – vahancho Jul 13 '17 at 10:24
  • You have a comment in the code shown "error comes whether I comment this or not". Comment out what specifically -- just the `QAbstractSocket::SocketError` parameter to `Error` or the entire call to to `connect`? – G.M. Jul 13 '17 at 10:39
  • @vahancho, if I make it `Error(int)` then there is an error of incompatible type: *"QObject::connect: Incompatible sender/receiver arguments. Connection::Socket::error(QAbstractSocket::SocketError) --> Connection::Socket::Error(int)"*. Suppose if I remove argument and make it `Error()`, then the actual problem mentioned in Qn still persists. – iammilind Jul 13 '17 at 10:39
  • Why do you need QueuedConnection? Usually I am using default AutoConnection – Jeka Jul 13 '17 at 11:39
  • @Jeka, because many a times when the function of "Write()" is called, before it finishes "Read()" is invoked in between. The logic of my code expects that "Write()" should be fully finished before "Read()" starts and vice versa. With `QueuedConnection` that part was resolved. – iammilind Jul 13 '17 at 11:42
  • @iammilind wait a second, if your ReceiveData slot called many times thats mean that in main thread queue will be many "Receive" events, I see nothing preventing from "Write" events be putted in between. – Jeka Jul 13 '17 at 11:59
  • @Jeka, actually I am not using the threads at all. Both Read/Write happens in the same thread (or at least I perceive it to be). My expectation is that when a "Write()" slots is executed, "Read()" slot should not interrupt in between & vice versa. i.e. Let the "Read()" or "Write()" function finish their execution & let the control return to the event loop. Only after that the subsequent signal/slots should be invoked. – iammilind Jul 13 '17 at 12:01
  • @iammilind yes, since you are in only one thread and there is no any interruption available any slot calls caused only by systems events pending in QEventLoop of main thread. In such case it is impossible that Read and Write could be interrupted. What is your OS? Do you have any exaple when Read or Write interrupted, I am sure this is impossible – Jeka Jul 13 '17 at 12:35

2 Answers2

3

I don't think the problem is related to threads, rather it's the combination of a QAbstractSocket::SocketError type parameter and Qt::QueuedConnection that's causing the issue.

Looking at the the various connect implementations in the Qt5.8 source, if Qt::QueuedConnection is specified as the connection type then a check against the signal's parameter types will be performed. Something like...

int *types = 0;
if ((type == Qt::QueuedConnection)
        && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
   return QMetaObject::Connection(0);
}

where queuedConnectionTypes will return a null pointer if any of the types are not registered.

So, if the connection is queued then all parameters used by the signal must be registered regardless of whether or not they are used by the slot. To avoid the error make sure you call...

qRegisterMetaType<QAbstractSocket::SocketError>();

once at some point before any calls to connect that use the combination of a QAbstractSocket::SocketError parameter and Qt::QueuedConnection.

G.M.
  • 12,232
  • 2
  • 15
  • 18
  • Thanks for the answer. Would you like to also add some code on how & where/when to make that registration (for the sake of newbies). Also, any idea of the issue 2? My guess is that 1 & 2 might be related. May be the `error()` is not signalled due to above problem --> `Error()` slot is not called --> the socket still keep writing & flushing a disconnected socket --> crash. – iammilind Jul 13 '17 at 11:05
  • Edited the answer to include the basic fix -- a call to `qRegisterMetaType`. Regarding the `flush` issue I'm not certain at this point. They could be related as you say -- SIGPIPE suggests that the `flush` may be causing a write *after* the other end has shutdown/closed. – G.M. Jul 13 '17 at 11:24
  • Yes, above solution fixes the 1st problem. The 2nd problem, I have to wait to get reproduced (hope it doesn't). BTW, when I look at the code of `qRegisterMetaType()`, it's definition being prefixed with `QT_DEPRECATED`. I am using the latest Qt 5.9. Is this right? Do we need to use `Q_DECLARE_METATYPE()` by any chance? Kindly explain that as well. – iammilind Jul 13 '17 at 11:41
  • Regarding the `QT_DEPRECATED` macro and `qRegisterMetaType`, I'm not sure why. The [documentation](http://doc.qt.io/qt-5/qmetatype.html#qRegisterMetaType-1) makes no mention of it being deprecated. As for `Q_DECLARE_METATYPE`, this is already used for `QAbstractSocket::SocketError` in the Qt headers so no need to use it explicitly in your own code. – G.M. Jul 13 '17 at 12:04
0

No, the sockets don't create a separate thread for read/write. Instead the OS raises the event on a given socket descriptor, whenever there is a read/write observed. This event should be queued. Hence for that Qt::QueuedConnection is preferred.

The QAbstractSocket::SocketError is impromptu and appears OS specific. It cannot be avoided. At the max, the socket can be destroyed when such error happens.

To avoid crashes, whenever there is a disconnection in the socket, following can be done:

void Destroy (QWebSocket* const pSocket)
{
  if(pSocket == nullptr)
    return;
  pSocket->disconnect();  // no further signal/slot
  pSocket->close();  // graceful closure
  pSocket->deleteLater(); // don't delete immediately; let the Qt take care
  pSocket = nullptr; // to avoid further undefined behaviour
}

Even after doing above, sometimes a socket crash happens due to write() operation. viz. When the socket close()-es, it tries to flush() all the writable data. During then, if the remote connection is already closed, then the OS crashes the program using a SIGPIPE event. Unfortunately it cannot be prevented in C++ using the std::exceptions.

The solutions mentioned in below post doesn't help:
How to prevent SIGPIPEs (or handle them properly)
This can be avoided by following:

if(pSocket->isValid())
  pSocket->sendBinaryMessage(QByteArray(...));

So isValid() helps in a situation, where a socket is trying to write something into an already disconnected remote socket connection.

iammilind
  • 68,093
  • 33
  • 169
  • 336
  • `This can be avoided by following: ...`. Unfortunately, this CANNOT be avoided this way, because reported socket state (retreived by calling isValid() or state() methods) at time of calling flush is ok, even if remote connection is already closed. It is the only Qt internal implementation able to solve issue. Qt bug? In the meantime I consider flush() method is broken. – Artem Pisarenko Oct 02 '20 at 09:09
  • Qt implementation should call send() with MSG_NOSIGNAL flag. – Artem Pisarenko Oct 02 '20 at 09:17
  • @ArtemPisarenko, with good amount of evidences, you may consider raising a bug with the Qt team. I have lost the track of this issue, as of today. But remember posting this answer, after it fixed issue in my code. – iammilind Oct 02 '20 at 09:27
  • I've just found I was wrong (partially). Actually, Qt implementation does it right way. It was just a bug in my application which made me think that SIGPIPE caused crash. No, it didn't. – Artem Pisarenko Oct 03 '20 at 19:23
  • In my case, source of confusion was the fact that debugger catches SIGPIPE, pauses execution and my bug being triggered right after continuing execution (ending with crash). One just need to configure debugger to ignore SIGPIPE. So it doesn't cause any harm. But you may catch it anyway regardless of whether you do isValid() check or no. Furthermore, flush() call will emit stateChanged signal in this case, so be careful in slots connected to it (at least, it was surprising for me and lead to bug I mentioned). – Artem Pisarenko Oct 03 '20 at 19:23