Hello again. I'm still on this problem with some new findings and some clarifications on the topic.
Firstly, I want to clean up the code I've posted before. So here is an updated class:
socket_example.hpp
class SocketExample : public QObject
{
Q_OBJECT
public:
SocketExample();
~SocketExample();
bool Initialize();
private:
QTcpSocket clientSocket;
int timerId;
bool clientBound;
bool aboutToClose;
protected:
void timerEvent(QTimerEvent *) override;
private slots:
void onErrorOccurred(QTcpSocket::SocketError);
void onStateChanged(QTcpSocket::SocketState);
void onConnected();
void onReadyRead();
void onDisconnected();
void bindSocket();
}
socket_example.cpp
SocketExample::SocketExample()
: clientSocket(this)
, timerId(0)
, clientBound(false)
, aboutToClose(false)
{
connect(&clientSocket, &QTcpSocket::errorOccurred, this, &SocketExample::onErrorOccurred, Qt::ConnectionType::DirectConnection);
connect(&clientSocket, &QTcpSocket::stateChanged, this, &SocketExample::onStateChanged, Qt::ConnectionType::DirectConnection);
connect(&clientSocket, &QTcpSocket::connected, this, &SocketExample::onConnected, Qt::ConnectionType::DirectConnection);
connect(&clientSocket, &QTcpSocket::readyRead, this, &SocketExample::onReadyRead, Qt::ConnectionType::DirectConnection);
connect(&clientSocket, &QTcpSocket::disconnected, this, &SocketExample::onDisconnected, Qt::ConnectionType::DirectConnection);
}
SocketExample::~SocketExample()
{
aboutToClose = true;
if (timerId)
{
killTimer(timerId);
timerId = 0;
}
clientSocket.abort();
}
SocketExample::Initialize()
{
clientBound = clientSocket.bind("172.26.28.1")
if (!clientBound)
return false;
timerId = startTimer(std::chrono::milliseconds(20000), Qt::TimerType::VeryCoarseTimer);
if (!timerId)
return false;
return true;
}
/*override*/
void SocketExample::timerEvent(QTimerEvent *)
{
switch (clientSocket.state())
{
case QTcpSocket::SocketState::UnconnectedState:
case QTcpSocket::SocketState::BoundState:
clientSocket.connectToHost(QHostAddress("172.26.20.30"), QIODevice::OpenMode::ReadWrite);
break;
default:
break;
}
}
/*slots*/
void SocketExample::onErrorOccurred(QTcpSocket::SocketError socketError)
{
switch (socketError)
{
case QTcpSocket::SocketError::ConnectionRefusedError:
break;
default:
clientSocket.abort();
break;
}
}
void SocketExample::onStateChanged(QTcpSocket::SocketState socketState)
{
// print string representation of *socketState*
}
void SocketExample::onConnected()
{
// omitted details
}
void SocketExample::onReadyRead()
{
// omitted details
}
void SocketExample::onDisconnected()
{
clientBound = false;
if (!aboutToClose)
QTimer(0, this, &SocketExample::bindSocket);
}
void SocketExample::bindSocket()
{
if (clientSocket.localAddress() == QHostAddress("172.26.28.1"))
{
clientBound = true;
if (timerId)
return;
timerId = startTimer(std::chrono::milliseconds(20000), Qt::TimerType::VeryCoarseTimer);
if (!timerId)
{
// print about failed timer
return;
}
}
else
{
clientBound = clientSocket.bind(QHostAddress("172.26.28.1"));
if (!clientBound)
{
if (timerId)
{
killTimer(timerId);
timerId = 0;
}
// print about failed bind
return;
}
if (timerId)
return;
timerId = startTimer(std::chrono::milliseconds(20000), Qt::TimerType::VeryCoarseTimer);
if (!timerId)
{
// print about failed timer
return;
}
}
}
Now it looks a bit cleaner and the main difference here is the slot SocketExample::bindSocket which tries its best to bind a socket to a given address and start a reconnection timer.
Just to recap the original post:
I want to connect to a server in another network which runs a QTcpServer.
Both the server and my program (i.e. the client) are written with Qt 5.15.8 and run on Debian 12.
Sometimes the network is unaccessible which leads to rejection of connection attempts, I simulate this behaviour with nftables and rejected rule as follows:
nft add table ip filter
nft add chain ip filter output { type filter hook output priority 0 \; }
nft add rule ip filter output ip daddr 172.26.20.30 reject
I simulate this behaviour because for some reason when a connection attempt gets rejected the socket starts to spam connect and poll system calls hunders of times per second.
This was the original post.
The problem stays the same: rejected connection attempts are causing CPU usage spike because connect and poll system calls are called hunders of times per second.
And they start, happen and end between my QTcpSocket::connectToHost call and SocketExample::onErrorOccurred slot fires for the very first time. So I can't catch this hapenning, since control flow returns to my code after spamming has already ended.
Now to new details: I have built libQt5Network.so.5.15.8 with debugging symbols and extended debugging messages in the code.
Here's what I got:
we follow the standard debug messages of the network module code and getting messages about the socket changed state
14:14:01 [D] QAbstractSocket::connectToHost([172.26.20.30], 8406, 3)...
14:14:01 [D] QAbstractSocket::connectToHost("172.26.20.30", 8406, 3)...
14:14:01 [D] QTcpSocket::SocketState::HostLookupState
14:14:01 [D] QAbstractSocketPrivate::_q_startConnecting(const QHostInfo &) at qabstractsocket.cpp:1015
14:14:01 [D] QAbstractSocketPrivate::_q_startConnecting(hostInfo == {172.26.20.30})
14:14:01 [D] QTcpSocket::SocketState::ConnectingState
14:14:01 [D] QAbstractSocketPrivate::_q_connectToNextAddress() at qabstractsocket.cpp:1087
14:14:01 [D] QAbstractSocketPrivate::_q_connectToNextAddress(), connecting to 172.26.20.30:8406, 1 left to try
14:14:01 [D] QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) at qnativesocketengine.cpp:597
14:14:01 [D] address==172.26.20.30
14:14:01 [D] port==8406
14:14:01 [D] QNativeSocketEnginePrivate::nativeConnect() 10
14:14:01 [D] connectResult==-1 (return value of ::connect)
14:14:01 [D] errno==115 (Operation now in progress)
then we can see how the internal socket state changes from BoundState to ConnectingState
14:14:01 [D] QAbstractSocketEnginePrivate::socketState==4 (before switch)
14:14:01 [D] QAbstractSocketEnginePrivate::socketState==2 (after switch)
14:14:01 [D] QNativeSocketEnginePrivate::nativeConnect(172.26.20.30, 8406) == false (Connection in progress)
14:14:01 [D] QAbstractSocket::connectToHost("172.26.20.30", 8406) == false (connection in progress)
And here I found something new: as soon as first connection attempt ends the internal notifier gets a connection notification from QCoreApplication::notifyInternal2 which through several jumps calls connect system call:
#0 QNativeSocketEnginePrivate::nativeConnect (this=this@entry=0x5650efa17f10, addr=..., port=port@entry=8406) at socket/qnativesocketengine_unix.cpp:457
#1 0x00007f2f4fb4655e in QNativeSocketEngine::connectToHost (this=0x5650efa00ae0, address=..., port=<optimized out>) at socket/qnativesocketengine.cpp:617
#2 0x00007f2f4fb466bc in QNativeSocketEngine::connectionNotification (this=0x5650efa00ae0) at socket/qnativesocketengine.cpp:639
#3 0x00007f2f4fb47910 in QWriteNotifier::event (this=0x5650efaad4c0, e=0x7ffedc03ac80) at socket/qnativesocketengine.cpp:1330
#4 0x00007f2f4e6b16cd in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /lib/x86_64-linux-gnu/libQt5Core.so.5
#5 0x00007f2f4e70a38d in ?? () from /lib/x86_64-linux-gnu/libQt5Core.so.5
14:14:01 [D] QWriteNotifier::event(QEvent *e) at qnativesocketengine.cpp:1303
14:14:01 [D] e->type()==50
14:14:01 [D] engine->connectionNotification()
14:14:01 [D] QNativeSocketEngine::connectionNotification() at qnativesocketengine.cpp:621
14:14:01 [D] QAbstractSocketEngine::state()==2
14:14:01 [D] QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) at qnativesocketengine.cpp:597
14:14:01 [D] address==172.26.20.30
14:14:01 [D] port==8406
14:14:01 [D] QNativeSocketEnginePrivate::nativeConnect() 10
14:14:01 [D] connectResult==-1 (return value of ::connect)
14:14:01 [D] errno==114 (Operation already in progress)
14:14:01 [D] QAbstractSocketEnginePrivate::socketState==2 (before switch)
14:14:01 [D] QAbstractSocketEnginePrivate::socketState==2 (after switch)
14:14:01 [D] QNativeSocketEnginePrivate::nativeConnect(172.26.20.30, 8406) == false (Connection in progress)
14:14:01 [D] QWriteNotifier::event(QEvent *e) at qnativesocketengine.cpp:1303
14:14:01 [D] e->type()==50
14:14:01 [D] engine->connectionNotification()
14:14:01 [D] QNativeSocketEngine::connectionNotification() at qnativesocketengine.cpp:621
So this notification being sent hundreds of times per second is causing at least connect system call spam, I'm still not sure what spams poll system call though.
Connection notifications are sent many times when finally some internal timer fires and one of connect calls returns ECONNREFUSED which in the end changes the socket state to UnconnectedState and ends the spamming:
14:14:02 [D] QWriteNotifier::event(QEvent *e) at qnativesocketengine.cpp:1303
14:14:02 [D] e->type()==50
14:14:02 [D] engine->connectionNotification()
14:14:02 [D] QNativeSocketEngine::connectionNotification() at qnativesocketengine.cpp:621
14:14:02 [D] QAbstractSocketEngine::state()==2
14:14:02 [D] QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) at qnativesocketengine.cpp:597
14:14:02 [D] address==172.26.20.30
14:14:02 [D] port==8406
14:14:02 [D] QNativeSocketEnginePrivate::nativeConnect() 10
14:14:02 [D] connectResult==-1 (return value of ::connect)
14:14:02 [D] errno==111 (Connection refused)
14:14:02 [D] QAbstractSocketEnginePrivate::socketState==2 (before switch)
14:14:02 [D] QAbstractSocketEnginePrivate::socketState==0 (after switch)
14:14:02 [D] QNativeSocketEnginePrivate::nativeConnect(172.26.20.30, 8406) == false (Connection refused)
14:14:02 [D] QAbstractSocketPrivate::connectionNotification() at qabstractsocket.cpp:822
14:14:02 [D] QAbstractSocketPrivate::state==2
14:14:02 [D] QAbstractSocketPrivate::connectionNotification() testing connection
14:14:02 [D] QAbstractSocketPrivate::_q_testConnection() at qabstractsocket.cpp:1187
14:14:02 [D] QAbstractSocketPrivate::_q_testConnection() connection failed, checking for alternative addresses
14:14:03 [D] QAbstractSocketPrivate::_q_connectToNextAddress() at qabstractsocket.cpp:1087
14:14:03 [D] QAbstractSocketPrivate::_q_connectToNextAddress(), all addresses failed.
14:14:03 [D] QTcpSocket::SocketState::UnconnectedState
So this is some new details I found, maybe a similar problem has already been discussed somewhere. I would appreciate any input on the topic and new details.
Many thanks,
Daniel