views:

207

answers:

2

I created a server with Perl under Windows (ActivePerl 5.10.1 build 1006) that forks upon being connected to, accepts some JSON data, and writes it to a database. I am running into a problem after 64 clients connect to the server, the error message being "Resource is not available" when trying to fork.

Running this code under Linux I found many defunct child process, which was solved by adding a wait() call on the parent. This however did not solve the problem. Running the code under Linux works past the 64 calls allowed in Windows.

I also spun up a virtual Windows server in case it was restrictions on the server, but a fresh install of Perl resulted in the same 64 connection limit.

Any ideas is appreciated.


use IO::Socket; 
use Net::hostent; 
use JSON;
use DBI;
use Data::Dumper;

my $port=shift || 9000;
my $clients_served = 0;

while(1){
  my $server = IO::Socket::INET->new( Proto => 'tcp', 
    LocalPort => $port, 
    Listen => 1, 
    Reuse => 1); 

  die "can't setup server" unless $server; 
  print "[Server $0 is running]\n"; 

#### 
# wait for a client to connect
# once it has, fork to a seperate thread and
# retrieve the JSON data
#### 
  while (my $client = $server->accept()) { 
    my $pid = fork();

      if ($pid != 0) {
        print ("Serving client " . $clients_served++ . "\n");
      }else{
        $client->autoflush(1); 
        my $JSONObject = JSON->new->ascii->pretty->allow_nonref();
        my $hostinfo = gethostbyaddr($client->peeraddr); 
        my $client_hostname = ($hostinfo->name || $client->peerhost);

        printf "connect from %s\n", $client_hostname;

        print " $client_hostname connected..\n";
        syswrite($client, "Reached Server\n", 2048);
        if (sysread($client, my $buffer, 2048) > 0) {

          foreach my $tasks($JSONObject->decode($buffer)){
            foreach my $task (@$tasks){
              insert_record($client_hostname, $task); #empty method, contents does not affect result
            }
          }
        }

        print " $client_hostname disconnected..\n";
        close $client; 
        exit 0;
      }
  }
  $server->close();
}

exit 0;
+1  A: 

Try reaping the zombie processes from the finished transactions. I can get your sample code to keep running if I include a couple more lines:

    use POSIX ':sys_wait_h';

    if ($pid != 0) {
        print ("Serving client " . $clients_served++ . "\n");
        1 while waitpid -1, WNOHANG > 0;

If you might have 64 simultaneous connections, you might have to think of something else -- it's no good to install a SIGCHLD handler on Windows.

mobrule
This is strange if true -- without a CHLD handler you shouldn't *get* zombies unless the parent is badly crashed. Even on Windows.
hobbs
Unix-style signals with Perl on Windows is a tremendous kludge. A wide variety of Windows events with different and unrelated mechanisms are shoehorned into the signal framework. As of now, the termination of a child process in Windows (actually a *psuedo-process*, a separate thread but not a separate process) does not deliver a SIGCHLD event to the parent, though you can use `waitpid -1` synchronously to detect if any children have finished and reap them.
mobrule
+1  A: 

This is a dodge since it doesn't answer your question directly, but sometimes the best way to remove bugs is to write less code -- why not let someone else do the process management and socket handling for you -- namely Net::Server? The Net::Server::Fork personality offers the same behavior as you're writing now, although personally I would think about Net::Server::PreFork instead.

With Net::Server, your app would look like:

use strict;
use warnings;
use base 'Net::Server::Fork';

sub process_request {
    my $self = shift;
    # Do JSON stuff here.
    # The client is attached to *STDIN and *STDOUT.
}

# You might omit this and the arguments to run() because
# Net::Server has command-line and config-file handling too.

my $port = shift || 90000;

__PACKAGE__->run(
    proto => "tcp",
    port => $port,
);

Which is really pretty tidy, I have to say.

hobbs
If I were building this for Linux I would use Net::Server. Unfortunately according to their CPAN site their tests don't pass in Windows, which caused me look elsewhere. Couldn't agree more that the Net::Server solution is much more elegant.
JGaudette
@JGaudette: where are you looking, exactly? CPAN Testers shows a nice healthy bunch of PASSes for Net::Server on Win32: http://matrix.cpantesters.org/?dist=Net-Server%200.97;reports=1;os=MSWin32
hobbs