[Dancer-users] Dancer::Exception

Tim King timk at jtimothyking.com
Thu Aug 11 23:24:09 CEST 2011


Hi, Damien. I have lots of comments on this issue, as I've just 
implemented an exception-handling mechanism for the project I'm working 
on. Some of the features you're considering could have made my job a lot 
easier.


CATCHING EXCEPTIONS

In the project that I'm working on, it would have been useful from a 
plugin to catch an exception thrown in a route handler. A 
"catch_exception" handler might have worked. But this isn't really a 
hook, because if a "catch_exception" handler consumes an exception, none 
on the other handlers in the chain should be called. It seems to me, 
what we're talking about here is a new concept: a "catch-point." It's 
like a hook in that it calls back to a handler sub, but it more closely 
mirrors try-catch semantics.

As you suggested, someone might also want to catch exceptions from other 
parts of the process, e.g., exceptions thrown during deserialization or 
serialization, or from within a hook. So it seems that there are *at 
least* two distinct points at which applications and plugins might want 
to catch exceptions:

   * One is in $route->execute. A handler hooked into this catch-point 
would execute in the context of the handler, and could do anything the 
handler could do, including modify the response, send errors, return 
serializable data, and throw the same or different exceptions.

   * The other is in Dancer::Handler::render_request. A handler hooked 
into this catch-point would be able to modify the response, including 
sending errors, but would not have access to the serializer or router. 
This handler should also be able to throw the same or other exceptions, 
to be caught by later handlers in the chain.

And they may want to catch non-Dancer exceptions, too. Just as Dancer 
has default handling in Dancer::Handler::render_request for non-Dancer 
exceptions, so also should any catch-point mechanism treat both Dancer 
and non-Dancer exceptions equally.


OTHER IDEAS

Idea 2, try-catch route syntax, doesn't add any capability that wasn't 
implementable before. Might be nice for pretty syntax, but I don't 
particularly care for it. I'd rather just use Try::Tiny, then in my 
route handler:

     try {
         do_some_stuff;
     } catch {
         if (my $e_value = is_dancer_exception(my $e = $_)) {
             handle_dancer_exception($e, $e_value);
         } else {
             die $e; # rethrow exception
         }
     };

Idea 3, using the before_error hook, doesn't really seem like the right 
solution, because this is about catching exceptions, not about 
generating errors. Yes, by default, Dancer instantiates and renders an 
error on a caught exception, but there's no particular reason why that's 
the only way to handle an exception. In some cases, for example, the 
right way to respond to a particular exception might be to forward to a 
different path. Or to generate a non-error response.

And using shared data to communicate the exception is so very, very 
wrong. As you may guess, I have strong feelings about this, because 
Dancer's use of global data has caused me no end of grief, and hack 
after hack after hack in my current project, and has only served to 
remind me why non-constant global data is evil, evil, evil. Any data 
needed to implement Dancer's pretty syntax should be *localized* data 
(not global), as Plack uses (for example) in its pretty-syntax 
implementation. This is actually a discussion item from a much larger 
list I'm compiling, to be published later, and it has little to do with 
Dancer::Exception, except that you mentioned it. For now, I'll just say 
I cannot urge you strongly enough to *avoid* adding anything to 
Dancer::SharedData (or any other global data in the system, such as in 
Dancer::Serializer), and instead consider how to refactor the global 
data that's already there in order to limit its scope or make it more 
object-oriented.


THE SESSION SYSTEM EXAMPLE

I haven't used the Dancer session system, and I'm using the latest 
released Dancer (not the devel branch). So I can't really knowledgeably 
comment on "Real life example 3." But it seems to me that this is a 
situation in which you'd want to use a meta-session class, analogous to 
Dancer::Serializer::Mutable. That is, the mutable serializer chooses 
which serializer to use— in that case, based on the content types that 
the request and the serializers support. Then 
Dancer::Serializer::Mutable proxies the API to that chosen serializer.

One might visualize a session meta-engine that tries each configured 
session engine, handing off control to that engine. As each engine threw 
E_SESSION, the meta-engine would catch it, trying each additional engine 
in turn, until it found one that accepted the request. The meta-engine 
itself would only raise E_SESSION when none of the configured engines 
could handle the request.

The meta-engine (if possible) could even cache the knowledge it learned 
from these trials, so that it wouldn't have to go through multiple 
trials and failures for each and every request. That sounds very 
inefficient, multiple trials and failures every single request, for 
normal, expected requests. It should have some way of knowing which 
engine is to handle a given request, and funnel that request to that 
engine. Only when an engine becomes inoperative (e.g., because a 
database has gone down) should it search for an alternative.

In particular, you probably don't want to implement this functionality 
by catching the E_SESSION exceptions at a higher level. Because its 
logic is local to the session subsystem. Keep things that work together 
close together; keep things that work separately apart.

However, if there is a compelling reason to catch exceptions at some 
other point in the Dancer framework, you could define additional 
catch-points.


DANCER EXCEPTIONS

