How do I end processes that time out?

Discussion in 'Perl Misc' started by J. Romano, Jul 16, 2004.

  1. J. Romano

    J. Romano Guest

    Dear Perl community,

    I've written some Perl code that runs the Unix "finger" command
    inside of backticks (in order to capture its output). Occasionally
    the finger command will hang for about thirty seconds or so (in which
    case it almost always fails).

    I want my Perl code to only wait three seconds for the finger
    command to finish. After that, if the finger command has not
    finished, I want to just discard the output and move on. Therefore, I
    made use of the alarm() function:


    $SIG{CHLD} = 'IGNORE'; # avoid creating zombie processes

    # This next part (the eval/die blocks) is
    # right out of "perldoc -f alarm":
    eval {
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm 3; # wait a maximum of three seconds
    $text = `finger some_user\@some_host`;
    alarm 0;
    };
    if ($@) {
    die unless $@ eq "alarm\n"; # propagate unexpected errors
    # Timed out:
    print "Timed out!\n";
    }


    Now, this is difficult to test because the finger command almost
    always works. However, in the rare case where it doesn't work, my
    script prints "Timed out!" (as it should) and continues on until it
    finishes.

    The problem is that the timed-out finger command is apparently
    still running even after my Perl script that called it finishes
    running, because a short time later (like thirty seconds or a minute
    later), an error message pops up on my terminal saying something like
    "Unable to connect to host ...".

    Now, I explicitly set $SIG{ENV} to 'IGNORE' so that this wouldn't
    happen, but now I think that setting $SIG{ENV} to 'IGNORE' only
    terminates (that is, reaps) child processes that have finished BEFORE
    the Perl script has finished. (If I'm wrong in saying this, please
    correct me.) But if the child process created by the backtick
    operator finishes AFTER my Perl script finishes, it stays around well
    longer than needed.

    To summarize, my question is: If I use the alarm() function to
    break out of a command that I started with the backtick operator, how
    do I force that child process to exit so that it doesn't outlive my
    Perl script?

    In case anyone is interested, here is the output of "perl -v":

    This is perl, v5.6.1 built for alpha-netbsd

    Thanks in advance for any help,

    J.
     
    J. Romano, Jul 16, 2004
    #1
    1. Advertising

  2. J. Romano

    Guest

    (J. Romano) wrote:
    > Dear Perl community,
    >
    > I've written some Perl code that runs the Unix "finger" command
    > inside of backticks (in order to capture its output). Occasionally
    > the finger command will hang for about thirty seconds or so (in which
    > case it almost always fails).
    >
    > I want my Perl code to only wait three seconds for the finger
    > command to finish. After that, if the finger command has not
    > finished, I want to just discard the output and move on. Therefore, I
    > made use of the alarm() function:
    >
    > $SIG{CHLD} = 'IGNORE'; # avoid creating zombie processes
    >
    > # This next part (the eval/die blocks) is
    > # right out of "perldoc -f alarm":
    > eval {
    > local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    > alarm 3; # wait a maximum of three seconds
    > $text = `finger some_user\@some_host`;
    > alarm 0;
    > };
    > if ($@) {
    > die unless $@ eq "alarm\n"; # propagate unexpected errors
    > # Timed out:
    > print "Timed out!\n";
    > }
    >
    > Now, this is difficult to test because the finger command almost
    > always works. However, in the rare case where it doesn't work, my
    > script prints "Timed out!" (as it should) and continues on until it
    > finishes.


    You could replace "finger" with "sleep", just for testing purposes.

    >
    > The problem is that the timed-out finger command is apparently
    > still running even after my Perl script that called it finishes
    > running, because a short time later (like thirty seconds or a minute
    > later), an error message pops up on my terminal saying something like
    > "Unable to connect to host ...".
    >
    > Now, I explicitly set $SIG{ENV} to 'IGNORE' so that this wouldn't
    > happen, but now I think that setting $SIG{ENV} to 'IGNORE' only
    > terminates (that is, reaps) child processes that have finished BEFORE
    > the Perl script has finished.


    I think you meant CHLD, not ENV.

    "terminate" ne "reap". reaping a child just means you wait for it to die
    naturally, and then you bury it. What you want to do is kill the child
    before it's time. $SIG{CHLD} doesn't do that.

    > To summarize, my question is: If I use the alarm() function to
    > break out of a command that I started with the backtick operator, how
    > do I force that child process to exit so that it doesn't outlive my
    > Perl script?


    Well, on at least some shells of at least some OSes, the
    parent can end by killing itself and all of it's children, instead of
    exiting normally:

    kill -15, $$;

    However, it would be better to kill them off as soon as they alarmed,
    rather than waiting for the end of the program. There are about 27,000
    modules on CPAN relating to this problem, if you find one you like, please
    let me know. I got tired of wading through them.

    Xho

    --
    -------------------- http://NewsReader.Com/ --------------------
    Usenet Newsgroup Service $9.95/Month 30GB
     
    , Jul 16, 2004
    #2
    1. Advertising

  3. J. Romano

    Ben Morrow Guest

    Quoth (J. Romano):
    > Dear Perl community,
    >
    > I've written some Perl code that runs the Unix "finger" command
    > inside of backticks (in order to capture its output). Occasionally
    > the finger command will hang for about thirty seconds or so (in which
    > case it almost always fails).
    >
    > I want my Perl code to only wait three seconds for the finger
    > command to finish. After that, if the finger command has not
    > finished, I want to just discard the output and move on. Therefore, I
    > made use of the alarm() function:
    >
    >
    > $SIG{CHLD} = 'IGNORE'; # avoid creating zombie processes
    >
    > # This next part (the eval/die blocks) is
    > # right out of "perldoc -f alarm":
    > eval {
    > local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    > alarm 3; # wait a maximum of three seconds
    > $text = `finger some_user\@some_host`;
    > alarm 0;
    > };
    > if ($@) {
    > die unless $@ eq "alarm\n"; # propagate unexpected errors
    > # Timed out:
    > print "Timed out!\n";
    > }
    >
    > The problem is that the timed-out finger command is apparently
    > still running even after my Perl script that called it finishes
    > running, because a short time later (like thirty seconds or a minute
    > later), an error message pops up on my terminal saying something like
    > "Unable to connect to host ...".


    You need to fork and exec yourself, and save the pid of the finger
    command. Then kill the finger process with SIGTERM if the alarm goes
    off. See perlipc for how to emulate backticks with fork/exec.

    Ben

    --
    'Deserve [death]? I daresay he did. Many live that deserve death. And some die
    that deserve life. Can you give it to them? Then do not be too eager to deal
    out death in judgement. For even the very wise cannot see all ends.'
     
    Ben Morrow, Jul 17, 2004
    #3
  4. J. Romano

    J. Romano Guest

    Dear Perl community,

    Thanks for all your help. Using your suggestions I was able to
    make a toy program that isolated my problem. I'll post the toy
    program, but let me point out a few things first:

    * Xho was correct in saying that I meant to say "$SIG{CHLD}"
    and not "$SIG{ENV}".

    * Working from Xho's suggestion, I wrote a test program
    that used "sleep" instead of finger. I just set the
    sleep-time to a value greater than the timeout value
    to see if my code works.

    * Fiftyvolts' and Ben Morrow's suggestion to open a new
    process from open() using "-|" (explained in "perldoc -f open")
    worked fairly well. Since the code handled by the child
    process was fairly short, I was able to put it all inside
    the open() statement.

    * I encountered only one problem:
    The toy program (i.e., test program) I'm posting below worked
    perfectly on a system with the following "perl -v" output:

    This is perl, v5.6.1 built for i386-linux

    but it didn't work on a different system with the following
    "perl -v" output:

    This is perl, v5.6.1 built for alpha-netbsd

    I finally got it to work by not killing the child's $pid
    (process id), but $pid + 1. For some reason, the pid
    assigned to the child process by the OS was one more than
    what was returned from the open() statement. Would anyone
    know why?

    Well, here is the toy program I used to test this with. You are
    asked for the run time of the child process and a time-out value. If
    the run-time value (for example, 10) is greater than the time-out
    value (for example, 3), the process should time out, and the remaining
    child process SHOULD then be killed. But if the time-out value is
    greater than the run-time value, the process shouldn't time out, and
    the child process should not be killed.

    If the child process isn't killed, you should see the line:

    *** If you can read this, the child was not killed.

    printed to STDERR. You will naturally see this if the time-out value
    is greater than the run-time value, but if the run-time value is
    greater than the time-out value, then you should only see this if the
    child process was not successfully killed.

    Here is the toy/test program I used:


    #!/usr/bin/perl -w
    # File: childTest.pl

    use strict;

    my $sleepTime;
    my $timeout;
    my @text; # holds the text of the child process

    print "Enter the run time for the child process: ";
    chomp($sleepTime = <STDIN>);
    print "Enter the timeout value: ";
    chomp($timeout = <STDIN>);
    print "\n";

    my $tinyProgram = <<"END_OF_TINY_PROGRAM";
    print "Starting...\n";
    sleep $sleepTime;
    print "Finished!\n";
    warn "*** If you can read this, the child was not killed.\n";
    END_OF_TINY_PROGRAM

    my $pid = open(CHILD, '-|', "perl -e '$tinyProgram'")
    or die "Could not open child process";
    print "\$pid = $pid\n";

    eval {
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm $timeout;
    @text = <CHILD>;
    alarm 0;
    };
    if ($@) {
    die unless $@ eq "alarm\n"; # propagate unexpected errors
    # timed out
    print "Timed out!\n";
    print "Killing process...\n";
    kill 'KILL', $pid;
    }
    else {
    print "Successful read!\n";
    }
    close(CHILD);

    print "\nText received:\n", @text;

    __END__


    If anyone can explain why the alpha-netbsd OS consistently assigns
    the child a process id that is one greater than what is returned from
    the open() statement, please share it with me. Under alpha-netbsd I
    can make this code work if I replace the line:

    kill 'KILL', $pid;

    with:

    kill 'KILL', $pid + 1;


    Anyway, thanks for all the help I received.

    -- Jean-Luc
     
    J. Romano, Jul 19, 2004
    #4
  5. On 2004-07-19, J. Romano <> wrote:
    >
    > I finally got it to work by not killing the child's $pid
    > (process id), but $pid + 1. For some reason, the pid
    > assigned to the child process by the OS was one more than
    > what was returned from the open() statement. Would anyone
    > know why?


    Presumably because perl hands the command to a shell for parsing. The
    $pid you get is for the shell, not for the actual command executed.
    For some reason, killing the shell has different effects on different
    platforms.

    You can avoid this by using the list form of open(). Simply replace

    "perl -e '$tinyProgram'"

    in your example code with

    "perl", "-e", $tinyProgram

    and everything should work as expected.

    (The reason why killing $pid+1 appears to work is simply that process
    id numbers are often assigned sequentially, so the pid of the actual
    command often just happens to be one greater than that of the shell.)

    --
    Ilmari Karonen
    If replying by e-mail, please replace ".invalid" with ".net" in address.
     
    Ilmari Karonen, Jul 19, 2004
    #5
  6. J. Romano

    J. Romano Guest

    > On 2004-07-19, J. Romano <> wrote:
    > >
    > > I finally got it to work by not killing the child's $pid
    > > (process id), but $pid + 1. For some reason, the pid
    > > assigned to the child process by the OS was one more than
    > > what was returned from the open() statement. Would anyone
    > > know why?


    Ilmari Karonen <> replied in message
    news:<>...
    >
    > Presumably because perl hands the command to a shell for parsing. The
    > $pid you get is for the shell, not for the actual command executed.
    > For some reason, killing the shell has different effects on different
    > platforms.
    >
    > (The reason why killing $pid+1 appears to work is simply that process
    > id numbers are often assigned sequentially, so the pid of the actual
    > command often just happens to be one greater than that of the shell.)


    Your explanation makes sense, Ilmari. So I would think that
    killing $pid+1 will usually work in my case, but won't work 100% of
    the time (and therefore should not be relied on).

    > You can avoid this by using the list form of open(). Simply replace
    >
    > "perl -e '$tinyProgram'"
    >
    > in your example code with
    >
    > "perl", "-e", $tinyProgram
    >
    > and everything should work as expected.


    Oddly enough, that doesn't work for me. When I use the line:

    my $pid = open(CHILD, '-|', 'perl', '-e', $tinyProgram);

    I get the following fatal error message at run-time:

    Can't use an undefined value as filehandle reference ...

    This happens even with the following line:

    my $pid = open(CHILD, '-|', 'ls', '-l');

    but doesn't happen when I use the one-argument form of "ls", like
    this:

    my $pid = open(CHILD, '-|', 'ls');

    I can't figure out why it's giving me an error message, because
    according to "perldoc -f open", the following four lines should be
    more or less equivalent:

    open(FOO, "cat -n '$file'|");
    open(FOO, '-|', "cat -n '$file'");
    open(FOO, '-|') || exec 'cat', '-n', $file;
    open(FOO, '-|', "cat", '-n', $file);

    so if the line:

    my $pid = open(CHILD, '-|', "perl -e' $tinyProgram");

    works, likewise the line:

    my $pid = open(CHILD, '-|', 'perl', '-e', $tinyProgram);

    should also work, just as you said it would. But it doesn't. I keep
    getting the fatal run-time error message that I can't use an undefined
    value as a filehandle reference when I use the list form of open().
    Am I missing something?

    (Just so you know, this same error happens on two separate Unix
    machines. Their "perl -v" output is:

    This is perl, v5.6.1 built for alpha-netbsd
    This is perl, v5.6.1 built for i386-linux
    )

    I did get around this error, however. I used the third form of the
    open() statements listed above (the one with the exec() call).
    Therefore, I replaced the line:

    my $pid = open(CHILD, '-|', "perl -e' $tinyProgram");

    with:

    my $pid = open(CHILD, '-|') || exec 'perl', '-e', $tinyProgram;

    and everything worked correctly (meaning that I also received the
    correct $pid used to kill the process).

    So in the end, I solved my problem of killing child processes that
    time out, but now I don't understand why the list form of open() keeps
    giving me a fatal error message. If anyone knows why, please let me
    know.

    Anyway, thanks for all your help. I appreaciate it.

    -- Jean-Luc
     
    J. Romano, Jul 23, 2004
    #6
  7. On 2004-07-23, J. Romano <> wrote:
    >
    > Oddly enough, that doesn't work for me. When I use the line:
    >
    > my $pid = open(CHILD, '-|', 'perl', '-e', $tinyProgram);
    >
    > I get the following fatal error message at run-time:
    >
    > Can't use an undefined value as filehandle reference ...


    That appears to be a bug in perl 5.6.x. For details, see:

    http://groups.google.com/groups?threadm=

    Your open() || exec() workaround is just fine, though you might want
    to check that $pid is false _but defined_ before calling exec().

    --
    Ilmari Karonen
    If replying by e-mail, please replace ".invalid" with ".net" in address.
     
    Ilmari Karonen, Jul 23, 2004
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. vee_kay
    Replies:
    7
    Views:
    497
    DHOLLINGSWORTH2
    Mar 12, 2005
  2. thomson
    Replies:
    1
    Views:
    362
    Karl Seguin [MVP]
    Jul 4, 2006
  3. Jeff Rodriguez
    Replies:
    23
    Views:
    1,157
    David Schwartz
    Dec 9, 2003
  4. flamesrock
    Replies:
    8
    Views:
    502
    Hendrik van Rooyen
    Nov 24, 2006
  5. Marc Heiler
    Replies:
    1
    Views:
    182
    Robert Klemme
    May 24, 2009
Loading...

Share This Page