The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Safe::World - Create multiple virtual instances of a Perl interpreter that can be assembled together.

DESCRIPTION

With Safe::World you can create multiple virtual instances/compartments of a Perl interpreter, that will work/run without touch the other instances/compartments and mantaining the main interpreter normal.

Each instance (WORLD object) has their own STDOUT, STDERR and STDIN handlers, also has a fake HEADOUT output for the headers implemented inside the STDOUT. Soo, you can use this to redirect the outputs of the WORLD object to a FILEHANDLER, SCALAR or a SUB.

The module Safe::World was created for 3 purposes:

1. A Safe compartment that can be "fully" cleanned.

This enable a way to run multiple scripts in one Perl interpreter process, saving memory and time. After each execution the Safe compartment is "fully" cleanned, saving memory for the next compartment.

2. A Safe compartment with the output handlers implemented, creating a full WORLD, working as a normal Perl Interpreter from inside.

A normal Safe objects doesn't have the output handlers, actually is just a compartment to run codes that can't go outsied of it. Having a full WORLD implemented, with the STDOUT, STDERR, STDIN and HEADERS handlers, the output can be redirected to any kind of listener. Also the error outputs (STDERR) can be catched via sub (CODE), that can be displayed in the STDOUT in a nice way, or in the case of HTML output, be displayed inside comment tags, instead to go to an error log.

But to implement a full WORLD warn(), die() and exit() need to be overwrited too. Soo you can control if exit() will really exit from the virtual interpreter, and redirect the warn messages.

3. A WORLD object (a virtual Perl interpreter) that can be linked/assembled with other WORLD objects, and work/run as if the objects where only one, then be able to unlink/disassemble them.

This is the advanced purpose, that need all the previous resources, and most important thing of Safe::World. Actually this was projected to work with mod_perl, soo the Perl codes can be runned in different compartments, but can have some part of the code cached in memory, specially the Perl Modules (Classes) that need to be loaded all the time.

Soo, you can load your classes in one World, and your script/page in other World, then link them and run your code normally. Then after run it you unlink the 2 Worlds, and only CLEAN the World with your script/page, and now you can keep the 1st World with your Classes cached, to link it again with the next script/page to run.

Here's how to implement that:

1 Cache World.

A cache world is created, where all the classes common to the all the different scripts/pages are loaded.

1 Execution World.

For each script/page is created a world, each time that is executed (unless a script need to be persistent). Inside this worlds only the main code of the scripts/pages are loaded.

1 Linking 2 WORLDS.

Using the method link_world(), two worlds can be assembled. Actually one world is imported inside another. In this case the Cache World is linked to the Execution World. Now you can't evaluate codes in the Cache World, since it's shared, and evaluation is only accepted in the Execution World.

  my $world_cache = Safe::World->new(sharepack => ['DBI','DBD::mysql']) ;
  $world_cache->eval(" use DBI ;") ;
  $world_cache->eval(" use DBD::mysql ;") ;
  
  my ( $stdout , $stderr ) ;
  
  my $world_exec = Safe::World->new(
  stdout => \$stdout ,
  stderr => \$stderr ,
  ) ;
  
  $world_exec->link_world($world_cache) ;
  
  $world_exec->eval(q`
      $dbh = DBI->connect("DBI:mysql:database=$db;host=$host", 'user' , 'pass') ;
  `);

USAGE

See the test.pl script for more examples.

  use Safe::World ;

  my $world = Safe::World->new(
  stdout => \$stdout ,     ## - redirect STDOUT to this scalar.
  stderr  => \$stderr ,    ## - redirect STDERR to this scalar.
  headout => \$headout ,   ## - SCALAR to hold the headers.
  autohead => 1 ,          ## - tell to handle headers automatically.
  headsplitter => 'HTML' , ## - will split the headers from the content handling
                           ##   the output as HTML.
  flush => 1 ,             ## - output is flushed, soo don't need to wait exit to
                           ##   have all the data inside $stdout.
  
  on_closeheaders => sub { ## sub to call when headers are closed (when content start).
                       my ( $world ) = @_ ;
                       my $headers = $world->headers ;

                       $headers =~ s/\r\n?/\n/gs ;
                       $headers =~ s/\n+/\n/gs ;
                       $headers .= "\015\012\015\012" ; ## add the headers end.
  
                       $world->print($headers) ; ## print the headers to STDOUT
                       $world->headers('') ; ## clean the headers scalar.
                     } ,
  
  on_exit => sub { ## sub to call when exit() happens.
               my ( $world ) = @_ ;
               $world->print("<!-- ON_EXIT_IN -->\n");
               return 0 ; ## 0 make exit() to be skiped. 1 make exit() work normal.
             } ,
  ) ;
  
  ## Evaluate some code:
  $world->eval(q`
     print "Content-type: text/html\n\n" ; 
     
     print "<html>\n" ;
     print "content1\n" ;
     
     ## print some header after print the content,
     ## but need to be before flush the output!
     $SAFEWORLD->print_header("Set-Cookie: FOO=BAR; domain=foo.com; path=/;\n") ;
     
     print "content2\n" ;
     print "</html>\n" ;
     
     warn("some alert to STDERR!") ;
     
     exit;
  `);
  
  $world->close ; ## ensure that everything is finished and flushed.
  
  print $socket $stdout ; ## print the output to some client socket.
  print $log $stderr ; ## print errors to a log.
  
  $world = undef ; ## Destroy the world. Here the compartment is cleanned.

