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.
    ●...
Why an object wrapper?
●   All of the code required to handle all of the 
    special cases ends up bloating your work.
● ...
Requirements for Wrappers.
●   They should feel like the real thing.
●   They cannot leak.
●   These are not easy: making ...
Where wrappers can help.
●   Re­usable sanity checks (what goes on here).
●   Compatibility layers for changing API's.
●  ...
Perly wrappers.
●   There is (of course) more than one way:
    ●   Override a method in a derived class.
    ●   Replace ...
My particular itch: DBI with forks.
●   DBI objects cannot be re­cycled across forks.
●   I was writing heavily forked cod...
Why forks hurt DBI
●   The points are sharp.
●   Database connections use PID's to bookkeep 
    connections.
●   Servers ...
Cleaning Up $dbh
●   Forked process:
    ●   Disable destroy side effects on the channel for 
        each object.
    ●  ...
Cleaning up $dbh
                              my @kidz
                              = do
●   Extract the list of       {...
Sanity checking a PID
●   Perl stores he current Process ID (“PID”) in  
    “$$” (i.e., looks like the shell variable).
●...
Looks objective:
●   The code is re­usable.
    ●   All of the handles are cleaned up the same way.
    ●   All of the for...
My Requirements
●   These are called for every method in the 
    wrapped class: they have to be fast.
●   They also canno...
O::W is built in layers.
●   Object::Wrapper base class provides generic 
    new, DESTROY, and an AUTOLOAD.
●   The AUTOL...
A Place to Hang Your Hat
●   A few hooks are all that's kneaded:
    ●   pre­dispatch for the sanity check.
    ●   straig...
Throwing a Hook
●   Perl's “can” is rather helpful:
    ●   It returns true if the object “can”.
    ●   Its true value is...
Structure of a Franger
●   Remember the need for speed, flexibility, and 
    encapsulation of the wrapped object.
●   Tak...
Constructing a Franger
●   Store the call stack for validation as­is.

    sub new
    {
        my $proto   = shift;
    ...
OK, but what do you do with it?
●   Whatever you want.
●   Object::Wrapper, in fact, does nothing but re­
    dispatch the...
Wrapper AUTOLOAD is Standard
●   Does nothing more than necessary.
●   Useful when the DESTROY check is enogh.

AUTOLOAD
{...
Oedipus Not­Complex: Forks
AUTOLOAD
{
    my $franger = shift;
    my ( $obj, $pid ) = @$franger;

    $pid == $$
     or ...
Clean Up Your Mess: O::W::Destroy
DESTROY
{
    my $franger = shift;

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

   ...
DBI: It's All How You Clean Up
●   Check for cached_kids.
●   Within the constructing PID: 
    ●   Finish all the kids.
 ...
First Step: Find the Kids
sub cleanup
{
    my ( $dbh, $pid ) = @_;

    my $struct
    = do
    {
         my $drh    = $...
Second Step: Do the Deed

    if( $$ != $pid )
    {
        # handle crossed a fork: turn off side
        # effects of d...
Cleaning Up Statements Is Easier
sub cleanup
{
    my ( $sth, $pid ) = @_;

    if( $$ ~~ $pid )
    {
        # same proc...
Getting What You Want: 
Overloading Constructors
●   For DBI this requires versions of connect and 
    connect_cached, pr...
Overloading STH Constructors
●   These get a DBI wrapper object.
●   Returning a wrapped DBD.
    sub prepare
    {
      ...
Catch: This wrapper leaks.
●   DBI offers a tied­hash interface.
●   Kinda hard to handle this with a blessed array.
●   F...
Other Uses for Object::Wrappers
●   Maximum time:
     bless [ $obj, ( time + $window ) ];
     time < $franger->[1] or .....
Only Your Wrapper Knows For Sure
●   Long­lived processes may not want to die after 
     the wrapped object hits its limi...
Summary
●   Keeping your object safe is easy:
    ●   Use a franger.
    ●   Check before you use it.
    ●   Make sure yo...
Object::Franger: Wear a Raincoat in your Code
Upcoming SlideShare
Loading in...5
×

Object::Franger: Wear a Raincoat in your Code

806

Published on

Object wrappers can be lightweight and provide re-usable sanity checks. This shows one way of wrapping DBI objects.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
806
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Object::Franger: Wear a Raincoat in your Code

  1. 1. Object::Franger Wear a Raincoat in Your Code Steven Lembark Workhorse Computing
  2. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 29. Catch: This wrapper leaks. ● DBI offers a tied­hash interface. ● Kinda hard to handle this with a blessed array. ● Fortunately, the hash interface is rarely  necessary.
  30. 30. Other Uses for Object::Wrappers ● Maximum time: bless [ $obj, ( time + $window ) ]; time < $franger->[1] or ... ● Maximum reuse: bless [ $obj, $counter ]; --$franger->[1] or ...
  31. 31. 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.
  32. 32. Summary ● Keeping your object safe is easy: ● Use a franger. ● Check before you use it. ● Make sure you clean up afterwards.
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×