Mendes
Mendes

Reputation: 18511

Socket connect abort from a detached thread

I´m building a socket client where I need to implement timeout for connections, read, writes and also timeout for the protocol itself (lack of answer, etc.).

I´m thinking of using a simple timer in a detached thread that will be started on every transaction and then cancelled on transaction completion. This same approach will be used for protocol control using a different timeout.

To test is I did the following simple code:

#include <string>
#include <sstream>
#include <map>
#include <iostream>
#include <cstring>
#include <thread>

#ifdef _WIN32
#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/types.h>
#endif

#include <stdio.h>
#include <stdlib.h>

bool timerOn = false;
int currentSocket = 0;


void Timer(int seconds)
{
    int tick = seconds;

    while (tick > 0)
    {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        tick--;
    }

    if (timerOn)
        close(currentSocket);
}

void StartTimer(int seconds)
{
    timerOn = true;
    std::thread t(&Timer, seconds);
    t.detach();
}

void StopTimer()
{
    timerOn = false;
}


void Connect(std::string address, int port)
{
    struct addrinfo hints;
    struct addrinfo *result = NULL;
    struct addrinfo *rp = NULL;
    int sfd, s;

    std::memset(&hints, 0, sizeof(struct addrinfo));

    hints.ai_family = AF_UNSPEC;        /* Allow IPV4 or IPV6 */
    hints.ai_socktype = SOCK_STREAM;    
    hints.ai_flags = 0;
    hints.ai_protocol = 0;              

    std::string portStr;
    portStr = std::to_string(port);

    s = getaddrinfo(address.c_str(), portStr.c_str(), &hints, &result);

    if (s != 0)
    {
        std::stringstream ss;
        ss << "Cannot resolve hostname " << address << gai_strerror(s);
        throw std::runtime_error(ss.str());
    }

    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

        if (sfd == -1)
            continue;

        StartTimer(10);
        int sts = connect(sfd, rp->ai_addr, rp->ai_addrlen);
        StopTimer();

        if (sts == 0)
            break;

        close(sfd);
    }

    freeaddrinfo(result); /* Object no longer needed */

    if (rp == NULL)
    {
        std::stringstream ss;
        ss << "Cannot find server address at " << address << " port " << port;
        throw std::runtime_error(ss.str());
    }

    currentSocket = sfd;
}


int main()
{
    try
    {
        Connect("192.168.0.187", 9090);
        std::cout << "Connected to server. Congrats!!!" << std::endl;
    }
    catch (std::exception& ex)
    {
        std::cout << "Error connecting to server. Aborting." << std::endl;
        std::cout << ex.what() << std::endl;
    }
}

Closing the socket on timer is not canceling the 'connect' operation, forcing it to abort with error. I´ve tried also shutdown(sfd, SHUT_RDWR); with no success...

Is my approach invalid ? Why is it not working ?

How to force connect to abort with error from the detached thread ?

Upvotes: 0

Views: 848

Answers (1)

David Schwartz
David Schwartz

Reputation: 182827

Closing the socket on timer is not canceling the 'connect' operation, forcing it to abort with error.

Whoa! You absolutely can't do that. There's no possible way to know that the thread is actually blocked in connect (as opposed to being about to call connect) when you close the socket. Releasing a resource in one thread while another thread is, or might be, using it is a recipe for disaster.

Imagine this happens:

  1. A thread is about to call connect, so it arranges for a timeout.

  2. The timeout expires and the socket is closed.

  3. A thread in some library creates a new socket to use for some reason of its own, getting the same socket descriptor.

  4. The thread that was about to call connect finally gets scheduled and calls connect -- connecting the library's socket! Disaster.

You have two choices:

  1. Use a non-blocking connect operation.

  2. Use something like a signal to interrupt the thread that calls connect.

But I have to wonder why you are bothering. Why do you need to abort the connect? If you need to do something else if the connect hasn't succeeded before the timeout, just go ahead and do it.

Upvotes: 4

Related Questions