One of my client projects has suffered from running code too quickly (go Perl!) and too much in parallel (go Unix processes!) and has kept me tuning the transactional model of the backend storage.
I'm not worried about inconsistencies, but rather detecting and avoiding lock contention where possible, rescheduling transactions where lock contention does occur, and above all, wrapping transactions in the smallest units possible.
The fantastic (but not for all projects) KiokuDB has been very useful for this project. If you use it with a transactional backend, it provides a DBI-inspired method to invoke a function reference in its own transaction:
$dir->txn_do( sub { $dir->delete( @args ) } );
I was glad of the ability to pass around closures in a lightweight manner; delaying computation until KiokuDB has set up the transaction is very useful. Even so, I was less pleased with all of the syntactic noise littering my code in several places.
That's because I wasn't taking full advantage of the abstraction possibilities of late binding.
Now I have instead:
use Try::Tiny;
sub do_txn
{
my ($self, $method, @args) = @_;
my $dir = $self->dir;
my $sub = sub { $dir->$method( @args ) };
while ( ... )
{
try { $dir->txn_do( $sub ) }
catch { ... }
}
...
}
... and I can call it with:
$self->do_txn( add => $new_obj );
$self->do_txn( delete => @args );
$self->do_txn( update => $invocant );
... to remove visual clutter from other parts of the code. Better yet, all of the retrying semantics are in one place, and I can add logging or tuning there.
I hoisted the creation of the closure passed to txn_do()
out of
the while
loop for two reasons. Primarily, I believe doing so
makes the code within the loop clearer. It's also slightly more efficient
(slightly) to create the closure once than on every trip through the
loop. (If efficiency were of greater concern—and lock contention is much
more troublesome here—I could pre-resolve the method and bind to the
function reference representing the candidate method first instead of the name
of the method, but that would add at least one line of code and possibly a few
more for error checking, and it's not worthwhile yet.)
Despite Perl 5's support for pervasive and relatively lightweight closures,
sometimes they're not the best abstraction to use if your primary concern is
code clarity. I believe the resulting code is much clearer (even if
do_txn()
isn't the right name).