Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

multi-command sytem call

by water (Deacon)
on Aug 25, 2004 at 02:40 UTC ( [id://385562]=perlquestion: print w/replies, xml ) Need Help??

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...

Replies are listed 'Best First'.
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.)

      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?
        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.

      What would you call waitpid on? How would you find the process id of the child (perl prog-rpla.pl or perl prog-rpla.pl) if you use open(PROC1, "|perl prog-rpla.pl &") as I think you are indicating water should do?

        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
        --
        edan

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.

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
      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.

        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
      The main difference between system() and exec() is that exec() forks the child and does not wait for a return, meaning it does not wait until the child process does it's thing and then continue. The system() command does just that, When you use system() the call waits for the child process to do it's thing and then reports the exit status of the child. If the child fails it reports whatever failed status the child exited with.

      Hell it could even be a status of zero. You could explicitly assign an exit(0) to an error exit if you so choose. Take the following code fore example:

      #!/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.


      www.perlskripts.com
        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.

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.

    After Compline,
    Zaxo

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (3)
As of 2024-04-24 03:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found