Object::Franger


 Wear a Raincoat
  in Your Code


   Steven Lembark
 Workhorse Computing
Why do you need one?
●   Frankly, because you can't control your object:  
    you never know where it might end up.
    ●   Other people can fiddle with it.
    ●   Forks can leave it in unintended places.
    ●   It might get bugs (or worse).
    ●   External issues can require special handling.
Why an object wrapper?
●   All of the code required to handle all of the 
    special cases ends up bloating your work.
●   Most wrappers are re­usable: forks, timeouts, 
    and signals all use common code.
●   The division of labor isolates the wrapper and 
    inner portions into re­usable pieces.
●   All of which make these good fodder for OO.
Requirements for Wrappers.
●   They should feel like the real thing.
●   They cannot leak.
●   These are not easy: making the wrapper feel 
    enough like the real thing to fool both your 
    object and the caller.
Where wrappers can help.
●   Re­usable sanity checks (what goes on here).
●   Compatibility layers for changing API's.
●   Localizing values on the call stack.
●   Modifying context.
●   Simplifying the wrapped code, which doesn't 
    have to deal with all of this in one place.
    ●   Example: Top­ & Bottom­half device drivers.
Perly wrappers.
●   There is (of course) more than one way:
    ●   Override a method in a derived class.
    ●   Replace a method in the symbol table.
    ●   AUTOLOAD from stub namespace.
●   Example:
    ●   Attribute Handlers replace the subroutine before 
        it is installed (functional).
    ●   Object::Trampoline replaces the object in­place.
    ●   Object::Wraper (AUTOLOAD).
My particular itch: DBI with forks.
●   DBI objects cannot be re­cycled across forks.
●   I was writing heavily forked code for high­
    volume database access.
●   Needed the forks, needed the DBI to handle 
    the children gracefully.
●   Wanted child proc's to fail gracefully – 
    hopefully without damaging the database.
Why forks hurt DBI
●   The points are sharp.
●   Database connections use PID's to bookkeep 
    connections.
●   Servers cannot handle multiple clients requests 
    on the same channel.
●   Destroying an object in one process brings 
    kicks its longer­lived sibling in the socket.
Cleaning Up $dbh
●   Forked process:
    ●   Disable destroy side effects on the channel for 
        each object.
    ●   Iterate the handle and cached kids.
●   Within process:
    ●   Call $kid­>finish for all cached kids.
    ●   Call $dbh­>disconnect.
Cleaning up $dbh
                              my @kidz
                              = do
●   Extract the list of       {
                                  my $drh = $dbh->{ Driver };

    handles.                       my $list
                                   = $drh
                                   ? $drh->{ CachedKids }
●   If the process did not         : '';


    create them, then              $list
                                   ? values %$list
                                   : ()
    inactivate the            };

    DESTROY side effects.     if( $$ != $pid )
                              {
                                  $_->{ InactiveDestroy } = 1
●   Otherwise finish the      }
                                  for ( $dbh, @kidz );

                              else
    kids and then             {
                                  $_->finish for @kidz;
    disconnect.                    $dbh->disconnect;
                              }
Sanity checking a PID
●   Perl stores he current Process ID (“PID”) in  
    “$$” (i.e., looks like the shell variable).
●   Storing this when the handle is created allows 
    re­checking it before dispatching the call.
●   If the stored PID and $$ don't agree then the 
    handle needs to be cleaned up.
●   This has to be sanity­checked on method calls, 
    dealt with carefully in DESTROY.
Looks objective:
●   The code is re­usable.
    ●   All of the handles are cleaned up the same way.
    ●   All of the fork checks are the same.
    ●   All of the wrapping is done the same way.
●   It can be parameterized.
    ●   $$ is $$ wherever you are.
●   Frangers are good for objects.
My Requirements
●   These are called for every method in the 
    wrapped class: they have to be fast.
●   They also cannot use bulky storage since they 
    are add to the requirements for all wrapped 
    objects.
●   They should also avoid unintended side effects 
    (e.g., modifying object values, calling context).
O::W is built in layers.
●   Object::Wrapper base class provides generic 
    new, DESTROY, and an AUTOLOAD.
●   The AUTOLOAD calls sanity check hooks in 
    the derived classes and re­dispatches the result 
    or croaks.
●   Object::Wrapper::Fork bookkeeps $$.
●   Object::Wrapper::Fork::DBI deals with the 
    cleanups.
A Place to Hang Your Hat
●   A few hooks are all that's kneaded:
    ●   pre­dispatch for the sanity check.
    ●   straight­jacket for failed sanity checks.
●   These accommodate all of the necessary 
    customizations for the basic wrapper.
Throwing a Hook
●   Perl's “can” is rather helpful:
    ●   It returns true if the object “can”.
    ●   Its true value is a subref to the  object's handler.
●   This makes:
        my $handler = $object­>can( $hook );
        $object­>$handler­>( @argz );
    synonymous with:
        $handler­>( $object, @argz );
Structure of a Franger
●   Remember the need for speed, flexibility, and 
    encapsulation of the wrapped object.
●   Take a look at the calling standard: generic 
    object with arguments.
●   The structure is obvious:
      bless [ $object, @sanity_argz ], $class;
●   Resulting in:
      $handler->( @$obj );
Constructing a Franger
●   Store the call stack for validation as­is.

    sub new
    {
        my $proto   = shift;
        my $class   = blessed $proto || $proto;

        my $object = shift
        or croak "Bogus franger: missing object";

        bless [ $object, @_ ], $class
    }
OK, but what do you do with it?
●   Whatever you want.
●   Object::Wrapper, in fact, does nothing but re­
    dispatch the method calls.
●   Useful for cases where the interesting part of 
    the wrapper is in the DESTROY, not the 
    individual calls.
Wrapper AUTOLOAD is Standard
●   Does nothing more than necessary.
●   Useful when the DESTROY check is enogh.

AUTOLOAD
{
    my $franger = shift;

    my $i       = rindex $AUTOLOAD, ':';
    my $name    = substr $AUTOLOAD, ++$i;

    my $sub     = $franger->[0]->can( $name )
    or confess "Bogus $AUTOLOAD: '$franger->[0]' cannot '$name'";

    $franger->[0]->$sub( @_ )
}
Oedipus Not­Complex: Forks
AUTOLOAD
{
    my $franger = shift;
    my ( $obj, $pid ) = @$franger;

    $pid == $$
     or confess "Bogus $AUTOLOAD: @{$franger} crosses fork.";

    my $i       = rindex $AUTOLOAD, ':';
    my $name    = substr $AUTOLOAD, ++$i;

    my $sub     = $obj->can( $name )
    or confess "Bogus $AUTOLOAD: '$obj' cannot '$name'";

    # goto &$obj is slower according to Benchmark...

    $obj->$sub( @_ )
}
Clean Up Your Mess: O::W::Destroy
DESTROY
{
    my $franger = shift;

    my $class   = blessed $franger || $franger;

    # $cleanupz{ $class } may be a method name or coderef to save time.

    my $cleanup = $cleanupz{ $class } || $franger->can( 'cleanup' )
    or confess "Bogus franger: no cleanup for '$franger' or '$class'";

    my $sub
    = ref $cleanup
    ? $cleanup
    : $franger->can( $cleanup )
    or confess "Bogus $class: no cleanup for '$franger' ($class)";

    'CODE' eq reftype $sub
    or confess "Bogus $class: not a coderef '$sub'";

    $cleanup->( @$franger );

    return
}
DBI: It's All How You Clean Up
●   Check for cached_kids.
●   Within the constructing PID: 
    ●   Finish all the kids.
    ●   Disconnect the parent.
●   Within child Proc's:
    ●   Disable destroy side effects in the kids & parent.
First Step: Find the Kids
sub cleanup
{
    my ( $dbh, $pid ) = @_;

   my $struct
   = do
   {
        my $drh    = $dbh->{ Driver };

        $drh
        ? $drh->{ CachedKids }
        : ''
   };

   my @kidz
   = $struct
   ? values %$struct
   : ()
   ;
Second Step: Do the Deed

    if( $$ != $pid )
    {
        # handle crossed a fork: turn off side
        # effects of destruction.

        $_->{ InactiveDestroy } = 1
        for
        (
            $dbh,
            @kidz
        );
    }
    else
    {
        $_->finish for @kidz;

        $dbh->disconnect;
    }

    # at this point the DBI object has been
    # prepared to go out of scope politely.

    return
}
Cleaning Up Statements Is Easier
sub cleanup
{
    my ( $sth, $pid ) = @_;

    if( $$ ~~ $pid )
    {
        # same process: finalize the handle and disconnect.
        # caller deals with clones.

           $sth->{ Active }
           and $sth->finish;
    }
    else
    {
           $sth->{ InactiveDestroy } = 1;
    }

    # at this point the DBD object has been
    # prepared to go out of scope politely.

    return
}
Getting What You Want: 
Overloading Constructors
●   For DBI this requires versions of connect and 
    connect_cached, prepare and prepare_cached.
