user3395103
user3395103

Reputation: 141

How to wait for running process to complete in perl when running process is not child process?

I am going through a Perl script which is using waitpid($pid, 0) to wait for current process to complete. But print statement written right after this waitpid is printing it before the process gets complete.

I want to know why waitpid is not waiting for process to complete first.

Also, control of running process is under different module, not part of this perl script. Only accessible is pid and name of the process. I can't change anything in the module which invokes the process.

Upvotes: 2

Views: 6570

Answers (2)

zdim
zdim

Reputation: 66873

Note   A simple-minded one-liner with kill 0, $pid is at end, commented.


We need to detect completion of an external program, which had not been started by this script. The question asks about using waitpid. To copy my early comment:

You cannot. You can only wait on a child process. See perldoc wait (or waitpid, it's the same), first sentence.

The wait and waitpid wait for signals delivered to the script regarding the fate of its child(ren). There is no reason for the script to receive such signals about processes that it did not start.


We know the process's id and its name. Its PID can be used to poll for whether it is running. Using pid on its own is not completely reliable since in between our checks the process can finish and a random new one be assigned the same pid. We can use the program's name to strengthen this.

On a Linux system information about a process can be obtained by utilizing (the many) ps options. Either of these returns the program's full invocation

ps --no-headers -o cmd PID
ps --no-headers -p PID -o cmd

The returned string may start with the interpreter's path (for a Perl script, for example), followed by the program's full name. The version ps -p PID -o comm= returns only the program's name, but I find that it may break that word on a hyphen (if there), resulting in an incomplete name. This may need tweaking on some systems, please consult your man ps. If there is no process with given PID we get nothing back.

Then we can check for PID and if found check whether the name for that PID matches the program. The program's name is known and we could just hardcode that. However, it is still obtained by the script as it starts, using the above ps command, to avoid ambiguities. (Then it is also in the same format for later comparison.) This itself is checked against the known name since there is no guarantee that the PID at the time of script execution is indeed for the expected program.

use warnings;
use strict;

# For testing. Retrieve your PID as appropriate for real use    
my $ext_pid = $ARGV[0] || $$;

my $cmd_get_name = "ps --no-headers -o cmd $ext_pid";

# For testing.  Replace 'sleep' by your program name for real use
my $known_prog_name = 'sleep';

# Get the name of the program with PID
my $prog_name = qx($cmd_get_name);

# Test against the known name, exit if there is a mismatch
if ($prog_name !~ $known_prog_name) {
    warn "Mismatch between:\n$prog_name\n$known_prog_name -- $!";
    exit;
}   

my $name;
while ( $name = qx($cmd_get_name) and $name =~ /$prog_name/ )
{
    print "Sleeping 1 sec ... \n";
    sleep 1;
}   
# regex above may need slight adjustment, depending on format of ps return

The command output received via qx() above (backtick operator) contains a newline. If that proves to be a problem in what the script does it can be chomp-ed, which would require a slight adjustment. The remaining loophole is that that very program may have finished and was restarted between the checks, and with the same PID.

This would be tested by running in a shell

sleep 30 &
script.pl `ps aux | egrep '[s]leep'`

The egrep is grep -E. The output from `ps ...` contains multiple words. These are passed as command line arguments to our script, which uses the first one as the PID. If there is a problem with it run the ps filtering first and then manually enter the PID as script's input argument. The sleep of 30 seconds above is to give enough time to do all this on the command line.

The code can be simplified by matching $name with a hard-coded $prog_name, if the program name is unique enough and it will not change. The hard-coded name is used above, but for a check and it generates a warning if mismatched. (If we rely on it only hardcoded we cannot issue warnings if it mismatches, since that is then a part of code's operation.)


If the process is owned by the same user as the script one can use kill 0, $pid, as

while ( kill 0, $ext_pid ) { sleep 1 }

Then you'd either have to make another call to check the name or be content with the (small) possibility of an error in what actual process the $pid represents.

The module Proc::ProcessTable can be used for much of this

Upvotes: 3

Sebastian
Sebastian

Reputation: 2550

The waitpid documentation states:

Waits for a particular child process to terminate and returns the pid of the deceased process, or -1 if there is no such child process.

Quick test:

my $pid = open my $fh,"-|","sleep 3";
print waitpid(28779,0); # Some other process
print waitpid($pid,0);

28779 is another process currently running (took a random one from ps axu). Output:

-1
4088

You can't use waitpid to wait for a process which are not a child of your current process.

The kill command could check if a PID is currently running:

print kill(0,28779);

Output:

1

You'd still need to poll (loop with sleep) for the pid to disappear. Also remember that the monitored process might exit and a new one might reuse the same PID before your next check (unlikely, but possible).

Upvotes: 3

Related Questions