1

The following perl code was running through a Python script.

my $pid = fork;
my @pids;
if ($pid) { 
    push(@pids, $pid);
    $return_code = system($command);
}
foreach my $pid (@pids) {
    waitpid($pid, 0);
}

$return_code = $return_code >> 8;
return $return_code;

This $return_code was 0 even though the $command ran for next 5 minutes.

This happened only once. I could not reproduce this later. Could anyone identify what possibly caused this issue? The $command will invoke several perl threads.

zdim
  • 64,580
  • 5
  • 52
  • 81
tourist
  • 506
  • 1
  • 6
  • 20

2 Answers2

2

That system runs in a parent process after the fork, while the child process falls through right away. Since the @pids is written to in one of the processes (the parent) the other one (the child) doesn't see this change to a parent's variable; its own @pids stays as it was before the if split the processes. Thus since its @pids is empty the child process just passes over that loop.

Then, in that child process that fell through and went over the empty loop, the right-shift (>>) is done on an undefined value ($return_code has not been assigned yet) -- for what you'd get a warning if you had use warnings; on -- and the result of that is converted to 0, which is then assigned. And then that is returned.

So that zero is bogus, meaningless.

Do push @pids, $pid before the if condition, right after fork.

Also, you need to exit one of these processes -- commonly the one inside the if condition -- or you'll have two processes executing all code that follows.

Another option is to add an else branch for the child, in which the child process exits. Then all is well again since the parent will eventually get to its @pids list.


It is common to select branches by

if ($pid == 0) {  # child process enters this branch, parent does not
    ...
}
# parent process drops right here, since its pid is not `0`

In this question that is reversed, testing for true $pid -- and so here the parent process enters the branch and the child drops right below it. That's OK as well of course and the discussion applies.


As soon as a variable is written to the processes get their own copies.

But let's imagine that the other process (child) sees that change in @pids (made in the parent). There would still be a problem -- there would be a race condition, as the child may well get to the loop over @pids before the push @pids, $pid in the parent (inside the if branch) happens.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • In other words, do `push @pids, $pid` _before_ entering the child process, right after `fork`. That's where it belongs anyway – zdim Jul 28 '23 at 06:29
  • The command ran for several minutes before the `$return_code` and several minutes after. Also we are doing `push @pids, $pid` right after fork. – tourist Jul 28 '23 at 06:34
  • "_we are doing push `@pids, $pid` right after fork_" -- no, you are not; it's inside the child process. In the meanwhile the parent drops below and has every chance of beating that. I don't know what ran for how long but you have a bug there, a race condition. Move `push` _before_ entering the child process. See the last sentence in my answer – zdim Jul 28 '23 at 06:38
  • I could not get understand it. `my $pid = fork; my @pids; if ($pid) { push(@pids, $pid); }` , will the if condition go into a child process ? – tourist Jul 28 '23 at 06:40
  • The `if ($pid == 0) { ... }` is what the child process enters. You reversed it sort of, but it still works: `if ($pid) { }` branch is entered by the parent _but not by the child_ (which has pid of 0), so the child just skips it and immediately gets to the loop over `@pids` – zdim Jul 28 '23 at 06:42
  • So you want `my @pids; my $pid = fork // die "can't fork: $!"; push @pids, $pid; if ($pid) { .... }`. I assume you `fork` more than one process, thus a separate declaration of `@pids`. (If it were just one you wouldn't need an array `@pids`) – zdim Jul 28 '23 at 06:49
  • added an additional condition for child process. Please refer to the code in the question. – tourist Jul 28 '23 at 06:52
  • @tourist "_added an additional condition for child process_" -- well, but that fixes the problem. Then there is no question to ask. I reverted that change, to the question meaningful. change it back if you wish -- or let me know that you want that and I'll do it -- and I won't touch it again. – zdim Jul 28 '23 at 06:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254702/discussion-between-tourist-and-zdim). – tourist Jul 28 '23 at 07:00
  • @tourist And added comments to our "chat" (I think that one _doesn't_ get notified of that so I'm saying it here) – zdim Jul 28 '23 at 08:25
  • @tourist Updated the answer again, to summarize that last comment from the chat. – zdim Jul 28 '23 at 08:33
0

I don't know how you're calling perl from python but I am assuming you're using subprocess module

import subprocess
process = subprocess.Popen("call perl script",  stdout=subprocess.PIPE, shell=True)
output = process.communicate()
print(output)