1

I am facing one of the strangest programming problems in my life. I've built a few servers in the past and the clients would connect normally, without any problems.

Now I'm creating one which is basically a web server. However, I'm facing a VERY strange situation (at least to me).

Suppose that you connect to localhost:8080 and that accept() accepts your connection and then the code will process your request in a separate thread (the idea is to have multiple forks and threads across each child - that's implemented on another file temporarily but I'm facing this issue on that setup as well so...better make it simple first). So your request gets processed but then after being processed and the socket being closed AND you see the output on your browser, accept() accepts a connection again - but no one connects of course because only one connection was created.

errno = 0 (Success) after recv (that's where the program blows up) recv returns 0 though - so no bytes read (of course, because the connection was not supposed to exist)

int main(int argc, char * argv[]){
  int sock;
  int fd_list[2];
  int fork_id;

  /* Socket */
  sock=create_socket(PORT);

    int i, active_n=0;
    pthread_t tvec;
    char address[BUFFSIZE];
    thread_buffer t_buffer;
    int msgsock;

    conf = read_config("./www.config");
    if(conf == NULL)
    {
        conf = (config*)malloc(sizeof(config));
        if(conf == NULL)
        {
            perror("\nError allocating configuration:");
            exit(-1);
        }

        // Set defaults
        sprintf(conf->httpdocs, DOCUMENT_ROOT);
        sprintf(conf->cgibin, CGI_ROOT);
    }

    while(cicle) {

        printf("\tWaiting for connections\n");
        // Waits for a client
        msgsock = wait_connection(sock, address);
        printf("\nSocket: %d\n", msgsock);

        t_buffer.msg = &address;
        t_buffer.sock = msgsock;
        t_buffer.conf = conf;

        /* Send socket to thread */

        if (pthread_create(&tvec, NULL, thread_func, (void*)&t_buffer) != 0)
        {
                perror("Error creating thread: ");
                exit(-1);

        }
  }

  free(conf);

  return 0;
}

Here are two important functions used:

int create_socket(int port) {
    struct sockaddr_in server, remote;
    char buffer[BUFF];
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("opening stream socket");
        exit(1);
    }

    server.sin_family  = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_in))) {
        perror("binding stream socket");
        exit(1);
    }
    gethostname(buffer, BUFF);
    printf("\n\tServidor a espera de ligações.\n");
    printf("\tUse o endereço %s:%d\n\n", buffer,port);

    if (listen(sock, MAXPENDING) < 0) {
        perror("Impossível criar o socket. O servidor vai sair.\n");
        exit(1);
    }
    return(sock);
}

int wait_connection(int serversock, char *remote_address){
    int clientlen;
    int clientsock;

    struct sockaddr_in  echoclient;

    clientlen = sizeof(echoclient);
    /* Wait for client connection */
    if ((clientsock = accept(serversock, (struct sockaddr *) &echoclient, &clientlen)) < 0)
    {
        perror("Impossivel estabelecer ligacao ao cliente. O servidor vai sair.\n");
        exit(-1);
    }

    printf("\n11111111111111Received request - %d\n", clientsock);

    sprintf(remote_address, "%s", inet_ntoa(echoclient.sin_addr));

    return clientsock;
}

So basically you'd see: 11111111111111Received request - D

D is different both times so the fd is different definitely.

Twice! One after the other has been processed and then it blows up after recv in the thread function. Some times it takes a bit for the second to be processed and show but it does after a few seconds. Now, this doesn't always happen. Some times it does, some times it doesn't.

It's so weird...

I've rolled out the possibility of being an addon causing it to reconnect or something because Apache's ab tool causes the same issue after a few requests.

I'd like to note that even if I Don't run a thread for the client and simply close the socket, it happens as well! I've considered the possibility of the headers not being fully read and therefore the browsers sends another request. But the browser receives the data back properly otherwise it wouldn't show the result fine and if it shows the result fine, the connection must have been closed well - otherwise a connection reset should appear.

Any tips? I appreciate your help.

