Callback-Frame

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

NAME
    Callback::Frame - Preserve error handlers and "local" variables across
    callbacks

SYNOPSIS
        use Callback::Frame;

        my $callback;

        frame_try {
          $callback = fub {
                        die "some error";
                      };
        } frame_catch {
           my $stack_trace = shift;
           print $stack_trace;
           ## Also, $@ is set to "some error at ..."
        };

        $callback->();

    This will print something like:

        some error at tp.pl line 7.
        ----- Callback::Frame stack-trace -----
        synopsis.pl:8 - ANONYMOUS FRAME
        synopsis.pl:13 - ANONYMOUS FRAME

BACKGROUND
    When programming with callbacks in perl, you create anonymous functions
    with "sub { ... }". These functions are especially useful because when
    they are called they will preserve their surrounding lexical
    environment.

    In other words, the following bit of code

        my $callback;
        {
          my $var = 123;
          $callback = sub { $var };
        }
        print $callback->();

    will print 123 even though $var is no longer in scope when the callback
    is invoked.

    Sometimes people call these anonymous functions that reference variables
    in their surrounding lexical scope "closures". Whatever you call them,
    they are essential for convenient and efficient asynchronous
    programming.

    For many applications we really like straightforward callback style. The
    goal of Callback::Frame is to simplify the management of dynamic
    environments (defined below) while leaving callback style alone.

DESCRIPTION
    The problem that this module solves is that although closures preserve
    their lexical environment, they don't preserve error handlers or "local"
    variables.

    Consider the following piece of broken code:

        use AnyEvent;

        eval {
          $watcher = AE::timer 0.1, 0,
            sub {
              die "some error";
            };
        };

        ## broken!
        if ($@) {
          print STDERR "Oops: $@";
        }

        AE::cv->recv;

    The intent behind the "eval" above is obviously to catch any exceptions
    thrown by the callback. However, this will not work because the "eval"
    will only be in effect while installing the callback in the event loop,
    not while running the callback. When the event loop calls the callback,
    it will probably wrap its own "eval" around the callback and you will
    see something like this:

        EV: error in callback (ignoring): some error at broken.pl line 6.

    (The above applies to EV which is a well-designed event loop. Other
    event loops may fail more catastrophically.)

README  view on Meta::CPAN

    preserved. In this case it is the dynamic exception handlers that we
    would like to preserve. In some other cases we would like to preserve
    dynamically scoped (aka "local") variables (see below).

    By the way, "lexical" and "dynamic" are the lisp terms. When it applies
    to variables, perl confusingly calls dynamic scoping "local" scoping,
    even though the scope is temporal, not local.

    Here is how we could fix the code above using Callback::Frame:

        use AnyEvent;
        use Callback::Frame;

        frame_try {
          $watcher = AE::timer 0.1, 0, fub {
                                         die "some error";
                                       };
        } frame_catch {
          print STDERR "Oops: $@";
        };

        AE::cv->recv;

    Now we see the desired error message:

        Oops: some error at fixed.pl line 8.

    We created two frames to accomplish this: A root frame with "frame_try"
    which contains the exception handler, and a nested frame with "fub" to
    use as a callback. Unlike "fub", "frame_try" immediately executes its
    frame. Because the nested callback frame is created while the root frame
    is executing, the callback will preserve the dynamic environment
    (including the exception handler) of the root frame.

USAGE
    This module exports the following subs: "frame", "fub", "frame_try",
    "frame_catch", "frame_local", and "frame_void".

    "frame" is the general interface. The other subs are just syntactic
    sugar around "frame". "frame" requires at least a "code" argument which
    should be a coderef (a function or a closure). It will return another
    coderef that "wraps" the coderef you passed in. When this wrapped codref
    is run, it will reinstate the dynamic environment that was present when
    the frame was created, and then run the coderef that you passed in as
    "code".

    "frame" also accepts "catch", "local", "existing_frame", and "name"
    parameters which are described below.

    "fub" simplifies the conversion of existing callback code into
    Callback::Frame enabled code. For example, given the following AnyEvent
    statement:

        $watcher = AE::io $sock, 0, sub { do_stuff() };

    In order for the callback to have its dynamic environment maintained,
    you just need to change it to this:

        $watcher = AE::io $sock, 0, fub { do_stuff() };

    IMPORTANT NOTE: All callbacks that may be invoked outside the dynamic
    environment of the current frame should be created with "frame" or "fub"
    so that the dynamic environment will be correctly re-applied when the
    callback is invoked.

    The "frame_try" and "frame_catch" subs are equivalent to a call to
    "frame" with "code" and "catch" parameters. However, unlike with
    "frame", the frame is executed immediately.

    "frame_void" takes a single callback argument. This can be useful if you
    wish to kick off an unassociated asynchronous action while handling. If
    the action is run in void context, there is no way for it to throw an
    exception that will affect your request, or to access its local
    variables. Note that you probably should install a separate
    "frame_catch" in case the unassociated operation throws exceptions.

    Libraries that wrap callbacks in frames can use the
    "Callback::Frame::is_frame()" function to determine if a given callback
    is already wrapped in a frame. It returns true if the callback is
    wrapped in a frame and is therefore suitable for use with
    "existing_frame". Sometimes libraries like to automatically wrap a
    callback in a frame unless it already is one:

        if (!Callback::Frame::is_frame($callback)) {
          $callback = frame(code => $callback);
        }

    If you wish to run a coderef inside an existing frame's dynamic
    environment, when creating a frame you can pass in an existing frame as
    the "existing_frame" parameter. When this frame is executed, the "code"
    of the frame will be run inside "existing_frame"'s dynamic environment.
    This is useful for throwing exceptions from within some given callback's
    environment (timeouts for example):

        frame(existing_frame => $callback, code => sub {
          die "request timed out";
        })->();

    "existing_frame" is also useful for extracting/setting a callback's
    local variables.

    Although you should never need to, the internal frame stack can be
    accessed at $Callback::Frame::top_of_stack. When this variable is
    defined, a frame is currently being executed.

NESTING AND STACK-TRACES
    Callback::Frame tries to make adding error handling support to an
    existing asynchronous application as easy as possible by not forcing you
    to pass extra parameters around. It should also make life easier because
    as a side effect of adding error checking it also can be made to produce
    detailed and useful "stack traces" that track the callback history of
    some connection or transaction.

    Frames can be nested. When an exception is raised, the most deeply
    nested "catch" handler is invoked. If this handler itself throws an
    error, the next most deeply nested handler is invoked with the new
    exception but the original stack trace. If the last "catch" handler
    re-throws the error, the error will be thrown in whatever dynamic
    environment was in place when the callback was called, usually the event
    loop's top-level handler (probably not what you want).

    When a "catch" handler is called, not only is $@ set, but also a
    stack-trace string is passed in as the first argument. All frames will
    be listed in this stack-trace, starting with the most deeply nested
    frame.

    If you want you can use simple frame names like "accepted" but if you
    are recording error messages in a log you might find it useful to name
    your frames things like "accepted connection from $ip:$port at $time"
    and "connecting to $host (timeout = $timeout seconds)".

    All frames you omit the name from will be shown as "ANONYMOUS FRAME" in
    stack-traces.

    Since multiple frames can be created within the same parent frame and
    therefore multiple child frames can be active at once, frames aren't
    necessarily arranged in terms of a stack. Really, the frame "stack" is



( run in 2.698 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )