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.

SYNOPSIS

See USE section for complexer example and the test.pl script.

  use Safe::World ;

  my $world = Safe::World->new(
  stdout => \$stdout ,     ## - redirect STDOUT to this scalar.
  stderr  => \$stderr ,    ## - redirect STDERR to this scalar.
  flush => 1 ,             ## - output is flushed, soo don't need to wait exit to
                           ##   have all the data inside $stdout.
  ) ;
  
  ## Evaluate some code:
  $world->eval(q`
     use Data::Dumper ;
     print Dumper( {a => 1 , b => 2} ) ;
  `);
  
  $world->close ; ## ensure that everything is finished and flushed.

  die($stderr) if $stderr ;
  
  print $stdout ;
  
  $world = undef ; ## Destroy the world. Here the compartment is cleanned.

Note that in this example, inside the World is loaded Data::Dumper, but Data::Dumper was loaded only inside of it, keeping the outside normal.

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.

Actually each Each instance (WORLD object), is a Safe compartment (Safe::World::Compartment) with all the resources of a normal Perl interpreter implemented (IO, @INC, %INC, Dynaloader::, etc...). But what happens inside each World doesn't change the enverioment outside of it or other Worlds.

Each instance (WORLD object) has their own STDOUT, STDERR and STDIN handlers, also has a fake HEADOUT output (when the argument headout is past) 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 ).

** This is good to use if you are using a SCALAR as STDOUT. Also will run faster, since uses a simple STDOUT handler.

no_clean (bool)

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

no_set_safeworld (bool)

If TRUE tell to not set the internal object $SAFEWORLD, that gives access to it self inside the compartment.

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.

** If the sub returns '0', exit will be skiped.

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.

block_stdout ; block_stderr

Block the output to STDOUT/STDERR of the WORLD.

unblock_stdout ; unblock_stderr

UNblock the output to STDOUT/STDERR of the WORLD.

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_no_warn (CODE)

Evaluate a code inside the World without error alerts, warn(), die(), exit() or any output to STDERR.

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 data 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 a reference 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.

use_shared (MODULE)

Load a module inside a World created for cache (an World to be linked to the others).

This will require inside the World the MODULE and handle automatically the sharedpack list of all the modules loaded and sub-loaded by the MODULE.

Link some package to the world.

  $world->link_pack("Win32") ;

Unlink a package.

Unlink all the packages linked to this World.

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

Link the compartment of a world to another.

  $world->link_world( $world_shared ) ;

Unlink/disassemble a World from another.

Unlink all the worlds linked to this.

op_deny (OP, ...)

Deny the listed operators from being used when compiling code in the compartment (other operators may still be permitted).

Example of use:

  $world->deny_only( qw(chroot syscall exit dump fork lock threadsv) ) ;

** See Opcode for the OP list.

op_deny_only (OP, ...)

Deny only the listed operators from being used when compiling code in the compartment (all other operators will be permitted).

** See Opcode for the OP list.

op_permit (OP, ...)

Permit the listed operators to be used when compiling code in the compartment (in addition to any operators already permitted).

** See Opcode for the OP list.

op_permit_only (OP, ...)

Permit only the listed operators to be used when compiling code in the compartment (no other operators are permitted).

** See Opcode for the OP list.

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 also can redefine this attributes 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.

reset_internals

Reset the internal flags. Soo, if the World was exited, can be reused.

reset_output

Redefine the outputs (STDOUT, STDERR, STDIN, HEADOUT) targets.

Arguments: stdout, stderr, stdin, headout.

** Note that only pasted arguments will be used to redefine. Soo, wont be used default values like reset().

** See reset().

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 the shared variables. Note that this is called only to clean the package.

stdout_data (NEW_DATA)

Return the stdout data.

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

(NEW_DATA) can be used to set the new value of stdout data or to undef it.

stdout_buffer_data (NEW_DATA)

Return the buffered stdout data.

** Note that this will only return data if STDOUT is not FLUSHED.

(NEW_DATA) can be used to set the new value of the buffer or to undef it.

tiestdout

The tiehandler of STDOUT.

tiestderr

The tiehandler of STDERR.

warn

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

CACHING PERL MODULES EXAMPLE

To make a cache system for the Perl Modules you should use the method use_shared() and 2 Worlds, one as cache and other for execution:

  use Safe::World ;
  
  ## Cache world shouldn't have output, soo using $tmp:
  my $tmp ;
  
  my $world_cache = Safe::World->new(
  stdout => \$tmp ,
  stderr  => \$tmp ,
  flush => 1 ,
  ) ;
  
  ## Cache this perl module:
  $world_cache->use_shared('Data::Dumper') ;
  
  ## Run 3 Worlds using Data::Dumper cached:
  for(1..3) {
    my ( $stdout , $stderr ) ;
    my $world = Safe::World->new(
    stdout => \$stdout ,
    stderr  => \$stderr ,
    flush => 1 ,
    ) ;
    
    $world->link_world($world_cache) ;
  
    $world->eval(q`
       print Data::Dumper::Dumper( \%INC ) ;
    `);
    
    $world->close ; ## ensure that everything is finished and flushed.
                    ## close() also make an unlink_all_worlds() to
                    ## free $world_cache for the next World. 
    
    print "$stdout\n" ;
    print "=======================\n" ;
  }

And here's the output:

  $VAR1 = {
            'Carp.pm' => '#shared#',
            'Exporter.pm' => '#shared#',
            'XSLoader.pm' => '#shared#',
            'strict.pm' => 'C:/Perl/lib/strict.pm',
            'warnings/register.pm' => '#shared#',
            'warnings.pm' => '#shared#',
            'overload.pm' => '#shared#',
            'Data/Dumper.pm' => '#shared#'
          };
  
  =======================
  $VAR1 = {
            'Carp.pm' => '#shared#',
            'Exporter.pm' => '#shared#',
            'XSLoader.pm' => '#shared#',
            'strict.pm' => 'C:/Perl/lib/strict.pm',
            'warnings/register.pm' => '#shared#',
            'warnings.pm' => '#shared#',
            'overload.pm' => '#shared#',
            'Data/Dumper.pm' => '#shared#'
          };
  
  =======================
  $VAR1 = {
            'Carp.pm' => '#shared#',
            'Exporter.pm' => '#shared#',
            'XSLoader.pm' => '#shared#',
            'strict.pm' => 'C:/Perl/lib/strict.pm',
            'warnings/register.pm' => '#shared#',
            'warnings.pm' => '#shared#',
            'overload.pm' => '#shared#',
            'Data/Dumper.pm' => '#shared#'
          };
  
  =======================
  

SEE ALSO

Safe::World::Scope, HPL, Safe, Opcode.

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.