EDIT: If I take out the start thread part of the code, sometimes the connection is accepted 4, 5, 6 times...

EDIT 2: Note that I know that the program blows up after recv failing, I exit on purpose.

  • I'm using Apache's ab tool to test it and I'm getting the same results so I doubt it's the browser. As for the fork thing, I'm just using a simple threaded server setup right now to test it, the forks system is commented somewhere else. – Diogo Parrinha May 15 '14 at 21:45
  • The return value of `recv` will be 0 when the peer has performed an orderly shutdown. You should close the socket immediately. Maybe your browser is connecting multiple times. – Chnossos May 15 '14 at 22:41
  • The thing is, ab does that too! Not just my browser. Apache's ab does it as well. I'm currently closing the socket when that happens and exiting the respective thread (in the code I posted I shutdown the program so I could test it) – Diogo Parrinha May 15 '14 at 22:43
  • You talked about `fork()`, too. Are you sure you're not duplicating something here ? – Chnossos May 15 '14 at 22:43
  • Yes, if you see what I said carefully (in my post and in my answers below) you will see that the forking part is commented, not running at all. What you see in the first post is most of the code without includes and such. – Diogo Parrinha May 15 '14 at 22:47
  • Sounds like your client is connecting twice (possibly after forking?) but only sending a request on one connection. When it exits, both connections close and the server sees an immediate eof on the second connection. – Chris Dodd May 15 '14 at 23:04
  • I have said quite a few times already that I do NOT do the forking here, that is commented and the current code uses threads only. – Diogo Parrinha May 15 '14 at 23:52

2 Answers2

2

This is certainly a bug waiting to happen:

  pthread_create(&tvec, NULL, thread_func, (void*)&t_buffer

You're passing t_buffer, a local variable, to your thread. The next time you accept a client, which can happen before another client finished, you'll pass the same variable to that thread too, leading to a lot of very indeterministic behavior.(e.g. 2 threads reading from the same connection, double close() on a descriptor and other oddities. )

Instead of passing the same local variable to every thread, dynamically allocate a new t_buffer for each new client.

nos
  • 223,662
  • 58
  • 417
  • 506
  • You're right, totally missed that! I'll do that and will get back with more information soon. Thank you very much. – Diogo Parrinha May 15 '14 at 21:52
  • 2
    This has got to be the most common bug when using `pthread_create()`. – Dietrich Epp May 15 '14 at 21:58
  • @nos I've done that but I'm still facing the same issue so we can rule this out as the cause for this one! (though it might have been for some others which I had not noticed) – Diogo Parrinha May 15 '14 at 21:59
  • Even if you allocate separate memory for each `t_buffer`, it still contains a pointer to `address` which is local to the main thread, so every single thread is going to be sharing this resource, and `wait_connection()` is going to be overwriting it each time you get a connection. This code is very thread-unsafe, it's not remotely surprising that you're getting "strange" behavior. – Crowman May 15 '14 at 22:54
  • @DiogoParrinha Then post your new code, perhaps you didn't do it right. Also the code for thread_func. And do a bit of debugging, e.g. figure out the source IP address and port number, and determine if they make sense. wireshark is also a very useful tool here, so you can see what's actually going on on the wire. – nos May 15 '14 at 22:56
  • @Paul Griffiths thank you for your suggestion, I'll change that too. – Diogo Parrinha May 15 '14 at 22:57
  • @nos I don't think that's necessary, the only issue should be the one pointed by Paul. I'll see if I need to post more code after trying it. – Diogo Parrinha May 15 '14 at 22:58
0

Suppose ... after being processed and the socket being closed AND you see the output on your browser, accept() accepts a connection again - but no one connects of course because only one connection was created.

So if no-one connects, there is nothing to accept(), so this never happens.

So whatever you're seeing, that isn't it.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Hence why I'm saying this is one of the strangest issues I've found throughout my life when programming. Why would accept() process two requests when ONLY One is sent? – Diogo Parrinha May 15 '14 at 21:58