●   Connect simply returns the wrapped $dbh:
    sub connect
    {
        shift;

        my $dbh     = DBI->connect( @_ )
        or croak 'Fail connect: ' . $DBI::errstr;

        Object::Wrapper::Fork::dbh->new( $dbh )
    }
Overloading STH Constructors
●   These get a DBI wrapper object.
●   Returning a wrapped DBD.
    sub prepare
    {
        my $franger = shift;

        my ( $dbh, $pid ) = @$franger;

        $pid == $$
        or confess "Bogus prepare: @{ $franger } crosses fork.";

        my $sth = $dbh->prepare( @_ )
        or croak 'Failed prepare: ' . $dbh->errstr;

        Object::Wrapper::Fork::sth->new( $sth )
    }
Wrappers are not 100% effective
●   DBI offers a tied­hash interface.
●   Kinda hard to handle this with a blessed array.
●   Fortunately, the hash interface is rarely 
    necessary.
●   There is also one more issue for destructors.
Making Happy ENDings
●   Perl destroys objects out­of­order on exit.
●   This means that we also have to wrap 
    DBI::DESTROY to get complete coverage.
    ●   Fortunately this isn't all that hard to do with the 
        Symbol module's qualify_to_ref.
    ●   This requires a map of $dbh → O::W::F::DBI 
        objects that can be used to dispatch destruction.
    ●   No time to describe it here.
Other Uses for Object::Wrappers
●   Maximum time:
     bless [ $obj, ( time + $window ) ];
     time < $franger->[1] or ...
●   Maximum reuse:
     bless [ $obj, $counter ];
     --$franger->[1] or ...
Only Your Wrapper Knows For Sure
●   Long­lived processes may not want to die after 
     the wrapped object hits its limit.
●   Nice thing is that they don't have to:
      --$franger->[ 1 ]
      or @$franger = ( $class->new( ... ), $counter );
●   This is handy for classes with memory leaks in 
    the objects.
Summary
●   Keeping your object safe is easy:
    ●   Use a franger.
    ●   Check before you use it.
    ●   Make sure you clean up afterwards.

