Why am I getting runtime errors, with Sockets (TCP/Web) when moved to another thread? Is `QObject::moveToThread()` a synchronous call? [duplicate]

[亡魂溺海] 提交于 2020-03-05 06:06:44

问题


I am moving some sockets from main thread to worker thread and processing readyRead(), close(), write() etc. on the new thread. Rarely I see below dangerous warning:

"QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"

Usually the execution after this warning results in undefined behaviour (crash / uncaught exception / normal).
Have checked all the signal/slot to the socket and they seem proper. From my experience, usually the above warning will be frequent or quick if there is any coding irregularity.

Now I am suspecting if moveToThread() does its job as expected or not! Because from QEvent documentation a QEvent::ThreadChange is raised as the last event on the current thread during this function call.

The object is moved to another thread. This is the last event sent to this object in the previous thread. See QObject::moveToThread()1.
1 A QEvent::ThreadChange event is sent to this object just before the thread affinity is changed. You can handle this event to perform any special processing.

In my code, I am not checking for this event. Rather I assume that, once the moveToThread() is finished, the thread affinity is changed and the new "signal/slot" on the object is guaranteed on the new thread.

Question:

  1. Is it safe to move the sockets to another thread?
  2. Will moveToThread() assure that the signals on that object are also moved to the new thread just after this function?
  3. How should it be designed to assure no threading irregularity with Sockets?

BTW, in the latest Qt debugger it lands on following code segment:

// qsocketnotifier.cpp
if (Q_UNLIKELY(thread() != QThread::currentThread())) {
    qWarning("QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread");
    return;
}

Below is the stack frame for the main thread:

Below is the stack frame for the worker thread, which halts on the logging:


回答1:


Qt sockets are NOT supported with moveToThread() idiom!!!

I wish Qt had documented this fact as LOUD as above. I have spent weeks of debugging an issue which is not meant for fixing. Socket moveToThread() issue is so complicated, that it doesn't happen consistently. I could reproduce it consistently only after testing a customised QTcpServer subclass with lots of socket open/close scenario.

Later I came across this post: How do I execute QTcpSocket in a different thread?. This answer explicitly states that sockets are not supported for the movement across the threads.

Here are the documentations from QTcpServer

Note: The returned QTcpSocket object cannot be used from another thread. If you want to use an incoming connection from another thread, you need to override incomingConnection().

Note: If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the "socketDescriptor" to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.

Probably this will apply to the QWebSocketServer too!


So the best approach is to overload incomingConnection() and call its internal in another thread.

void MyTcpServer::incomingConnection (qintptr socketDescriptor) override
{
  emit SignalToDifferentThread(socketDescriptor);
}

If done so, the addPendingConnection() and nextPendingConnection() idiom may not be required, even though it's mentioned in a note.

Note: If another socket is created in the reimplementation of this method, it needs to be added to the Pending Connections mechanism by calling addPendingConnection(). (<-- this may not apply for the sockets in different thread)


QWebSocketServer is stricter than QTcpServer

This is another fact, which wasted more time for me. In QTcpServer, we can overload the incomingConnection() and pass on the socket descriptor to another thread to create a socket. However, QWebSocketServer doesn't have such overload. Mostly because, QSslSocket received in QTcpServer gets upgraded to QWebSocket and passed on to the QWebSocketServer via its handleConnection() method.

So QWebSocket must be created where QWebSocketServer resides!. Hence in whichever threads, we want a QWebSocket to be created, all those threads need to have an object of QWebSocketServer. Idiomatically we can have one QTcpServer listening on a single port and can keep passing its QTcpSockets (upgraded to QWebSockets) to these various threads' QWebSocketServers in a load balancing way.


A message to Qt developers

I wish that, you can save lot of time of the developers relying on the Qt library by a simple compilation error. This will negate all the wrong articles floating around internet which suggest how to use moveToThread() on a QTcpSocket. Just introduce below method:

class QTcpServer / QTcpSocket / QWebSocketServer / QWebSocket : public QObject
{
  Q_OBJECT
  ...
  // Create this object in a desired thread instead of moving
  private: void moveToThread (QThread*) = delete; // hiding `QObject::moveToThread()`
};

Update: Raised QTBUG-82373.




回答2:


A quote from the docs, that provides the answer:

Warning: This function is not thread-safe; the current thread must be same as the current thread affinity. In other words, this function can only "push" an object from the current thread to another thread, it cannot "pull" an object from any arbitrary thread to the current thread. There is one exception to this rule however: objects with no thread affinity can be "pulled" to the current thread.



来源:https://stackoverflow.com/questions/60019664/why-am-i-getting-runtime-errors-with-sockets-tcp-web-when-moved-to-another-th

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!