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 reusable: forks, timeouts,
and signals all use common code.
● The division of labor isolates the wrapper and
inner portions into reusable 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.
● Reusable 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 & Bottomhalf 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 inplace.
● Object::Wraper (AUTOLOAD).
7. My particular itch: DBI with forks.
● DBI objects cannot be recycled 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 longerlived 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
rechecking 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 sanitychecked on method calls,
dealt with carefully in DESTROY.
12. Looks objective:
● The code is reusable.
● 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 redispatches 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:
● predispatch for the sanity check.
● straightjacket 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 asis.
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 NotComplex: 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 tiedhash 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 outoforder 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
● Longlived 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.