Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hello. I'm not terribly well versed with Perl as I do most of my programming in PHP, however, I'm able to read Perl scripts well enough to generally have a good idea of what's going on.

I'm attempting to debug a script which I myself did not write. The script is a wrapper for a PHP script and works as a POP3 server. (PHP handles the POP3 functions, the Perl script seems to handle all of the connections/sockets and calls to PHP).

The script runs continuously in the background (using it as a POP3 daemon) and generally seems to create way too many child processes and litters my process list with zombie processes.

Every so often, the script kicks out warnings which I believe are related to this problem.

The errors:

Use of uninitialized value in ref-to-glob cast at ./zpopd.pl line 148. Bad symbol for filehandle at ./zpopd.pl line 148.

I've commented line 148 in the main code below to make it easier to find. (Look for: #******************Line 148 is below****************)

So without further ado, here is the main script. Sorry for the length (just over 200 lines):

main script (zpopd.pl):

#!/usr/bin/perl -w #use warnings; use IO::Socket; use MIME::Base64 (); require "zpopd.config.pl"; # ********* FUNCTIONS ********* sub print_timestamp { print time(); print ": "; } sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" } sub write_child_pid_file { if (!open(PIDFILE, ">$pid_dir/@_")) { print "Error: Could not write child pid to file "; print "$pid_dir/@_\n"; exit; } close(PIDFILE); } my $sleep_time = 2; my %children_pid; my %connection_info; my %children_time; my $result; my $parent_pid; my $pid; my $x; my $received_string; my $n_connections; my $new_sock; $result = 0; $listen_ip_address = $listen_ip_address; $welcome_message = "+OK $welcome_message\r\n"; $goodbye_message = "+OK $goodbye_message\r\n"; $timeout_message = "+OK $timeout_message\r\n"; $global_max_connections = $global_max_connections; $max_connections = $max_connections; $timeout = $timeout; $result = `stat -c %A $pid_dir 2>&1 | cat | cut -b 1`; $result =~ s/\n//; $result =~ s/\r//; if ($result ne "d") { #print "Error: Could not access directory $pid_dir\n"; #exit; } $result = ""; if (!open(PIDFILE, ">$pid_dir/$parent_pid_filename")) { print "Error: Could not write parent pid to file "; print "$pid_dir/$parent_pid_filename\n"; exit; } $pid = $$; printf(PIDFILE $pid); close(PIDFILE); my $sock = new IO::Socket::INET( LocalPort => "$listen_port", Proto => 'tcp', Listen => $global_max_connections, Reuse => 1); die "Could not create socket: $!\n" unless $sock; logmsg "server started on port $listen_port"; $n_connections = 0; local $SIG{ALRM} = sub { foreach my $key (keys %children_time) { if ((time() - $children_time{$key}) >= $timeout) { kill("TERM", $key); delete($children_time{$key}); } } }; local $SIG{INT} = sub { if ($pid == 0) # is this process a child? { unlink("$pid_dir/$$"); } else # or is it a parent { wait(); print_timestamp(); print "Server with PID $$ exiting.\n"; unlink("$pid_dir/$parent_pid_filename"); } exit; }; local $SIG{QUIT} = sub { unlink("$pid_dir/$parent_pid_filename"); unlink("$pid_dir/$parent_pid_filename"); print "Server shutdown cleanly\n"; exit; }; local $SIG{USR1} = sub { my $child_pid; my $client_ip_address; $child_pid = wait(); if ($child_pid != -1) { $client_ip_address = $children_pid{"$child_pid"}; $connection_info{"$client_ip_address"}--; unlink("$pid_dir/$child_pid"); delete($children_pid{"$child_pid"}); delete($children_time{"$child_pid"}); $n_connections--; } }; local $SIG{TERM} = sub { #******************Line 148 is below**************** my $client_sock = getpeername($new_sock); my($client_port,$client_addr) = sockaddr_in($client_sock); my $client_ip_addr = inet_ntoa($client_addr); send($new_sock, $timeout_message, 0); kill("USR1", getppid()); $param = $$."|$client_ip_addr|quit"; $param = MIME::Base64::encode($param); $param =~ s/\r\n/_/g; $param =~ s/\n/_/g; `/usr/local/apache/php/bin/php -f ./zpop.php $param > /dev/null`; exit; }; while (1) { alarm $sleep_time; $new_sock = $sock->accept(); alarm 0; if (($n_connections >= $global_max_connections) && defined($new_s +ock)) { close($new_sock); next; } if (!defined($new_sock)) { next; } my $client_sock = getpeername($new_sock); my($client_port,$client_addr) = sockaddr_in($client_sock); my $client_ip_addr = inet_ntoa($client_addr); if (!defined($connection_info{"$client_ip_addr"})) { $connection_info{"$client_ip_addr"} = 1; } else { if ($connection_info{"$client_ip_addr"} >= $max_connections) { close($new_sock); next; } else { $connection_info{"$client_ip_addr"}++; } } $pid = fork(); if ($pid == 0) # child process { write_child_pid_file($$); send($new_sock, $welcome_message, 0); while(<$new_sock>) { $received_string = $_; if (($received_string =~ /^quit/i)) { send($new_sock, $goodbye_message, 0); $param = $$."|$client_ip_addr|quit"; $param = MIME::Base64::encode($param); $param =~ s/\r\n/_/g; $param =~ s/\n/_/g; `/usr/local/apache/php/bin/php -f ./zpop.php $param > + /dev/null`; close($sock); kill("USR1", getppid()); exit; } else { $param = $$."|$client_ip_addr|".$received_string; $param = MIME::Base64::encode($param); $param =~ s/\r\n/_/g; $param =~ s/\n/_/g; print $new_sock `/usr/local/apache/php/bin/php -f ./zp +op.php $param 2>&1`; } } } else { my $client_sock = getpeername($new_sock); my($client_port,$client_addr) = sockaddr_in($client_sock); my $client_ip_addr = inet_ntoa($client_addr); $children_pid{"$pid"} = $client_ip_addr; $children_time{"$pid"} = time(); $n_connections++; } }