METHODS

new

Create the World object.

Arguments:

root

The name of the package where the compartment will be created.

By default is used SAFEWORLDx, where x will increse: SAFEWORLD1, SAFEWORLD2, SAFEWORLD3...

stdout (GLOB|SCALAR|CODE ref)

The STDOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

DEFAULT: \*main::STDOUT

stderr (GLOB|SCALAR|CODE ref)

The STDERR target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

DEFAULT: \*main::STDERR

stdin (GLOB ref)

The STDIN handler. Need to be a IO handler.

DEFAULT: \*main::STDIN

headout (GLOB|SCALAR|CODE)

The HEADOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

env (HASH ref)

The HASH reference for the internal %ENV of the World.

flush (bool)

If TRUE tell that STDOUT will be always flushed ( $| = 1 ).

no_clean (bool)

If TRUE tell that the compartment wont be cleaned when destroyed.

autohead (bool)

If TRUE tell that the STDOUT will handler automatically the handlers in the output, using headsplitter.

headsplitter (REGEXP|CODE)

A REGEXP or CODE reference to split the header from the content.

Example of REGEXP:

  my $splitter = qr/(?:\r\n\r\n|\012\015\012\015|\n\n|\015\015|\r\r|\012\012)/s ; ## This is the DEFAULT

Example of SUB:

  sub splitter {
    my ( $world , $data ) = @_ ;
    
    my ($headers , $rest) = split(/\r\n?\r\n?/s , $data) ;
  
    return ($headers , $rest) ;
  }
sharepack (LIST)

When a World is linked to another you need to tell what packages inside it can be shared:

  my $world_cache = Safe::World->new(sharepack => ['DBI','DBD::mysql']) ;
on_closeheaders (CODE)

Sub to be called when the headers are closed.

on_exit (CODE)

Sub to be called when exit() is called.

on_select (CODE)

Sub to be called when the WORLD is selected to evaluate codes inside it.

on_unselect (CODE)

Sub to be called when the WORLD is unselected, just after evaluate the codes.

CLEAN

Call DESTROY() and clean the compartment.

** Do not use the World object after this!

call (SUBNAME , @ARGS)

Call a sub inside the World and returning their values.

  my @ret0 = $world->call('foo::methodx', $var1 , time()); ## foo::methodx($var1 , time())
  
  my @ret1 = $world->call('methodz', 123); ## main::methodz(123)

close

Ensure that everything is finished and flushed. You can't evaluate codes after this!

close_tiestdout()

Close the tied STDOUT.

close_tiestderr()

Close the tied STDERR.

eval (CODE)

Evaluate a code inside the World and return their values.

eval_pack (PACKAGE , CODE)

Evaluate inside some package.

Same as:

  my $code = "print time ;" ;
  $world->eval("package foo ; $code") ;

eval_args (CODE , ARGS)

Evaluate code sending args (defining internal @_):

  $world->eval_args(' print "$_[0]\n" ' , qw(a b c) ); ## Should print 'a'.

eval_pack_args (PACKAGE , CODE , ARGS)

Same as eval_args(), but setting the package name to run the code.

flush (bool)

Set $| to 1 or 0 if bool is defined.

Also flush STDOUT. Soo, if some sata exists in the buffer it will be flushed to the output.

get (VAR)

Return some variable value from the World:

  my $document_root = $world->get('$ENV{DOCUMENT_ROOT}') ;

get_from (PACKAGE , VAR)

Return some variable value inside some package in the World:

  my $document_root = $world->get('Foo' , '$VERSION') ;

get_ref (VAR)

Return reference of to a variable:

  my $env = $world->get_ref('%ENV') ;
  $$env{ENV}{DOCUMENT_ROOT} = '/home/httpd/www' ; ## Set the value inside the World.

get_ref_copy (VAR)

Return reference copy of a variable:

  my $env = $world->get_ref_copy('%ENV') ;

