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

Dear all,

I have a daemon awaiting xml-rpc requests at a port:

#!/usr/bin/env perl use XMLRPC::Transport::HTTP; use XML::Xerces; use XML::Xerces::DOMParse; use Carp; use Fcntl ":flock"; my $daemon = XMLRPC::Transport::HTTP::Daemon -> new (LocalPort => 8081, Reuse => 1) -> dispatch_to('Monster') ; print "Contact to XMLRPC server at ", $daemon->url, "\n"; $daemon->handle; sub go{ shift if UNIVERSAL::isa($_[0] => __PACKAGE__); my $CP = shift; $SIG{CHLD} = 'IGNORE'; ...various faffing about with $CP.... FORK: { $child = fork; if (defined $child) { if($child == 0){ my $exec = "/usr/local/bin/monster -i$id $filePath$file +&"; qx/$exec/; } }elsif ($! == EAGAIN) { sleep 5; redo FORK; }else { die "Can't fork: $!\n"; } } return 0; }

To describe my problem, once i'm running this daemon, and contact it from an applet, it creates two copies (hence the fork) but the second copy, even after 'monster' has finished, never dies, and I want it to die, i dont want any zombies.

Also, I get this error message: "Can't ignore signal CHLD, forcing to default."

Also i dont want to have to wait for process to finish, and for any number of requests to be made at any time, however, once i try to send another request, the webpage hangs, and the daemon dies of a broken pipe....

I was wondering if anyone has any ideas? Im pretty bad at interactions outside perl, cant get my head around it...

Thanks

Sam Seaver

Replies are listed 'Best First'.
Re: Reaping Zombies (dont wanna wait)
by DrManhattan (Chaplain) on Jun 09, 2003 at 18:03 UTC
    This looks fishy:
    my $exec = "/usr/local/bin/monster -i$id $filePath$file &"; qx/$exec/;

    Calling exec in scalar context like this, you're feeding what appears to be user input to /bin/sh. If an external user controls the value of $id, $filePath, or $file, you'll get owned because the shell interprets metacharacters. E.g. if $id is set to `rm -rf /`, sh will execute it.

    Also, I'm not sure if this is related, but there's no need for the '&' at the end of the command. You've already forked a child, so you shouldn't need to fork again. Try this:

    exec("/usr/local/bin/monster", "-i", $id, "$filePath$file");
    Calling exec() in array context executes the program directly rather than feeding it to the system shell, so metacharacters won't be a problem.

    -Matt

      DrManhattan, I agree with the sentiments in your post. I'm just providing some careful feedback on the jargon.

      "Calling exec in scalar context like this," is wrong for two reasons. One, the poster is not calling exec(), the poster is using the qx// operator which is identical to the backtick operator. Two, the calling context refers to how the results are to be collected, not what arguments or operands are given, so this calling context is void.

      As an aside, there's no reason to use backticks in void context; use system() or exec() instead. Backticks collect the output of a subprocess, and if you're in void context, you're collecting all that junk for nothing.

      "Calling exec() in array context..." is also wrong for the definition of context. Phrasing it as "Calling exec() with a list..." is more proper. The documentation refers to this as the exec LIST form of the function, as opposed to the exec EXPR form.

      Hope this helps clarify an otherwise good point on taint-validation and its security risk when not considered.

      --
      [ e d @ h a l l e y . c c ]

Re: Reaping Zombies (dont wanna wait)
by bobn (Chaplain) on Jun 09, 2003 at 17:58 UTC
    Apprently your system doewsn't support $SIG{CHLD} = 'IGNORE'

    The parent needs to do a wait() or a waitpid() on the child. There are non-bloking ways to do this, I think.

    perldoc perlipc gives information on this.


    --Bob Niederman, http://bob-n.com
Re: Reaping Zombies (dont wanna wait)
by sgifford (Prior) on Jun 09, 2003 at 21:07 UTC
    What you probably want to do is set up a signal handler for $SIG{CHLD} which uses wait() to clean up the child. Since it's in the CHLD signal handler, it will only get called when a process has already finished, and so it won't really have to wait. Here's some sample code:
    #!/usr/bin/perl $SIG{CHLD}=sub { wait(); print "Child exited\n";}; $| = 1; while(1) { if ((my $f = fork()) == 0) { # Child sleep(1); exit(0); } else { print "Started child $f\n"; } sleep(2); }
Re: Reaping Zombies (dont wanna wait)
by pemungkah (Priest) on Jun 11, 2003 at 17:12 UTC
    Let's do this the easy way. There will be a wait() in the code below, but it's for a process that's already exited.
    unless (fork) { # this is the child unless (fork) { # this is the grandchild # Do your request processing here ... my $exec = "/usr/local/bin/monster -i$id $filePath$file &"; qx/$exec/; exit 0; } # end grandchild # Child process just exits. exit 0; } # Parent reaps quick-exiting child. wait;
    The grandchild has no parent (child exits immediately), so it gets switched over to be a subprocess of init by the scheduler.

    The child exits immediately, so it doesn't need to wait for its child.

    The parent waits for the child, so the zombie child is cleaned up right away.

    You can add all the redo logic to this relatively easily; I've left it out so as to not obscure the simplicity of the mechanism. The mainline code does wait, but since the code it's waiting on runs extremely fast, it works more like relinquishing control to the child just long enough for it to exit.

    As a funny aside, this is from the Perl 4 Programming Perl.

      Dear All,

      Thanks for your input, understanding forks better, I realised that since I didnt want control of the child, i used 'exec()':

      FORK: { $child = fork; if (defined $child) { if($child == 0){ exec("/usr/local/bin/monster", "-i$id", "$filePath$file") or die "couldn't exec monster: $!"; } }elsif ($! == EAGAIN) { sleep 5; redo FORK; }else { die "Can't fork: $!\n"; } }

      However, now the child refuses to exit, which is strange. It wont even 'die' and print the error message if any.

      I tried this very same thing with pemungkah's suggestion, and again it happened, the child won't exit.

      Repeated requests creates new children, but they are all hanging there...

      Anyone?

      Sam

        woops, forgot to sign in. This 'A.M.' is actually seaver.
        Try isolating the "must fork" code; trying to get the redo sorted out was driving me nuts:
        sub insistent_fork { my $pid = undef; FORK: { if (defined ($pid = fork())) { return $pid; } elsif ($! =~ /No more process/) { sleep 5; redo FORK; } else { die "Can't fork: $!\n"; } } }
        You can pair this with the original code I posted, substituting insistent_fork for fork. (Works on OS X.)
      Has this ever been placed into a CPAN module? If so, which one? If not, which one should it be patched into (as an option?)?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      I shouldn't have to say this, but any code, unless otherwise stated, is untested