Config file (zpopd.config.pl):

our $listen_ip_address = "localhost";<br> our $listen_port = 110;<br> our $timeout = 300;<br> our $max_connections = 3;<br> our $global_max_connections = SOMAXCONN;<br> our $pid_dir = "./pid/zpopd";<br> our $parent_pid_filename = "parent.pid";<br> our $welcome_message = "POP3 Server Ready";<br> our $goodbye_message = "POP3 Server Signing Off";<br> our $timeout_message = "POP3 Server Signing Off (timeout)";<br>



I feel bad about just dumping all of the code and errors here without much else. I really took some time in attempting to determine the cause of these errors, but this is just beyond me.

Thanks for any help.

Readmore tags added by GrandFather

br tags removed from code by GrandFather

Replies are listed 'Best First'.
Re: Script Creates Zombies
by cdarke (Prior) on Jun 21, 2009 at 10:17 UTC
    Line 148 is part of a signal handler for TERM, so you should only get that when someone is killing the process. The error is probably caused because the kill has been raised at a point when $client_sock is invalid. You can never be sure when a signal occurs, so the results may be inconsistent.
    The TERM signal seems to be raised by the script if the accept times out.

    The way in which the code sends USR1 signals to the parent is convoluted, and I should say that I have not spent the time to fully understand it. Perhaps you should read the design document for this script ;-)

    A Zombie is created when a child is waiting for the parent to acknowledge the CHLD signal raised when the child is ending. This is usually cleared by the parent using a wait or waitpid (this is UNIX architecture, not specific to Perl). In your code, it looks like a USR1 signal has to be raised for the parent to do a wait(), and that is probably the issue here. Normally I would expect to see a wait or waitpid in the final 'else' block (the parent) after the fork.

    Incidently, if the fork should fail (usually that would be if the number of processes exceeds some limit) then it returns undef, which you are not testing for.

    Update: improved wording.