** Note that the reference inside $env is not pointing to a variable inside the World.

headers

Return the headers data.

** Note that this will only return data if HEADOUT is defined as SCALAR.

Link some package to the world.

  $world->link_pack("Win32") ;

Unlink a package.

Link the compartment of a world to another.

  $world->link_world( $world_shared ) ;

Unlink/disassemble a World from another.

Print some data to the STDOUT of the world.

Print some data to the HEADOUT of the world.

Print some data to the STDERR of the world.

Same as print.

Print some data to the STDOUT of the world.

redirect_stdout (SCALAR)

Redirect the STDOUT to a scalar. Soo, you can internally redirect a peace of the output to a scalar.

In this example I want to catch what the sub test() prints:

    sub test { print "sub_test[@_]" ; }
    
    print "A\n" ;
    
    my $out ;
    $SAFEWORLD->redirect_stdout(\$out) ;
    
      test(123);
    
    $SAFEWORLD->restore_stdout ;
    
    print "B\n" ;
    print "OUT: <$out>" ;

** See restore_stdout().

restore_stdout().

Restore the STDOUT output if a redirect_stdout() was made before.

** See redirect_stdout().

reset

Reset the object flags. Soo, if it was closed (exited) can be reused.

You can redefine this flags (sending this arguments):

stdout (GLOB|SCALAR|CODE ref)

The STDOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

DEFAULT: \*main::STDOUT

stderr (GLOB|SCALAR|CODE ref)

The STDERR target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

DEFAULT: \*main::STDERR

stdin (GLOB ref)

The STDIN handler. Need to be a IO handler.

DEFAULT: \*main::STDIN

headout (GLOB|SCALAR|CODE)

The HEADOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.

env (HASH ref)

The HASH reference for the internal %ENV of the World.

root

Return the root name of the compartment of the World.

safe

Return the Safe object of the World.

scanpack_table (PACKAGE)

Scan the elements of a symbol table of a package.

scanpacks

Return the package list of a World.

select_static

Select static a World to make multiple evaluations faster:

  $world->select_static ;
    $world->eval("... 1 ...") ;
    $world->eval("... 2 ...") ;
    $world->eval("... 3 ...") ;
  $world->unselect_static ;  

unselect_static

Unselect the world. Should be called after select_static().

set (VAR , VALUE_REF) || (VAR , VALUE , 1)

Set the value of a varaible inside the World:

    my @inc = qw('.','./lib') ;
    $world->set('@INC' , \@inc) ;
    
    ## To set a value that is a reference, like an object:
    
    $world->set('$objectx' , $objecty , 1) ;    

set_sharedpack (PACKAGE)

Set a package inside a world SHARED, soo, when this World is linked to another this package is imported.

** See argument sharepack at new().

unset_sharedpack (PACKAGE)

Unset a SHARED package.

set_vars (VARS_VALUES_LIST)

  $world->set_vars(
  '%SIG' => \%SIG ,
  '$/' => $/ ,
  '$"' => $" ,
  '$;' => $; ,
  '$$' => $$ ,
  '$^W' => 0 ,
  ) ;

share_vars (PACKAGE , VARS_LIST)

Set a list of variables to be shared:

  $world->share_vars( 'main' , [
  '@INC' , '%INC' ,
  '$@','$|','$_', '$!',
  ]) ;

unshare_vars (PACKAGE , VARS_LIST)

Unshare a list of variables

stdout_data

Return the stdout data.

** Note that this will only return data if STDOUT is defined as SCALAR.

tiestdout

The tiehandler of STDOUT.

tiestderr

The tiehandler of STDERR.

Unlink all the packages linked to this World.

** You shouldn't call this by your self. This is only used by DESTROY().

warn

Send some warn message to the world, that will be redirected to the STDERR of the World.

SEE ALSO

HPL, Safe.

NOTES

This module was made to work with HPL and mod_perl, enabling multiple executions of scripts in one Perl interpreter, and also brings a way to cache loaded modules, making the execution of multiple scripts and mod_perl pages faster and with less memory.

Actually this was first writed as HPL::PACK module, then I haved moved it to Safe::World to be shared with other projects. ;-P

** Note that was hard to implement all the enverioment inside Safe::World, soo if you have ideas or suggestions to make this work better, please send them. ;-P

AUTHOR

Graciliano M. P. <gm@virtuasites.com.br>

I will appreciate any type of feedback (include your opinions and/or suggestions). ;-P

Enjoy!

THANKS

Thanks to:

Elizabeth Mattijsen <liz@dijkmat.nl>, to test it in different Perl versions and report bugs.

COPYRIGHT

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.