balls and socket

jjohn on 2003-06-02T21:29:54

Even though this is a web site devoted to technical content, readers of this blog will have noticed by now that I don't talk about technical matters much in my blog. That's because I have other interests besides computers, despite the volumes of pornography and spam it provides me with. This entry is a different. I want to talk about some of the problems and solutions I've found while working with the perl class IO::Socket.

I confess that I'm not as strong as I'd like to be network programming. Sure I've read (most) of Lincoln Stein's excellent Network Programing with Perl, thumbed through Perl Cookbook and even Read The Fine Manual but there are lots of issues that come up when I try to do socket level stuff. Debugging at this level is painful. Recently I wrote some an XMLRPC server based on HTTP::Daemon and Frontier::RPC2. Why didn't I use Frontier::Daemon? I needed much finer grained control than that pre-baked class provides. I've used HTTP::Daemon before and it appears to be a jolly good module.

And then entered Red Hat 8.0.

Mysterious, my sub-class of HTTP::Daemon which had worked fine on RH 7.x seem to hang on RH 8.0. I traced to the cause to a select() function that never detected that the client socket was ready to read. So select() blocked until it timed out (180 seconds later). This bug sent the (Leostream) team and me back to vanilla socket filehandles (abandoning HTTP::Daemon all together).

Time passes.

The vanilla socket code that replace my IO::Socket extravaganza looked like this:

  # ... socket initization code omitted
  while (accept(NS,S)) {
    my $cpid;
    defined ($cpid = fork) or next;
    if ($cpid) {
	next;
    }

    while () {
	# read in request
    }
  }

Ok, this is a forking web server. The forking code makes the socket stuff a little weirder, but this code worked swimmingly in older projects. Yet in the lab, this code would successfully read from (NS) the first connection but fail to accept any further connections. This is baffled us mightily until we were a bit more pedantic in closing our socket handles.

   while (accept(NS,S)) {
      my $cpid;
      defined ($cpid = fork) or next;
      if ($cpid) {
	close(NS);
	next;
      }
      close(S);
      while () {
      }
   }

This code works as advertized.

Time passes.

The next socket problem that appear in this seemingly unassuming code to does a socket connect to make sure that there's some server listening on the other end.

   return IO::Socket->new(PeerAddr => $host,
			  PeerPort => $port,
	                  Timeout  => 2,
			  Domain   => AF_INET,
			  Proto    => 'tcp',
			 ) ? 1 : 0;

Seems simple enough. The code creates a client socket to connect to $host:$port. If a valid socket connection is made, the function returns 1. Otherwise, it returns 0. What could possibly go wrong?

It appears that when a this code talks talks to RH 8.0 server that new() doesn't return until the timeout.

The solution was to close the socket explicitly.

While I haven't isolated the select() "bug," I feel it must be related to sloppy socket code that doesn't close dead sockets explicitly.

The moral of this cautionary tale is to always explicitly close sockets, even if they seem to go out of scope on their own.

I don't know if the problem is really related to RH 8.0 or not.

Has anyone else run into similar socket problems?