Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

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.