Hack 88. Clean Up at the End of a Scope


Execute your cleanup code, no matter how you exit a scope.

Successful programs are robust. Even if errors happen, the programs can adapt, continuing if possible, but always exiting cleanly and sensibly.

Robust programs often need to guarantee that some sort of cleanup happens sometimes, whether that's closing spawned programs properly, flushing buffers, removing temporary files, or giving up an exclusive resource. Some programming languages and platforms provide ways to ensure that your cleanup code always runs. Perl doesn'tbut it does provide the hooks to make it possible.

The Hack

Imagine that you have to write a program that processes and analyzes records from a database. The processing isn't idempotent, so you need to keep track of the most recent record processed. You can assume that the record ids increase monotonically. However, admins can interrupt the program as necessary, as the task has a low priority. You want to make sure that, no matter what happens, you always record the id of the most-recently processed item.

Use Scope::Guard and a closure to schedule an end-of-scope operation:

use Scope::Guard; sub process_records {     my $records  = fetch_records( );     my $last_rec = 0;     my $cleanup  = sub { cleanup( $last_rec ) if $last_rec };     my $guard    = Scope::Guard->new( $cleanup );     for my $record ( @$records )     {         process_record( $record );         $last_rec = $record;     } } sub cleanup {     my $record = shift;     # mark record as last record successfully completed }

process_records( ) declares a lexical variable, $last_rec, to hold the last record successfully processed. It then builds a closure in $cleanup which calls the cleanup( ) subroutine, passing $last_rec. Then it creates a new Scope::Guard object with the closure.

In the normal flow of operation, the subroutine will process all of the records. Then it exits the subroutine. At this point, Perl garbage collects $guard and calls the closure, which itself calls the cleanup( ) subroutine and marks the last successfully processed record.

It's possible that process_record( ) may throw an exception if it cannot process a record appropriately. It's also possible that an admin or resource limit will kill the process, or something else could stop the program before it finishes processing all of the records. Even so, $guard still goes out of scope and calls cleanup( ).

Could you modify process_record( ) to update the record of the last successfully processed record? Absolutely. However, it's not always appropriate or efficient or possible to do so.


Hacking the Hack

Scope::Guard isn't just good for cleanup. You can perform all sorts of interesting operations when you leave a scope. For example, what if you need to chdir to various directories to run external processes that expect very specific current working directories? You could write a chdir replacement that takes a directory and return a Scope::Guard object that returns to the current working directory:

use Cwd; sub change_directory {     my $newdir = shift;     my $curdir = cwd( );     chdir( $newdir );     return Scope::Guard->new( sub { chdir $curdir } ); }

Of course, you could also use David Golden's File::pushd module from the CPAN.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net