Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

"Backing up" a subroutine during runtime.

by dextius (Monk)
on Oct 21, 2008 at 16:30 UTC ( #718534=perlmeditation: print w/replies, xml ) Need Help??

Background: Recently, the perl based system I am responsible for had a production issue when new code was being rolled out. It is our desire to never fully crash due to some state information that would be lost that is too transient to effectively serialize to disk with any level of efficiency. It was decided to simply increase the amount of exception handling within the core routines, so even if an issue was encountered, it could be isolated, and even repaired while the system remained running. We have full regression and unit tests for our system, which posed an interesting problem for me. How do you run your production code, and verify that it can handle any part of it's code throwing an exception, without breaking any test after this one will run (and not modifying the code BEFORE the test is run). The solution that I came up with, is grounded in the flexibility of our testing framework, so I welcome any alternative solutions, just understand that I'm not filling you in on all the constraints I'm working within.
  • This test needs to be able to modify any method of any object within the system
  • I need to temporarily modify a method of an object during runtime
  • I need to verify the exception handler works
  • I need to verify that the logic picks up where it left off after the "bug" is fixed
  • None of the runtime changes I make to test this functionality can have any impact after on subsequent tests
Here is a code snippet to illustrate the approach I took. (keep in mind that I understand that the "eval" is not needed, I built this test to try and more accurately model my system specifically)
#!/usr/bin/perl package test_package; use strict; use warnings; sub new { return bless {}; } sub test_function { print "Original test_function! $_[1]\n"; } sub loader { eval($_[1]); warn "ERROR: $@\n" if ( $@ ); } package main; use strict; use warnings; my $counter = 0; my $f = new test_package(); $f->test_function($counter++); # 1 # "backup" the test_function method $f->loader('*test_package::new_test_function = \&test_package::test_fu +nction;'); $f->new_test_function($counter++); # Overwrite the existing "test_function" subroutine $f->loader("package test_package; sub test_function { print \"New outp +ut! \$_[1]\n\"; } 1;"); # Should see two different outputs $f->test_function($counter++); $f->new_test_function($counter++); # "Restore" the test function from it's "backup" $f->loader("*test_package::test_function = *test_package::new_test_fun +ction;"); $f->test_function($counter++);
The output looks like this:
Original test_function! 0 Original test_function! 1 Subroutine test_function redefined at (eval 2) line 1. New output! 2 Original test_function! 3 Original test_function! 4
Using this approach allowed me to do the following:
  • Copy existing function calls to a "backup" area.
  • Overwrite subroutines with that I supply
  • I can restore the system to the original state, with ease, so it won't impact subsequent tests
All in all, this approach worked for me, and although it may be common knowledge to some, it wasn't something I found documented on the web, or even within perlmonks. I hope it comes in useful in the future.

Replies are listed 'Best First'.
Re: "Backing up" a subroutine during runtime.
by grinder (Bishop) on Oct 21, 2008 at 18:17 UTC

    Yep. This is truly an amazing feature of Perl.

    I once did a similar thing with an IRC robot. It had various handlers that were triggered based on regexps matching things passing in a channel.

    Without taking down the bot, I could edit the code of a particular handler on the server, and then connect to the channel, send a sekret™ private message to the bot, which would instruct it to reload the handler code, save the old code onto an array of coderefs attached to the trigger regexp, and install the new code in its place.

    If the code failed to evaluate, it wouldn't get installed. If the new code ran for a while and I didn't like what it did, I could send another message to tell the bot to throw away the new version of the code and reinstate the version N-1.

    I thought it was incredibly cool to be able to undo code changes, and have always kept it in the back of my mind hoping I'd be able to put it to use in Real Life sometime.

    • another intruder with the mooring in the heart of the Perl

      That is exactly what our code does. The ruby guys in my office were going on about how this is such a well covered tactic in their world. I wasn't out to prove them wrong, I just needed the equivalent functionality. I'm glad I found it.
Re: "Backing up" a subroutine during runtime.
by TGI (Parson) on Oct 21, 2008 at 18:49 UTC

    This looks pretty slick.

    I'd probably put some more abstraction into the interface of the loader function. Also, you shouldn't need two steps to create a backup and install a new sub.

    #!/usr/bin/perl package test_package; use strict; use warnings; sub new { return bless {}; } sub test_function { print "Original test_function! $_[1]\n"; } sub loader { my $self = shift; my $override = shift; # Name of function to override. my $new_function = shift; # code ref to install my $backup; # keep a code ref of the original here. local @_; eval { no strict 'refs'; no warnings 'redefine'; die "Can't override non-existant function $override\n" unless exists &{$override}; $backup = \&{$override}; *{$override} = $new_function; }; if ( $@ ) { warn "ERROR: $@\n" if ( $@ ); $backup = undef; } return $backup; } package main; use strict; use warnings; my $counter = 0; my $f = new test_package(); $f->test_function($counter++); # 1 my $newsub = sub { print "New output! $_[1]+\n"; 1 }; # Replace function, get backup in return. my $backup = $f->loader( 'test_package::test_function', $newsub ); $f->test_function($counter++); # Restore function. $f->loader( "test_package::test_function", $backup); $f->test_function($counter++); my $faked =$f->loader( "test_package::fake_function", $newsub ); warn "Unable to override function" unless $faked; #$f->fake_function( 'Fake'); # program will die

    If you specifically want to focus on overriding and restoring functions, you could actually use your object to store the names of overridden functions and their original code refs. Then your code could look like this:

    $f->override( 'test::function', sub { print 'foo' } ); # Do your testing; $f->restore( 'test::function' );

    Update: You might also want to look at Sub::Installer.


    TGI says moo

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://718534]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (1)
As of 2022-05-18 04:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (68 votes). Check out past polls.

    Notices?