Search code examples
cmultithreadingsocketsgethostbyname

C :: Threads Inconsistently Open a Sending Socket


I am building a C program and I’m wrestling with a weird – and inconsistent – thread and socket error with gethostbyname() that I’ve never seen before.

Some environmental stuff first: I’m working on a Ubuntu box, and my code is compiled with GCC:

root@ubuntu:/home/me/socketProject# gcc -v
...
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
root@ubuntu:/home/me/socketProject#

The objective here: I’ve developed another C program which will someday be a network service, I hope. The server is working fine, but I need to stress-test it. So I’ve built a client program to bombard it with simulated network requests. Once the server is up and listening on TCP 12345, the client program just needs to do the following:

  • For N times, spin up a detached worker thread
    • Each thread should open a sending socket, then send some data to 127.0.0.1, via TCP 12345
    • After sending, a thread should immediately terminate

This should be child’s play. I want to use large numbers of N to see what happens when my server is pounded.

Let me walk you through my code, then describe the problem. The program is relatively simple. There's a few structs and then main(). Basically, main() loops for N = numThreads, creating a detached worker thread each time. Each thread is given a populated package struct, with information it will use later:

typedef struct{
   int sock;
   struct sockaddr address;
   int addr_len;
} connection_t;

typedef struct{
   char* IP;
   int port, myNum;
} package;


void process(void* pack);                  // code for the thread, see below...


int main( int argc, char ** argv ){
   pthread_t  thread;
   int        numThreads = 10;             // or 100 or 1000 or whatever
   char       svrIP[20] = "127.0.0.1";
   int        portNumber = 12345;

   int i=0;
   for( ; i<numThreads; i++){

      package* pack   = (package*) malloc( sizeof(package) );
      pack->IP        = (char*) malloc( sizeof(char) * 20 );
      strcpy( pack->IP, svrIP );
      pack->port      = portNumber;
      pack->myNum     = i;

      pthread_create(&thread, NULL, process, (void*) pack);
      pthread_detach(thread);

      free( pack->IP );
      free( pack );
   }

   sleep(1);

   printf("END OF PROGRAM\n");
   return 0;
}

Everything above works great, regardless of what I set for numThreads. Now for the thread’s code. Upon birth, the thread tries to open a socket and then send a text string. Then the thread terminates. Note all that fprintf() code around gethostbyname() – I’ll be referring to it later:

void process(void* pack){
   int                  len, sock = -1;
   struct sockaddr_in   address;
   struct hostent *     host;

   char* msg = "Some text string here for the server...";

   printf("THREAD %d STARTED:  %s  --  %d\n", ((package*)pack)->myNum, ((package*)pack)->IP, ((package*)pack)->port );

   // create the socket
   sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (sock <= 0){
      fprintf( stderr, "Thread %d: error: cannot create socket\n", ((package*)pack)->myNum );
      pthread_exit(0);
   }

   // connect to server
   address.sin_family = AF_INET;
   address.sin_port = htons( ((package*)pack)->port );
   host = gethostbyname( ((package*)pack)->IP );
   if (!host){
      fprintf( stderr, "Thread %d: error: unknown host %s  --  Error is:  (%d) \”%s\”\n",
            ((package*)pack)->myNum,
            ((package*)pack)->IP,
            errno, strerror(errno));
      pthread_exit(0);
   }

   memcpy(&address.sin_addr, host->h_addr_list[0], host->h_length);
   if (connect(sock, (struct sockaddr *)&address, sizeof(address))){
      fprintf( stderr, "Thread %d: error: cannot connect to host %s\n", ((package*)pack)->myNum, ((package*)pack)->IP );
      pthread_exit(0);
   }

   printf("Thread %d: Sending message \”%s\”\n", ((package*)pack)->myNum, msg);

   len = strlen(msg);
   write(sock, &len, sizeof(int));
   write(sock, msg, len);

   printf("Thread %d: message sent, exiting...\n", ((package*)pack)->myNum );

   close(sock);

   pthread_exit(0);
}

Okay, so that’s the code. So here’s the issue:

Regardless of what value I set for numThreads, some worker threads successfully open a socket, and some don’t. The ones who fail are failing on that call to gethostbyname(). Check out the below; I’ve set numThreads to 10, which means I should be seeing Threads 0 through 9 launch:

me@ubuntu:/home/me/socketProject# ./runTest
THREAD 2 STARTED:  127.0.0.1  --  24601
THREAD 5 STARTED:  127.0.0.1  --  24601
THREAD 3 STARTED:  127.0.0.1  --  24601
THREAD 3 STARTED:  127.0.0.1  --  24601
THREAD 4 STARTED:  127.0.0.1  --  24601
THREAD 7 STARTED:  127.0.0.1  --  24601
Thread 7: Sending message "Some text string here for the server..."
Thread 7: message sent, exiting...
THREAD 7 STARTED:  127.0.0.1  --  24601
Thread 7: Sending message "Some text string here for the server..."
Thread 7: message sent, exiting...
THREAD 8 STARTED:  127.0.0.1  --  24601
Thread 8: Sending message "Some text string here for the server..."
Thread 8: message sent, exiting...
THREAD 9 STARTED:    --  24601
THREAD 9 STARTED:    --  24601
Thread 9: Sending message "Some text string here for the server..."
Thread 9: message sent, exiting...
Thread 9: error: unknown host   --  Error is:  (0) "Success"
Thread 9: error: unknown host   --  Error is:  (0) "Success"
Thread 9: error: unknown host   --  Error is:  (0) "Success"
Thread 9: error: unknown host   --  Error is:  (0) "Success"
Thread 9: error: unknown host   --  Error is:  (0) "Success"
Thread 9: error: unknown host   --  Error is:  (0) "Success"
END OF PROGRAM
me@ubuntu:/home/me/socketProject#

The server received four test messages, as the above output suggests.

Sooooooooo… there’s a lot of weirdness happening here. There must be some bug with those “THREAD X STARTED” messages, because it looks like Threads 0, 1, and 6 never launched, while somehow Threads 3, 7, and 9 launched twice. But numThreads == 10 and ten threads started, so I’ll ignore that issue for now.

Of greater concern is that six threads failed in their call to gethostbyname(). I’ll admit: for this chunk of code, I copied an example from a textbook, and I can’t remember where I got it. But I’m wondering if that if(!host) statement makes sense. There was no problem when I beta tested this in the non-threaded version, but now…

Of even bigger weirdness is that the Errno code is "Success." If its successful... why is the call failing? I feel like I'm missing something really, really obvious.

It also bothers me that this problem is inconsistent. Sometimes when I test this, 7/10 threads successfully send. Sometimes 1/10 do. My average success rate is about 3/10. If I’ve learned anything about C, its that inconsistent problems usually indicate that a variables has been successfully created but not initialized. What might need to be initialized here? The host struct? I’m not sure.

I’ve been Googling this for about three hours now, but not pulling up anything useful. Any suggestions or advice will be wildly appreciated.


Solution

  • Problem is here:

      pthread_create(&thread, NULL, process, (void*) pack);
      pthread_detach(thread);
    
      free( pack->IP );
      free( pack );
    

    ... you create a resource specific for the new thread (pack) and then immediately delete it before the thread has completed its work! These calls to free should be performed at the end of process