madscientist has asked for the wisdom of the Perl Monks concerning the following question:

I've created an environment that allows people to write "plugins" (really just Perl subs) that are registered, then run in a certain order, with various framework features provided. One of those features is error handling, so plugins can register Perl subs which are pushed onto a list, and if some subsequent plugin fails, the error handler subs are invoked to "clean up".

This all works great and I catch various signals for sanity. But of course I cannot catch kill -9 or a power failure, so now I'm trying to see if I can recover after these kinds of events. My basic idea is to use Dumper() to write out a file containing information about the state of the processing, then when I start up and notice one of these variable cache files I can read it and see what was going on. And one of the important items to remember are these error handler subs.

Up until now I've been storing sub refs, like: my::Error::register(\&handler);

That doesn't work well with Dumper() of course. So I was going to change the my::Error::register() sub to take a string name of the handler instead of a sub ref. Dumper() would write out the string name. Then, I could create the sub ref from the string with \&{$string}. This does work.

The first question is, is this the best way to do it? Or is there another way? For example, given a sub ref is there any way I can convert that back into a string, that could be written by Dumper()? I'm assuming not since if there was, Dumper() would already do it... wouldn't it?

If this is how others would do it, I have some concerns that maybe someone could provide hints about. For example, what if someone fat-fingers the name of the handler? Is there a way in my::Error::register() to "look up" the string and see if it's a real sub, without actually running it, so I can die() immediately in my::Error::register() instead of later when trying to run the error handler?

Second, these plugins are actually separate packages, all inherited from "my::Plugin", so today I can just run register(\&handler) but to use a string I suppose I need my::Error::register('my::Plugin::SomePlugin::handler'). Is there any reasonable way to make this easier? I guess I can always add the 'my::Plugin::' prefix myself, or something, but... I dunno.

Thanks for any hints you can provide!

Replies are listed 'Best First'.
Re: Saving/recovering sub refs in a file
by ikegami (Patriarch) on Jun 16, 2010 at 00:28 UTC

    Is there a way in my::Error::register() to "look up" the string and see if it's a real sub,

    (ref($s) // '') eq 'CODE'
    Is there any reasonable way to make this easier? I guess I can always add the 'my::Plugin::' prefix myself, or something, but... I dunno.

    It sounds like your obsession about the length of a line that will only occur once per plugin.

    By the way, it could be shortened from

    my::Error::register('my::Plugin::SomePlugin::handler')
    to
    my::Error::register(__PACKAGE__)
    if it's done from the module that needs registering.
      Is there a way in my::Error::register() to "look up" the string and see if it's a real sub,
      (ref($s) // '') eq 'CODE'

      Sorry, I wasn't clear. I'm familiar with ref(); that's not what I'm looking for (unless it has hidden properties I'm not aware of).

      What I need to know is this: the caller will pass in a string naming a sub, and I want to know if the string corresponds to a real sub that has been defined, or not. In case the caller mistyped the name.

      So for example, the caller runs: my::Error::register('my::Plugins::ThisPlugin::miHandler') but the name of the real sub is myHandler... is there any way inside the register() function that I can figure out if the string I've been passed is the name of a real, existing sub or not (without running it of course!)

      I'd like to throw an error up-front in the register() function if someone passes a bad/invalid name for a sub, rather than waiting until a real error occurs and only finding out then, when someone tries to invoke the error handler, that it was bad.

        $ perl -le'sub f {} print exists(&$_)?1:0 for qw( f g )' 1 0

        So for example, the caller runs: my::Error::register('my::Plugins::ThisPlugin::miHandler') but the name of the real sub is myHandler...

        Then you should call my::Plugins::ThisPlugin::miHandler. If it throws an error, so be it. If you want to catch the error, use eval BLOCK or whatever module you prefer.

        That said, it might make more sense to register the package, then always call a specific method of that package (say ->handle_error()). You could even allow objects in addition to packages for free.

Re: Saving/recovering sub refs in a file
by cdarke (Prior) on Jun 16, 2010 at 13:18 UTC
    given a sub ref is there any way I can convert that back into a string

    You can get the subroutine source from a reference using B::Deparse. For eample:
    #!/usr/bin/perl use strict; use warnings; use B::Deparse; sub myfunc { print "Hello @_\n"; return 42 } my $tricky = \&myfunc; my $deparse = B::Deparse->new(); my $body = $deparse->coderef2text($tricky); print "$body\n";
    Gives:
    { use warnings; use strict 'refs'; print "Hello @_\n"; return 42; }
      You can get the subroutine source from a reference using B::Deparse.

      Hm. That's interesting. What I was really looking for was a way to find the string name of a sub, not the source for it. IOW, suppose I had:

      package my::Plugins::SomePlugin; sub myhandler { do_something(); } my::Error::register(\&myhandler);
      Then inside my::Error::register() somehow I could convert the ref I'd been passed back into the name of the sub (in this case the string "my::Plugins::SomePlugin::myhandler").

        I would do it this way:
        package My::Plugins::SomePlugin; use strict; use warnings; use Sub::Identify ':all'; sub name { my $subname = sub_name(\&myhandler); defined $subname and print "$subname\n"; } sub myhandler { do_something(); } My::Plugins::SomePlugin::name;
Re: Saving/recovering sub refs in a file
by repellent (Priest) on Jun 16, 2010 at 22:13 UTC