Apps may want to throw other exception objects, not just internal Dancer 
exceptions. And it's important to keep that in mind. Because 
Dancer::Exception is of limited usefulness to many apps.

Frankly, I'm still stymied by the choice to use a power-of-2 integer as 
an exception code in Dancer::Exception. It's no more lightweight than 
using an arbitrary integer—in fact, it's slightly heavier, as seen 
below—and it restricts the usefulness of the class.

The reason to use a power-of-2 integer is if you have a small, limited 
number of defined conditions, and you want to communicate two or more of 
them simultaneously in the same value. So for example:

     use Fcntl ':mode';
     $mode = (stat($filename))[2];
     print "$filename is readable\n" if ($mode & S_IREAD);
     print "$filename is a directory\n" if ($mode & S_IFDIR);

In this example, the mode returned by stat is a bit-mask simultaneously 
communicating the state of all the different file permissions that stat 
knows about, as they pertain to the $filename being examined.

But exceptions are a horse of a different color... or maybe an animal of 
a different species. You only raise one exception at a time, and there 
can be an arbitrarily large set of possible exceptions that an app might 
throw. Looking at the latest released Dancer source, I see that 
Dancer::Exception will refuse even to register more than 16 exceptions— 
on a system with 64-bit integers. That means you're limited to a maximum 
of 16 different exception types, and of the 64 bits carried around with 
each exception object, 48 of them will always go to waste. That's 
heavier (or less useful) than using an arbitrary integer value.

While Dancer::Exception might still be useable for internal Dancer 
exceptions, it fails to provide the minimum feature-set needed for most 
application code. In general, a lightweight exception class should 
encompass a simple integer value and a string, and it should be 
subclassable for apps that need to extend it... Of course, what I've 
just described is Exception::Base, which is why I've been throwing 
Exception::Base objects in my code.


Anyhow, you asked for feedback. Hope you weren't disappointed. :)

Cheers,
-TimK

http://www.JTimothyKing.com/


On 8/8/11 11:05 AM, damien krotkine wrote:
> Hi,
>
> I'm in the midle of doing the second round coding for 
> Dancer::Exception  (it's in topic/exceptions2 on github). I have few 
> design questions, and I'd like some feedback from the community.
>
> The plan is that with Dancer::Exception :
> - 1/ an exception has a value ( combination of power of 2 integers), 
> and a message (string)
> - 2/ all exceptions raised by the Dancer core will be 
> Dancer::Exception (instead of normal die / croak)
> - 3/ a user will be able to raise exceptions in the routes
> - 4/ a user will be able to create new exception types
> - 5/ when raised, exceptions are transmitted to the templating system 
> when rendering a Dancer::Error
> - 6/ exceptions should properly stringify and numerify for backward 
> compatibility and ease of use.
> - 7/ the user should have the opportunity to catch any Dancer 
> exception and perform an appropriate action
>
> Currently, in the topic/exceptions2 branch, all items are implemented, 
> except item 7. More precisely, it's possible to catch 
> Dancer::Exception using standard eval {...}. But what if a user wants 
> to catch an exception raised from a distant route, or from Dancer Core 
> itself ?
>
> Real life example 1 : in some routes, the developer does some 'raise' 
> of exceptions (internal or custom). The developer wants to run 
> specific codes when these exception are raised. For now, he can wrap 
> his route code with eval {} and use is_dancer_exception, as stated in 
> Dancer::Exception.
>
> Real life example 2 : in some routes, the developper wants to catch 
> core exceptions that could occur.
>
> Real life example 3 : if something wrong happens in the 
> Dancer::Session::Yaml modules, it raises a E_SESSION exception. When 
> this exception occurs, it'll render a Dancer::Error, stating that thet 
> exception were a E_SESSION. However what would be cool is that you 
> could catch the exceptions, see that it's of type SESSION, and in this 
> case try a different Dancer::Session system. In this case the 
> exception is a core one.
>
> example 3 is not related to routes, so it's a bit difficult to know 
> where to put a try/catch scope.
>
> I have some ideas, but I'm not really convinced by them :
> - idea 1 :  add a new hook, that would be called 'after_exception'
> - idea 2 : when an exception is raised, it's stored as Shared Data, 
> and thus can be detected with a before_error hook
> - idea 3 : add a try_catch syntax when creating route, like :
>
> get '/foo' => sub { ... raise E_LOGIN, "bad credential" },
>        catch E_REQUEST | E_GENERIC => sub { ... }
>        catch E_LOGIN => sub { ... }
>
> Currently, Dancer::Exception is not too bad : it's simple, efficient 
> and fast. I don't want it to become a bloated system. But we need to 
> provide a bit more feature for it to be really useful.
>
> Please share your thoughts :)
>
>
> dams.
>
>
>
> _______________________________________________
> Dancer-users mailing list
> Dancer-users at perldancer.org
> http://www.backup-manager.org/cgi-bin/listinfo/dancer-users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.backup-manager.org/pipermail/dancer-users/attachments/20110811/8e7b564e/attachment-0001.htm>


More information about the Dancer-users mailing list