Object::Franger: Wear a Raincoat in your Code

  • 1.
    Object::Franger Wear a Raincoat in Your Code Steven Lembark Workhorse Computing
  • 2.
    Why do you need one? ● Frankly, because you can't control your object:   you never know where it might end up. ● Other people can fiddle with it. ● Forks can leave it in unintended places. ● It might get bugs (or worse). ● External issues can require special handling.
  • 3.
    Why an object wrapper? ● All of the code required to handle all of the  special cases ends up bloating your work. ● Most wrappers are re­usable: forks, timeouts,  and signals all use common code. ● The division of labor isolates the wrapper and  inner portions into re­usable pieces. ● All of which make these good fodder for OO.
  • 4.
    Requirements for Wrappers. ● They should feel like the real thing. ● They cannot leak. ● These are not easy: making the wrapper feel  enough like the real thing to fool both your  object and the caller.
  • 5.
    Where wrappers can help. ● Re­usable sanity checks (what goes on here). ● Compatibility layers for changing API's. ● Localizing values on the call stack. ● Modifying context. ● Simplifying the wrapped code, which doesn't  have to deal with all of this in one place. ● Example: Top­ & Bottom­half device drivers.
  • 6.
    Perly wrappers. ● There is (of course) more than one way: ● Override a method in a derived class. ● Replace a method in the symbol table. ● AUTOLOAD from stub namespace. ● Example: ● Attribute Handlers replace the subroutine before  it is installed (functional). ● Object::Trampoline replaces the object in­place. ● Object::Wraper (AUTOLOAD).
  • 7.
    My particular itch: DBI with forks. ● DBI objects cannot be re­cycled across forks. ● I was writing heavily forked code for high­ volume database access. ● Needed the forks, needed the DBI to handle  the children gracefully. ● Wanted child proc's to fail gracefully –  hopefully without damaging the database.
  • 8.
    Why forks hurt DBI ● The points are sharp. ● Database connections use PID's to bookkeep  connections. ● Servers cannot handle multiple clients requests  on the same channel. ● Destroying an object in one process brings  kicks its longer­lived sibling in the socket.
  • 9.
    Cleaning Up $dbh ● Forked process: ● Disable destroy side effects on the channel for  each object. ● Iterate the handle and cached kids. ● Within process: ● Call $kid­>finish for all cached kids. ● Call $dbh­>disconnect.
  • 10.
    Cleaning up $dbh my @kidz = do ● Extract the list of  { my $drh = $dbh->{ Driver }; handles. my $list = $drh ? $drh->{ CachedKids } ● If the process did not  : ''; create them, then  $list ? values %$list : () inactivate the  }; DESTROY side effects. if( $$ != $pid ) { $_->{ InactiveDestroy } = 1 ● Otherwise finish the  } for ( $dbh, @kidz ); else kids and then  { $_->finish for @kidz; disconnect. $dbh->disconnect; }
  • 11.
    Sanity checking a PID ● Perl stores he current Process ID (“PID”) in   “$$” (i.e., looks like the shell variable). ● Storing this when the handle is created allows  re­checking it before dispatching the call. ● If the stored PID and $$ don't agree then the  handle needs to be cleaned up. ● This has to be sanity­checked on method calls,  dealt with carefully in DESTROY.
  • 12.
    Looks objective: ● The code is re­usable. ● All of the handles are cleaned up the same way. ● All of the fork checks are the same. ● All of the wrapping is done the same way. ● It can be parameterized. ● $$ is $$ wherever you are. ● Frangers are good for objects.
  • 13.
    My Requirements ● These are called for every method in the  wrapped class: they have to be fast. ● They also cannot use bulky storage since they  are add to the requirements for all wrapped  objects. ● They should also avoid unintended side effects  (e.g., modifying object values, calling context).
  • 14.
    O::W is built in layers. ● Object::Wrapper base class provides generic  new, DESTROY, and an AUTOLOAD. ● The AUTOLOAD calls sanity check hooks in  the derived classes and re­dispatches the result  or croaks. ● Object::Wrapper::Fork bookkeeps $$. ● Object::Wrapper::Fork::DBI deals with the  cleanups.
  • 15.
    A Place to Hang Your Hat ● A few hooks are all that's kneaded: ● pre­dispatch for the sanity check. ● straight­jacket for failed sanity checks. ● These accommodate all of the necessary  customizations for the basic wrapper.
  • 16.
    Throwing a Hook ● Perl's “can” is rather helpful: ● It returns true if the object “can”. ● Its true value is a subref to the  object's handler. ● This makes: my $handler = $object­>can( $hook ); $object­>$handler­>( @argz ); synonymous with: $handler­>( $object, @argz );
  • 17.
    Structure of a Franger ● Remember the need for speed, flexibility, and  encapsulation of the wrapped object. ● Take a look at the calling standard: generic  object with arguments. ● The structure is obvious: bless [ $object, @sanity_argz ], $class; ● Resulting in: $handler->( @$obj );
  • 18.
    Constructing a Franger ● Store the call stack for validation as­is. sub new { my $proto = shift; my $class = blessed $proto || $proto; my $object = shift or croak "Bogus franger: missing object"; bless [ $object, @_ ], $class }
  • 19.
    OK, but what do you do with it? ● Whatever you want. ● Object::Wrapper, in fact, does nothing but re­ dispatch the method calls. ● Useful for cases where the interesting part of  the wrapper is in the DESTROY, not the  individual calls.
  • 20.
    Wrapper AUTOLOAD is Standard ● Does nothing more than necessary. ● Useful when the DESTROY check is enogh. AUTOLOAD { my $franger = shift; my $i = rindex $AUTOLOAD, ':'; my $name = substr $AUTOLOAD, ++$i; my $sub = $franger->[0]->can( $name ) or confess "Bogus $AUTOLOAD: '$franger->[0]' cannot '$name'"; $franger->[0]->$sub( @_ ) }
  • 21.
    Oedipus Not­Complex: Forks AUTOLOAD { my $franger = shift; my ( $obj, $pid ) = @$franger; $pid == $$ or confess "Bogus $AUTOLOAD: @{$franger} crosses fork."; my $i = rindex $AUTOLOAD, ':'; my $name = substr $AUTOLOAD, ++$i; my $sub = $obj->can( $name ) or confess "Bogus $AUTOLOAD: '$obj' cannot '$name'"; # goto &$obj is slower according to Benchmark... $obj->$sub( @_ ) }
  • 22.
    Clean Up Your Mess: O::W::Destroy DESTROY { my $franger = shift; my $class = blessed $franger || $franger; # $cleanupz{ $class } may be a method name or coderef to save time. my $cleanup = $cleanupz{ $class } || $franger->can( 'cleanup' ) or confess "Bogus franger: no cleanup for '$franger' or '$class'"; my $sub = ref $cleanup ? $cleanup : $franger->can( $cleanup ) or confess "Bogus $class: no cleanup for '$franger' ($class)"; 'CODE' eq reftype $sub or confess "Bogus $class: not a coderef '$sub'"; $cleanup->( @$franger ); return }
  • 23.
    DBI: It's All How You Clean Up ● Check for cached_kids. ● Within the constructing PID:  ● Finish all the kids. ● Disconnect the parent. ● Within child Proc's: ● Disable destroy side effects in the kids & parent.
  • 24.
    First Step: Find the Kids sub cleanup { my ( $dbh, $pid ) = @_; my $struct = do { my $drh = $dbh->{ Driver }; $drh ? $drh->{ CachedKids } : '' }; my @kidz = $struct ? values %$struct : () ;
  • 25.
    Second Step: Do the Deed if( $$ != $pid ) { # handle crossed a fork: turn off side # effects of destruction. $_->{ InactiveDestroy } = 1 for ( $dbh, @kidz ); } else { $_->finish for @kidz; $dbh->disconnect; } # at this point the DBI object has been # prepared to go out of scope politely. return }
  • 26.
    Cleaning Up Statements Is Easier sub cleanup { my ( $sth, $pid ) = @_; if( $$ ~~ $pid ) { # same process: finalize the handle and disconnect. # caller deals with clones. $sth->{ Active } and $sth->finish; } else { $sth->{ InactiveDestroy } = 1; } # at this point the DBD object has been # prepared to go out of scope politely. return }
  • 27.
    Getting What You Want:  Overloading Constructors ● For DBI this requires versions of connect and  connect_cached, prepare and prepare_cached. ● Connect simply returns the wrapped $dbh: sub connect { shift; my $dbh = DBI->connect( @_ ) or croak 'Fail connect: ' . $DBI::errstr; Object::Wrapper::Fork::dbh->new( $dbh ) }
  • 28.
    Overloading STH Constructors ● These get a DBI wrapper object. ● Returning a wrapped DBD. sub prepare { my $franger = shift; my ( $dbh, $pid ) = @$franger; $pid == $$ or confess "Bogus prepare: @{ $franger } crosses fork."; my $sth = $dbh->prepare( @_ ) or croak 'Failed prepare: ' . $dbh->errstr; Object::Wrapper::Fork::sth->new( $sth ) }
  • 29.
    Wrappers are not 100% effective ● DBI offers a tied­hash interface. ● Kinda hard to handle this with a blessed array. ● Fortunately, the hash interface is rarely  necessary. ● There is also one more issue for destructors.
  • 30.
    Making Happy ENDings ● Perl destroys objects out­of­order on exit. ● This means that we also have to wrap  DBI::DESTROY to get complete coverage. ● Fortunately this isn't all that hard to do with the  Symbol module's qualify_to_ref. ● This requires a map of $dbh → O::W::F::DBI  objects that can be used to dispatch destruction. ● No time to describe it here.
  • 31.
    Other Uses for Object::Wrappers ● Maximum time: bless [ $obj, ( time + $window ) ]; time < $franger->[1] or ... ● Maximum reuse: bless [ $obj, $counter ]; --$franger->[1] or ...
  • 32.
    Only Your Wrapper Knows For Sure ● Long­lived processes may not want to die after   the wrapped object hits its limit. ● Nice thing is that they don't have to: --$franger->[ 1 ] or @$franger = ( $class->new( ... ), $counter ); ● This is handy for classes with memory leaks in  the objects.
  • 33.
    Summary ● Keeping your object safe is easy: ● Use a franger. ● Check before you use it. ● Make sure you clean up afterwards.