January 2012 Archives

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.

How to install into 5.6.2

Contrary to popular belief you can install almost all CPAN packages under 5.6.2.

First Step:

Set the urllist in your ./cpan/CPAN/MyConfig.pm to http://cp5.6.2an.barnyard.co.uk/ and rm ./cpan/Metadata and the 3 outdated sources/authors/01mailrc.txt.gz sources/modules/02packages.details.txt.gz sources/modules/03modlist.data.gz

Then start installing.

There are a couple of authors who agressively used to boycott 5.6 in their dependencies (schwern, dagolden, kenwilliams, rsignes), but it is quite easy to fix this. So after a couple of refused installations do this:

cd .cpan/build
grep 'use ExtUtils::MakeMaker' */Makefile.PL
sed -i 's/use ExtUtils::MakeMaker 6.3[0-9];/use ExtUtils::MakeMaker;/' \
   */Makefile.PL

Module::Build can also be installed (0.34), DBI (1.604), Moose (0.40), DateTime (0.66) and many more.

If your CPAN Metadata fails to find the package this is because nobody submitted a PASS report for this package on 5.6.2 (I will not do, because I patched the packages) and so the dynamic cp5.6.2an.barnyard.co.uk does not offer it. Fall back to your default CPAN url and try again then.

For makefile writers: It is easy to allow older perls to install by checking the EUMM version at run-time. For META and LICENSE checks.

Enjoy the speed of an unbloated and fast perl5.6.2!

BTW: If you are concerned about the 5.6 vulnerability for CGI scripts on oCERT-2011-003 style DOS attacks apply this patch to 5.6.2: http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2011-12/msg01082.html

p5p in the last years made such things possible: http://www.reddit.com/r/perl/comments/odt7m/perlisnowtheslowestprogramminglanguage/

About Reini Urban

user-pic Working at cPanel on cperl, B::C (the perl-compiler), parrot, B::Generate, cygwin perl and more guts, keeping the system alive.