Better Code Through Destruction
by Igor Gariev
|
Pages: 1, 2, 3
You can manage many sensitive resources this way, including file locks, semaphores, and even locks of database tables.
##
## File lock.
##
use Fcntl ':flock';
open my($fh), ">$filename.lock";
eval{
flock($fh, LOCK_EX);
my $sentry = Object::Destroyer->new( sub {flock($fh, LOCK_UN)} );
##
## Actual lock-sensitive code is here.
## It is safe to die.
##
};
##
## Semaphore
##
use Thread::Semaphore;
use Object::Destroyer;
my $s = Thread::Semaphore->new();
eval{
$s->down;
my $sentry = Object::Destroyer->new( sub { $s->up } );
##
## Critical code is here, die is safe
##
};
##
## MySQL database table lock.
##
use DBI;
my $dbh = DBI->connect("dbi:mysql:...", "", "");
eval{
$dbh->do("LOCK TABLE table1 READ");
my $sentry = Object::Destroyer->new(
sub { $dbh->do("UNLOCK TABLES"); }
);
##
## Again, actual code must be here
##
};
The code is clean, simple, and quite self-explanatory.
Simple Transactions
Everyone who works with relational databases knows how useful transactions are. One of the features of transactions is atomicity: either all modifications of data are committed at once, or all of them are ignored. Your data is always consistent; it's not possible to leave it in an inconsistent state. The same effect is possible in Perl code:
use Object::Destroyer 2.0;
my ($account1, $account2) = (15, 15);
printf("Account1=%d, Account2=%d, Total=%d\n",
$account1, $account2, $account1+$account2);
eval {
my $coderef = create_savepoint(\$account1, \$account2);
my $sentry = Object::Destroyer->new($coderef);
die "before changes" if rand > 0.7;
$account1 += 3;
die "after account 1 was modified" if rand > 0.7;
$account2 -= 3;
die "after account 2 was modified" if rand > 0.7;
##
## The transaction is considered to be committed here
## and $sentry can be dismissed.
## $coderef->() will not be called.
##
$sentry->dismiss;
die "after transaction is committed" if rand > 0.7;
};
print "Died $@" if $@;
printf("Account1=%d, Account2=%d, Total=%d\n",
$account1, $account2, $account1+$account2);
sub create_savepoint {
## Save references to variables and their current values
my @vars;
foreach my $ref (@_) {
die "Can remember only scalar values" unless ref($ref) eq 'SCALAR';
push @vars, { ref => $ref, value => $$ref };
}
## A closure to restore their values
return sub {
foreach my $var (@vars) {
${ $var->{ref} } = $var->{value};
}
};
}
Run the script several times. Due to rand, it will break on varying lines, but it is not possible to get a Total value other than 30.
See Also
RAII is by no means a new technique. It is very popular in the world of C++ programming. If you are not afraid of C++, you may find interesting the standard container auto_ptr and effective auto_ptr usage. The non-standard ScopeGuard class provides lexically scoped resource management in C++.
The Devel::Monitor module has guidelines on how to design data structures with weak and circular references. Its primary goal, by the way, is to trace the memory consumption of a running script.
There are several modules for lexically scoped resource management on CPAN, but the Object::Destroyer is my favorite. You may also look at Hook::Scope, Scope::Guard and Sub::ScopeFinalizer.
Finally, Object Oriented Exception Handling in Perl discusses why exceptions are invaluable for big projects.
Showing messages 1 through 2 of 2.
2007-09-17 01:39:26 dmitrykarasik [Reply]
Article is very good, however I'd say that it revolves around poor decisions made by authors of poor modules. If the aforementioned HTML::Tree and friends a) don't know about Scalar::Util::weaken and b) don't free their memory on DESTROY ( I'm not saying they don't, I don't know, this was just my impression by reading the article), that's simply is a bad design. I personally wouldn't use such modules in first place.
- Excellent Article
2007-06-19 07:47:57 perlpilot [Reply]
This was a great article. The examples were clear and they illustrated real-world situations. Good job!



