Very intermittent "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread"
-
I know questions about this message have cropped up at various times but I believe I am already handling things correctly with respect to what usually gives rise to it.
All reading and writing on the socket is encapsulated in a class that has been moved to a "communications thread" using
moveToThread
. All messages are then strictly passed via slots and signals from the main thread to/from the communications thread.This has worked without issue for a few years, but now I have had a few reports of very intermittent issues that seem to start with a
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
message appearing in my log.I am yet to see the issue myself so I have very little to go on.
The main thing that has changed in recent weeks is a switch from 5.9.6 to 5.15.10.
I know it's a long shot but posting here in case it rings any bells with anyone. Are there any circumstances, beyond the more fundamental ones that are usually discussed, that could give rise to this message appearing intermittently?
-
@Bob64 is it a member variable? If so - has it a proper parent?
Please show the code.
-
@Christian-Ehrlicher Hopefully the below will show the essence of it. It's basically a "JSON-RPC" like mechanism that the broader application uses. I am not sure exactly what you meant by it when you asked about parents but you will see that the
JsonServer
object that is moved to the comms thread is not given a parent when it is constructed. Should it be?Something to note is that, even in the cases where the issue arises, this object has already been used to make numerous remote calls without any errors before the issue happens.
class Rpc : public QObject { Q_OBJECT public: Rpc(QObject* parent = nullptr); ~Rpc(); void callMethodAsync(const QString& name, const QVariantMap& arguments); QVariant callMethodSync(const QString& name, const QVariantMap& arguments); signals: void asyncResponse(QVariant response); //... void remoteMethodRequest(QByteArray methodInfo); private slots: void receiveResponseAsync(QByteArray response); //... void stopCommsThread(); private: QThread m_commsThread; JsonServer* m_jsonServer; }; Rpc::Rpc(QObject* parent) : QObject(parent) { m_jsonServer = new JsonServer(); m_jsonServer->moveToThread(&m_commsThread); connect(&m_commsThread, &QThread::finished, m_jsonServer, &QObject::deleteLater); //... connect(this, &Rpc::remoteMethodRequest, m_jsonServer, &JsonServer::remoteMethodCall); connect(m_jsonServer, &JsonServer::methodResponse, this, &EngineRpc::receiveResponseAsync); //... m_commsThread.start(); } void Rpc::callMethodAsync(const QString& name, const QVariantMap& arguments) { QByteArray jsonRequest = jsonEncodeMethodCall(name, arguments); emit remoteMethodRequest(jsonRequest); } void Rpc::receiveResponseAsync(QByteArray response) { emit asyncResponse(jsonDecodeMessage(response)); }
By the way, the reason I am using a thread is that this turned out to be the most straightforward way to support a "synchronous method call" on this interface as well as the asynchronous call shown here. If I hadn't had a need for that, I could probably have avoided threading.
-
What is jsonsocket and where do you create the real socket? Is it a proper member of JSonSocket so it gets moved too?
-
@Christian-Ehrlicher
JsonServer
is a layer on top of the actual socket and is the owner of the socket. This is a very stripped down version but hopefully is enough to give the idea of it and clarify the ownership question. (I haven't shown the details of theSocketWriter
member but it's just a small helper that encapsulates thebytesWritten
signal handling ofQTcpSocket
.)class JsonServer : public QObject { Q_OBJECT public: JsonServer(); public slots: void remoteMethodCall(QByteArray methodCallInfo); signals: void methodResponse(QByteArray response); private slots: void readBytes(); private: bool m_isConnected; QByteArray m_buffer; QTcpSocket m_server; SocketWriter m_writer; }; JsonServer::JsonServer() : m_isConnected(false), m_server(this), m_writer(m_server) { // various connection handling, error handling, message processing details // excluded.... connect(&m_server, &QTcpSocket::readyRead, this, &JsonServer::readBytes); } void JsonServer::remoteMethodCall(QByteArray methodCallInfo) { m_writer.write(methodCallInfo); } void JsonServer::readBytes() { auto bytes = m_server.readAll(); m_buffer += bytes; // details omitted but essentially // emit methodResponse(response); // once a complete response is available in m_buffer }
-
This looks all good. Where do you open the tcp server? It should be done in the new thread. Maybe also connect to QThread::started() and open or even better create the tcp server in there just to be sure. I'm not yet sure if there was a problem with an internal member of QTcpServer not properly parented so it was not moved... but can't find a bug report about this.
/edit: maybe also run with QT_FATAL_WARNINGS to see where exactly the warning comes from
-
This looks all good. Where do you open the tcp server? It should be done in the new thread.
Maybe also connect to QThread::started() and open or even better create the tcp server in there just to be sure. I'm not yet sure if there was a problem with an internal member of QTcpServer not properly parented so it was not moved... but can't find a bug report about this.Apologies - my naming might be a bit confusing. Just to be clear, it's a
QTcpSocket
at this end and the server is actually the remote thing I am talking to. I'm using "server" in my names here as I am thinking of it as the interface to the server.I do have a
connectToHost
call in the class though, and that is definitely called some time after themoveToThread
call.Perhaps, along the lines of your suggestion, I should try deferring the construction of the
QTcpSocket
until the thread has started?/edit: maybe also run with QT_FATAL_WARNINGS to see where exactly the warning comes from
Thank you - I will try that.
-
@Bob64 said in Very intermittent "QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread":
I should try deferring the construction of the QTcpSocket until the thread has started
Yes. Or try to reproduce it in a small example. Does not look that hard 🙂