Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: sf::SocketSelector::isReady() faultly returns true  (Read 6898 times)

0 Members and 1 Guest are viewing this topic.

Meph

  • Newbie
  • *
  • Posts: 2
    • View Profile
sf::SocketSelector::isReady() faultly returns true
« on: July 24, 2021, 12:14:51 am »
It seems I have encountered unintended/faulty behavior by sf::SocketSelector::isReady(), which currently defeats the purpose of utilizing sf::SocketSelector at all.
sf::SocketSelector as I unterstand it is a utility to keep track of handling multiple blocking sockets. It does so by providing the ability to wait on multiple sockets and to determine which socket is actually ready to be called on receiving information so as to avoid the blocking of execution.
In my code I have taken the default example given with the class description and adjusted it to my needs, as I think not by much.


void NetworkNode::networkingThread(sf::Uint16 tcp_listen_port)
{
        unsigned int i = 0;
        for(; i < 20 ;++i)
                if(tcp_listener.listen(tcp_listen_port+i) == sf::Socket::Status::Done) break;
        if(i >= 20) cerr << "NetworkNode::networkingThread(): Tcp Port could not be bound!\n";
        cout << "NetworkNode::networkingThread(): Tcp Port listens on " << tcp_listener.getLocalPort() << '\n';

        vector<std::future<bool> > processes_accepted_connections;

        while(threads_running)
        {
                //delete all processes that are accepting incoming connections once they are finished as there return values are not needed currently
                processes_accepted_connections.erase(std::remove_if(processes_accepted_connections.begin(), processes_accepted_connections.end(), [](const auto& e)->bool{ return e.valid(); }), processes_accepted_connections.end());

                //TODO establish connections here
                //connection can only be added here synchronously
                sf::SocketSelector socket_selector;
                socket_selector.add(tcp_listener);
                {
                        std::lock_guard c_lg_ (connections_lock);
                        for(auto& c : connections) socket_selector.add(*c.tcp);
                }
                if(socket_selector.wait(sf::milliseconds(50)))
                {
                        if(socket_selector.isReady(tcp_listener))
                        {
                                cout << "NetworkNode::networkingThread(): New incoming connection.\n";
                                Connection c;
                                if(tcp_listener.accept(*c.tcp) == sf::Socket::Done)
                                        processes_accepted_connections.emplace_back(std::async(&NetworkNode::processAcceptedConnection, this, std::move(c)));
                        }
                        else
                        {
                                cout << "NetworkNode::networkingThread(): Data packet incoming:" << '\n';
                                std::lock_guard c_lg_ (connections_lock);

                                for(auto iter = connections.begin(); iter != connections.end(); ++iter) if (socket_selector.isReady(*(iter->tcp)))
                                {
                                        if(iter->tcp->getRemoteAddress() == sf::IpAddress::None)
                                        {
                                                iter = connections.erase(iter);
                                                cout << "NetworkNode::networkingThread(): Removed 1 closed connection.\n";
                                                continue;
                                        }
                                        std::pair<Connection*, sf::Packet> rec (&*iter, sf::Packet());
                                        cout << "NetworkNode::networkingThread(): receiving ....\n";
                                        if(!socket_selector.isReady(*iter->tcp)) cerr << "Faulty pass of SocketSelector::isReady() socket cannot receive!\n";
                                        auto status = iter->tcp->receive(rec.second);
                                        cout << "NetworkNode::networkingThread(): received .... " << rec.second.getDataSize() << "\n";
                                        if(status == sf::Socket::Status::Disconnected)
                                        {
                                                iter = connections.erase(iter);
                                                cout << "NetworkNode::networkingThread(): " << iter->tcp->getRemoteAddress() << ':' << iter->tcp->getRemotePort() << " disconnected, removing connection.\n";
                                                continue;
                                        }
                                        cout << "NetworkNode::networkingThread(): Received from " << iter->tcp->getRemoteAddress() << ':' << iter->tcp->getRemotePort() << " with status " << status << '\n';
                                        std::lock_guard r_lg_ (received_lock);
                                        received.emplace_back(std::move(rec));
                                }
                        }
                }
        }
        cout << "NetworkNode::networkingThread() : exit\n";
}
 

The code is built so that a Node can establish a connection to another Node, or accept an incoming connection.
After initial tcp-connection is established code is executed asynchronously to handle the application side of establishing the connection, upon completion a connection being added to the list. To account for this change, the sf::SocketSelector::wait() is used with a timeout. A new sf::SocketSelector is actually created and populated on every rotation of the loop with the current list of connections.
After the application layer has exchanged initial data, all sockets are handled blocking, all data transfered is done so by utilizing sf::Packet.

