What to avoid in BEGIN blocks
I came along this crazy code.
But first some short explanation. The perl compiler B::C saves the state of any program at CHECK time, and runs it later. That means every action in BEGIN blocks is frozen and then thawed on a potential another machine in later time. Not all action can be frozen/thawed as we know from the common modules Data::Dumper or Storable
The compiler is better than those. It can restore regular expressions which Storable cannot. It can save the state of some IO object, which Data::Dumper can not. It can save the whole dependency tree of code. Data::Dumper or Storable can only save data, not code along with it.
But some actions are really a bad a idea to be restored at run-time.
1st sample:
my $f;open($f,">&STDOUT");print $f "ok"
This is trivial. The fileno 2 is dup'ed to $f. All at run-time. Nevertheless you will not be able to freeze/thaw $f. The compiler can.
my $f;BEGIN{open($f,">&STDOUT");}print $f "ok"
This is not so trivial. Again the fileno 2 is dup'ed to $f. But the compiler must restore the $f->IoIFP and IoOFP handles to stdout. Done. This a typical problem from Test::Builder and testing the core testsuite with the compiler. Test::NoWarnings e.g. failed here. The old perlcc had a --testsuite command-line switch to cope with that.
my $f;BEGIN{open($f,"<README");}read $f,my $in, 2; print "ok"
This is hard or next to impossible. A IO::File handle must be restored. But as everybody knows only on Windows it is easy the restore a filename from a filehandle. It's pretty hard for the compiler to save the filename from open
. It would have to override CORE::open
or override IO::File
. So this cannot be generally compiled.
There can be pipes or sockets to be restored.
Right now B::C warns and leaves a line in the resulting C code, so a tool can fill in the missing filename. Of course it is better to fix the sourcecode to avoid this.
And now think of this:
my ($pid, $out, $in);
BEGIN {
local(*FPID);
$pid = open(FPID, "echo <<EOF |"); # DIE
open($out, ">&STDOUT"); # EASY
open(my $tmp, ">", ".tmpfile"); # HARD to get filename, WARN
print $tmp "test\n";
close $tmp; # OK closed
open($in, "<", ".tmpfile"); # HARD to get filename, WARN
}
# === run-time ===
print $out "ok";
kill 1, $pid; # DIE, if $pid is set at BEGIN only
read $in, my $x, 4;
unlink ".tmpfile";
Killing a pid at run-time saved at compile-time is a really bad idea.
I tried the example with the $pid
being -1
. All windows gone. Restart.
On the right are comments from the compiler point of view.
How to check the kill problem at compile-time? Where does the $pid
come from? One must analyze the code where its data came from. From user input or command-line is okay, this is run-time. From compiled code executed at run-time, okay.
From compiled code from BEGIN blocks, very bad!
BEGIN { $myself = $$; }
Whow. Good idea, but does not work yet. Avoid BEGIN here. Fixable though. The compiler can add code to restore the pid from run-time, and do not store the compile-time pid. But do we want that? No. The user has to decide.
There are many more examples which are a really bad idea to do. Beware. Not always are BEGIN blocks the best idea.
At cPanel where we use compiled perl we put common init stuff into the INIT block if the compiled code should init it at run-time, and into the BEGIN block if it should be initialized at compile-time.
App::Options is another example. It does options checking (including command-line arguments and config file) during BEGIN time, so I can't "perl -c" my script to check for syntax error. I always have to upload it to the production server first (where there is an actual config file). If there is a syntax error, rinse and repeat. Drives me crazy. As soon as my replacement module for App::Options is ready, I'm off it.
BTW, I did suggest[1] moving the options checking to INIT phase, but so far this is not implemented. Maybe the author never does "perl -c"? :)
BEGIN abuse also irks me quite a bit.
[1] https://rt.cpan.org/Public/Bug/Display.html?id=60737