water has asked for the wisdom of the Perl Monks concerning the following question:
As part of a test suite, I need to test that two programs cannot run at the same time.
$rc = system("perl $rpla & perl $rplb & ");
ok($rc, 'double rpl dies with rc=' . $rc);
When run from a shell as
perl prog-rpla.pl & perl prog-rplb.pl &
rpblb dies as it starts before rpla finishes. However, when run as via system, the test above fails -- that is, rc=0, which means system did NOT fail as desired.
My question: what does
system("foo & bar&")
return? Which return value?
Thanks
water
And yes, this is a test suite, so I'm not concerned about taint and the one-arg form of system...
Re: multi-command sytem call
by tilly (Archbishop) on Aug 25, 2004 at 02:57 UTC
|
The system command returns the status of the command that you executed - in this case whether you successfully backgrounded the two processes.
Which you do.
What you need is to background the jobs, reap them, and then collect the return value when you reap them.
I'd do that by calling open twice, then calling waitpid twice and storing $?. (Yeah, it is a lot more complicated - but you are trying to keep straight a much more complicated situation.) | [reply] |
|
Many thanks, tilly.... Sadly, your advice is over my head at this point in my perl learning curve -- I'm not yet fluent in processes, children, pids, etc. Any pointers towards sample code to get me started?
| [reply] |
|
edan provided sample code at Re^3: multi-command sytem call. You can also get an overview at perlipc.
The theory, quickly, goes like this.
Every process on the system gets a Process IDentifier (aka pid), which is an integer. For all operations, the pid identifies the process. If you launch a process with open, you're told that pid. When it exits, your process gets a CHLD signal. If you've not been set up to automatically ignore CHLD signals, you can then wait or waitpid to "reap" the child. When you call wait or waitpid, Perl tells you the pid of the process that you reaped and then puts its exit status into $?. Between the time that a process attempts to exit and when its parent reaps it, that process is a zombie - dead but not gone until it manages to tell someone its fate.
Yeah, it sounds complex. But if you sit down and try to write down what needs to happen, it generally works out to be reasonably straightforward.
The reason why you need to be aware of all of this complexity is that you're trying to launch two processes at once, then find out what happened to them later. In particular you want to know that one refused to run because the other was running. For that you need to use the API for dealing with multiple subprocesses and keeping them straight.
| [reply] |
|
| [reply] [d/l] [select] |
|
my $pid1 = open( my $fh1, '|-');
if ( $pid1 == 0 )
{
print "child1 sez: $$ forked, now exec()ing false!!\n";
exec (qw/false/) or die "Can't exec false $!";
}
my $pid2 = open( my $fh2, '|-');
if ( $pid2 == 0 )
{
print "child2 sez: $$ forked, now exec()ing true!!\n";
exec (qw/true/) or die "Can't exec false $!";
}
sleep 1; # just so the output makes more sense
print "Hi, I'm the parent, and I'm going to wait for pid1=($pid1) and
+pid2=($pid2)!\n";
my $dead_pid;
$dead_pid = waitpid $pid1, 0;
print "parent sez: pid $dead_pid exited with status $?\n";
$dead_pid = waitpid $pid2, 0;
print "parent sez: pid $dead_pid exited with status $?\n";
Output is:
child1 sez: 19628 forked, now exec()ing false!!
child2 sez: 19629 forked, now exec()ing true!!
Hi, I'm the parent, and I'm going to wait for pid1=(19628) and pid2=(1
+9629)!
parent sez: pid 19628 exited with status 256
parent sez: pid 19629 exited with status 0
| [reply] [d/l] [select] |
Re: multi-command sytem call
by belg4mit (Prior) on Aug 25, 2004 at 05:00 UTC
|
What tilly and Zaxo have said is correct, you get the
return staus of a shell. You're calling perl "foo&", and
that's a shell syntax for running a process in the background.
Only in special cases does system run things directly from
perl and bypass the shell, this is not one of them.
That said, the problem at hand can be solved in a few ways.
One is the double open as tilly suggested. Another, more
complicated, is to use fork and exec yourself. A third would
be to use short-circuting in your system call, something like
system(q(fred & barney || true ));
Which means run fred in the background,
and then try to run barney (no need to background barney
and it wouldn't allow the trick anyhow). If barney exits
with 0 status echo runs and we have successful termination
indicated by true's return value of 0.
Use true, false, && and
|| as necessary to achieve the desired results.
UPDATE: You shouldn't even need the logic actually,
you can twiddle the return code as needed within perl
itslef. In short, loose the second ampersand :-P.
--
I'm not belgian but I play one on TV.
| [reply] [d/l] |
Re: multi-command sytem call
by etcshadow (Priest) on Aug 25, 2004 at 03:11 UTC
|
When you run a process in the background via the shell (with the &), the return value is always zero. From shell (bash) docs:
If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0.
What you can do, though, is do something with the return value and then check it later... maybe create a file:
# clean up if the temp files were already there
unlink $_ or die "unlink $_: $!\n" for grep {-e $_} "/tmp/a$$", "/tmp/
+b$$";
system("(perl $rpla && touch /tmp/a$$) & (perl $rplb && touch /tmp/b$$
+)");
$rc = -e "/tmp/a$$" && -e "/tmp/b$$";
# cleanup temp files
unlink $_ or die "unlink $_: $!\n" for grep {-e $_} "/tmp/a$$", "/tmp/
+b$$";
There are other ways to do this, too... depending on whether you actually *wanted* the output of $rpla and $rplb to go to your terminal, you could do this without temp files at all:
my $output = `(perl $rpla >/dev/null 2>&1 && echo a) & (perl $rplb >/d
+ev/null 2>&1 && echo b)`;
my $success = $output =~ /a/ && $output =~ /b/;
Anyway, these all rely on the ability of the shell to fork off not just a single command, but a whole command list expression (and then you chain together the command whose status you want to check with another command that is conditional on the status of the first, and has side-effect that you can verify later).
------------
:Wq
Not an editor command: Wq
| [reply] [d/l] [select] |
|
When you run a process in the background via the shell (with the &), the return value is always zero. Not so! system, exec, and fork can all fail and depending on the function, return a non-zero value if the system cannot fork at the time (say, if there is a runaway process running that has forked too many times). Or actually, on failure fork returns undef, exec returns false, and system returns whatever the secondary process returned. Normal programs return 0 on success but are expected to return non-zero values on failure. So system is perfectly capable of returning non-zero values. When that happens, something wrong has happened in the program that system was running. The return code of the program is in $? >> 8 so check that when it fails.
| [reply] [d/l] |
|
Well, I was talking about the return code in the shell (since this was really more of a shell question than a perl question) and quoting the shell documentation. The OP was talking about the return values of the backgrounded processes, which are ignored in terms of computing the exit code of the shell process, and that's all I meant. I probably should have been more precise, though.
------------
:Wq
Not an editor command: Wq
| [reply] [d/l] |
|
#!/usr/bin/perl -w
use strict;
my $var = 1;
if ($var == 1) {
print "Hello World!\n";
}else{
exit(0);
}
In this scenario if the program fails to do what it is suppose to do it will exit with a status of 0. Now this script did not fail to run but if you changed the value of $var it would failed to do what it was suppose to do.
exec() does not return the exit status of the child because , as already explianed, it does not wait for this status report. Instead it returns the status of the forking process itself. If it was able to fork the process into the background it returns a successfull status, if it does not it returns an error status.
| [reply] [d/l] [select] |
|
exec does not fork, it overwrites the current process.
It doesn't return, unless something goes wrong, because
there's nothing to return to, nor record that it should
even try.
--
I'm not belgian but I play one on TV.
| [reply] |
|
Re: multi-command sytem call
by Zaxo (Archbishop) on Aug 25, 2004 at 03:00 UTC
|
Neither. It returns the exit status of the shell instance which perl starts to interpret the command line. It is parent to both the backgrounded processes.
| [reply] |
|
|