In the following are the outputs of the 2 Nodes:
NetworkNode::networkingThread(): Tcp Port listens on 44444
NetworkNode::establishConnection(): 127.0.0.1:44445
NetworkNode::establishConnection(): Tcp connection established.
NetworkNode::establishConnection(): Sent greeting.
NetworkNode::establishConnection(): Greeting successfull!
NetworkNode::establishConnection(): Received info.
NetworkNode::establishConnection(): Sent connection info.
NetworkNode::sendRootInfo() to 127.0.0.1:44445
NetworkNode::sendRootInfo() pass
NetworkNode::establishConnection(): Sent root information.
NetworkNode::establishConnection(): Application Connection established.
NetworkNode::networkingThread(): Data packet incoming:
NetworkNode::networkingThread(): receiving ....
^C
 

NetworkNode::networkingThread(): Tcp Port listens on 44445
NetworkNode::networkingThread(): New incoming connection.
NetworkNode::processAcceptedConnection(): Accepted connection from 127.0.0.1:53778
NetworkNode::processAcceptedConnection():: Greeting received : <Hi, I am a decent node!>
NetworkNode::processAcceptedConnection(): Sent info.
NetworkNode::processAcceptedConnection(): Sent connection info.
NetworkNode::sendRootInfo() to 127.0.0.1:53778
NetworkNode::sendRootInfo() pass
NetworkNode::processAcceptedConnection(): Sent root information.
NetworkNode::processAcceptedConnection(): New connection added.
NetworkNode::networkingThread(): Data packet incoming:
NetworkNode::networkingThread(): receiving ....
NetworkNode::networkingThread(): received .... 94
NetworkNode::networkingThread(): Received from 127.0.0.1:53778 with status 0
From 127.0.0.1:53778 received topic <!rootinfo>
a70fd81ce28652bbf59872a6fa882710e200130d|[Simulation: Planetoids]
From 127.0.0.1:53778 received topic <>
NetworkNode::networkingThread(): Data packet incoming:
NetworkNode::networkingThread(): receiving ....
NetworkNode::networkingThread(): received .... 0
NetworkNode::networkingThread(): 0.0.0.0:0 disconnected, removing connection.
 

At the very end of the exchange of application layer information establishing their connection each node sends one data sf::Packet by default, called rootinfo, which holds some node specific information.

The shown logs show both nodes sending this information (sendRootInfo() up until the pass notice), but only one of them actually receives it.
Further, and this is the actual crux of the matter, both nodes seem to regard their blocking tcp sockets still ready, despite there actually being no action.
The first log shows that an attempt of receiving data is actually done, but never finished (the log shows "receiving ..." but never a "received"), until the process is killed (the ^C notice).
The second log shows that a piece of information was received correctly, but then goes on to receiving again, despite nothing being sent to it also blocking. Upon killing the application from the first log, the application in the second log closes the connection correctly as it was closed by the remote ending the blocking receive call with the respective Status code.
In both cases sf::SocketSelector::isReady() seems to return true, when it is not wanted to do, causing a blocking receive call that never returns, blocking the whole networkingThread().

Isn't the whole purpose of sf::SocketSelector preventing this exact scenario, to prevent a blocking call on a socket that is not ready to actually follow through?
I am at a loss.
« Last Edit: July 24, 2021, 12:20:53 am by Meph »

Meph

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: sf::SocketSelector::isReady() faultly returns true
« Reply #1 on: July 27, 2021, 10:12:37 pm »
I have found the issue.

Basically the protocol that leads up to this was bugged. By miscounting some bytes in previous receives this left the socket in a wrong state for receiving sf::Packet. Basically what happened was that the start of packets was wrongfully received beforehand.
Then, when the actual receive call for the packet came around the call interpreted the first bytes of data it got, if it even got the 4 initial bytes the package needs (for determining its size). Either way this caused the receive call to never return, either waiting for the 4 initial bytes for its size, or getting an undefined, probably huge number of bytes to receive further, assuming the bytes it interpreted as size because of the mentioned shift are random.

Given that the socket still returned ready, I guess the latter.

It's not up to me, but I guess this thread can be lain to rest.
« Last Edit: July 27, 2021, 10:18:30 pm by Meph »