简体   繁体   中英

Calling a Perl fork from within a BEGIN block

I am having trouble understanding the fork behavior in Perl when it is called from within a BEGIN block. In perlfork , I read this

BEGIN blocks

The fork() emulation will not work entirely correctly when called from within a BEGIN block. The forked copy will run the contents of the BEGIN block, but will not continue parsing the source stream after the BEGIN block. For example, consider the following code:

 BEGIN { fork and exit; # fork child and exit the parent print "inner\\n"; } print "outer\\n"; 

This will print:

 inner 

rather than the expected:

 inner outer 

But, as I read it, this only applies to platforms where fork is emulated. Since I'm concerned about (and test the code on) Linux, that shouldn't be a problem, should it?

Indeed, if I copy the example code from that document

BEGIN {
    fork and exit;
    print "inner\n";
}
print "outer\n";

this is what happens when I run it

jirka@debian:~/xpath$ perl /tmp/test.pl
jirka@debian:~/xpath$ inner
outer

which seems consistent.

However, when I removed the exit I expected to have both a parent and a child process. That that didn't behave as I expected.

Here is my new code

BEGIN {
    fork;
    print "inner\n";
}
print "outer\n";

and here is the run

jirka@debian:~/xpath$ perl /tmp/test.pl
inner
outer
jirka@debian:~/xpath$ inner

I expected two inner and two outer . The second outer is missing.

My question is, what causes this strange behaviour, and how could it even be described.

It looks to me like the child no longer has the source file open (or it is all buffered in the parent only?)

Trying the code via -e succeeds.

My first guess is that the parent exits before the child has finished running, causing it to die (SIGPIPE?), but waiting for the child yields the same output:

BEGIN {
   $pid = fork;
   print "inner\n";
}
print "outer\n";
waitpid $pid, 0 if $pid;

Output:

inner
outer
inner

So indeed, it doesn't seem possible to accomplish. The reason for the problem is that the parent and the child share the same file pointer to the source file. When one reads from the source file, it advances the file pointer for both.

For example, if I prevent one of the processes from reading further down the file using __DATA__ , the other process will continue reading past the __DATA__ and execute the code there. If I append the following to the above program:

__DATA__
...8KB of newlines...
die("boo!");

I get:

inner
outer
inner
boo! at a.pl line 90.

OK, the problem really seems to be that the child and the parent stomp on each other's source file descriptor. Strace gives:

read(3, "BEGIN {\n        fork;\n\tprint \"in"..., 8192) = 67
_llseek(3, 46, [46], SEEK_SET)          = 0
_llseek(3, 0, [46], SEEK_CUR)           = 0
clone(Process 29716 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb75329a8) = 29716
[pid 29715] write(1, "inner\n", 6inner
)      = 6
[pid 29715] read(3, "    print \"outer\\n\";\n", 8192) = 21
[pid 29715] read(3, "", 8192)           = 0
[pid 29715] close(3)                    = 0
...
write(1, "inner\n", 6inner
)                  = 6
read(3, "", 8192)                       = 0
close(3)                                = 0

This seems to be caused by the fact that parent and child share a single file read pointer. From man fork :

  • The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent. This means that the two descriptors share open file status flags, current file offset , ...

Now, this begs the question: How to separate those file descriptors' offsets?

I wonder how you had inner printed after the final prompt?

If you read the documentation carefully

The fork() emulation will not work entirely correctly when called from within a BEGIN block. The forked copy will run the contents of the BEGIN block, but will not continue parsing the source stream after the BEGIN block

it says that the child process will parse (and therefore run) only the remainder of the BEGIN block. So the child prints inner and does no more.

Without the exit call the parent process goes on to print inner , and subsequently outer , so you should have

inner
inner
outer

I wish I had a Unix box to hand to try this out, but will do so when I get home

My question is, what causes this strange behaviour, and how could it even be described.

Actually BEGIN block is executed when syntax is checked (roughly). There is many phases such as BEGIN , UNITCHECK , CHECK , INIT and END . So when you do your fork() in BEGIN the program actually is not run yet.

On system where fork() is emulated, this caused by internal Perl interpretator state, since program is early start state (your code isnot even compiled!). So on emulated environment i think Perl drops emulated forks after compile.

I think to fix that you must place your code into INIT block. Please read perlmod man page for more detailed info about these stages.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM