Object::Franger: Wear a Raincoat in your Code

Like this? Share it with your network

Share

Object::Franger: Wear a Raincoat in your Code

  • 1,030 views
Uploaded on

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

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

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
1,030
On Slideshare
1,030
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
1
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 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. 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. Other Uses for Object::Wrappers ● Maximum time: bless [ $obj, ( time + $window ) ]; time < $franger->[1] or ... ● Maximum reuse: bless [ $obj, $counter ]; --$franger->[1] or ...
  • 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. Summary ● Keeping your object safe is easy: ● Use a franger. ● Check before you use it. ● Make sure you clean up afterwards.