Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

How to deal with a forked proces that is waiting for user input?

by gepapa (Acolyte)
on Oct 21, 2008 at 18:34 UTC ( [id://718558]=perlquestion: print w/replies, xml ) Need Help??

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

I am attempting to deal with the following scenario, and so far my attempts have been unsuccessful.

I want to run a variety of commands, they all should not require user input, but there is a chance one might. Because of this I want to implement a timeout for these calls, so that we don't wait indefinitely.

The most logical way to do this that I see is to fork a process, have that process run the command and monitor the process, if it hasn't returned in X amount of time kill the $pid. But I am running up against a few issues with this, the largest of which is dealing with the sub-process. I am using backticks to actually run the command because I need the output, when I go about killing the forked process, it does not kill the "backticked" sub-proces.

I noticed someone else posted something somewhat similar to what I am seeing, but the solution for that did not work for me.

I have tried the following so far to no avail:

Option 1:

sub _timedFork1 { my ($command, $time) = @_; my @resp1; my $pid; my @pids; local $SIG{ALRM} = sub {kill -15, $pid or die "kill: $!"; die "TIMEOUT!"; }; if ($pid = fork()) { push(@pids, $pid); alarm $time; waitpid($pid, 0); } elsif (defined $pid) { @resp1 = `$command`; return @resp1; exit; } return ($pid, @resp1) }

OPTION 2:

sub _timedFork2 { my ($command, $time) = @_; my $pid; my @resp2; local $SIG{ALRM} = sub {kill 15, $pid or die "kill: $!"; die 'Timeout!'; }; eval { $pid = fork(); unless (defined $pid) { die "Fork failed: $!"; } unless ($pid) { @resp2 = `$command`; } alarm $time; waitpid $pid => 0; }; if ($@ and $@ !~ m/Timeout!/i) { die $@; } return @resp2; }

OPTION 3:

sub _forkFHCommand { my ($command, $time) = @_; my @resp3; my $pid = open COMMAND, "-|", $command or die $!; my $endtime = time() + $time; my $line; while($line = <COMMAND>) { push(@resp3, $line); if (time > $endtime) { kill 15, $pid; last; } } close COMMAND; return (0, @resp3); }

As a note in the $SIG{ALRM}, I tried kill 9, kill -9 and kill 15. None of them worked for me.

I also attempted to do a simple fork function and have the main script sleep for a set amount of time, and then attempt to kill the $pid, this also didn't work out for me.

Essentially the perfect solution would enable me to get back whatever has been returned from the command prior to it being killed. Also, it obviously needs to be able to kill the sub-process.

I am completely open to using threads, but so far my attempts at that have resulted in hangs

So just to summarize the main issue I am having is with processes waiting for input. The _forkFHCommand solution works fine for commands that take a long time but don't require user input (i.e. do a ping -n 100 192.168.1.2 with a timeout of 5 seconds). The other solutions show the same issue of a leftover sub-process regardless if it that sub-process is waiting for input or not.

Any help would be greatly appreciated. Thanks.

Replies are listed 'Best First'.
Re: How to deal with a forked proces that is waiting for user input?
by BrowserUk (Patriarch) on Oct 21, 2008 at 18:51 UTC

      Hi Browser, thanks for the link. I tried this but Perl is crashing when I run it.

      Here is my code:

      my $command = 'dir'; my (@results) = _timedCommand($command, 30); if (@results) { print "Good:\n " . join(' ', @results); } else { print "Bad\n"; } sub _timedCommand { use threads; use threads::shared; my ($command, $time) = @_; my @results :shared; my $pid :shared; async { $pid = open my $fh, "$command |" or die "$+, $^E"; @results = <$fh>; $fh->close(); }->detach; kill 0, $pid while sleep 1 and $time--; kill 3, $pid and return if $time; return @results; }

      As you can see I tried testing it with a simple dir command which shouldn't cause any issues. When I put it in the debugger, it gets to the return statement, and @results is fine, once I execute the return line it simply kicks out of the debugger.

      Any ideas why it would do that?

      I made some changes to your basic code structure and came up with something that gets me almost there. Could you take a look and let me know if you see any glaring issues.

      Also I assumed the way I have it coded would enable me to get whatever was "printed" prior to killing the thread but this does not seem to be the case, any ideas on what I am doing wrong?

      UPDATED:

      my $command = 'diskpart'; my (@results) = _timedCommand($command, 30); my $last = pop(@results); if ($last =~ m/^!!!TIMEOUT!!!/i) { print "Bad: \n" . join(' ', @results); } else { push(@results, $last); print "Good:\n " . join(' ', @results); } sub _timedCommand { use threads; use threads::shared; my ($command, $time) = @_; my @results :shared; my $pid :shared; my $thr = async { $pid = open my $fh, "$command |" or die "$!"; @results = <$fh>; $fh->close(); }; while ($thr->is_running() and $time > 0) { sleep(1); $time--; } if ($thr->is_joinable()) { return @results; } else { $thr->detach; kill 3, $pid; push(@results, '!!!TIMEOUT!!!'); print ("\n @results \n"); return @results; } }

      Thanks

        Could you take a look and let me know if you see any glaring issues.

        This is iffy: or die "$+, $^E";.

        You probably mean $! not $+, and $^E probably won't give you anything unless you are running under windows (or maybe OS/2?).

        And you are testing if the thread is joinable, but never joining (or detaching) it. Which means it will hang around until the program ends. At which point you would probably get a warning message.

        Also I assumed the way I have it coded would enable me to get whatever was "printed" prior to killing the thread but this does not seem to be the case, any ideas on what I am doing wrong?

        I'm not really sure I understand what you mean by that? Exactly where in your code do you want to access it?

        You should certainly be able to access the contents of the shared array @result from outside the thread whilst the command is still running. But probably not the way you have it coded currently:

        @results = <$fh>;

        Perl probably locks the entire array until that statement completes, which won't be until the command completes. If you changed that to push @results, $_ while <$fh>; you might have more success, but remember that you would have to employ locking to prevent conflicts.

        Perhaps a better way would be to use a Thread::Queue which is basically just a shared array and a couple of simple methods (or was until they f***ed with it recently!), but it does take care of the locking for you which is a great convenience, as you know it is done right.

        However, since timedCommand() can't return anything until you've cleaned up the process and thread, it is hard to see what benefit you would get from that?


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: How to deal with a forked proces that is waiting for user input?
by eighty-one (Curate) on Oct 21, 2008 at 19:29 UTC

    Since this is an issue, I assume that the program will be run without a person sitting there watching it, correct?

    Also, I'm guessing that the forked processes will sometimes be run by a human directly and may then require user input, but sometimes will be run by your program, and for whatever reason will be able to function properly without receiving any input in that case.

    If my assumption and my guess are correct, then why not add a command line argument to the program that gets forked? Wrap the sections that get user input in an 'if' and just skip over that section if the command line argument has been set. That way you can fork it without having it hang by passing the correct argument, and it will still work as usual if invoked directly from the command line.

    It's not particularly fancy or elegant, but it's simple and should work unless there's constraints I'm not aware of

      This will always be run via automation, so no user input.
        Ohhh, I get it now. For some reason I thought that by 'processes' you were referring to stuff you had developed and were calling; upon re-reading your node I see that I was mistaken.
Re: How to deal with a forked proces that is waiting for user input?
by tmaly (Monk) on Oct 22, 2008 at 12:47 UTC
    I have used IPC::Run to call other programs and monitor their output and provide input to them with good success.
    Another option that I have used is POE with POE::Wheel::Run.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://718558]
Approved by SankoR
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (6)
As of 2024-04-23 14:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found