[perl #57610] [PATCH] Resumable exceptions
On Tue Aug 05 04:09:14 2008, tene wrote: pdd23: Exception handlers can resume execution immediately after the throw opcode by invoking the resume continuation which is stored in the exception object. That continuation must be invoked with no parameters; in other words, throw never returns a value. Exception.pmc has the following attributes: ATTR INTVALid; /* The task ID in the scheduler. */ ATTR FLOATVAL birthtime;/* The creation time stamp of the exception. */ ATTR STRING *message; /* The exception message. */ ATTR PMC *payload; /* The payload for the exception. */ ATTR INTVALseverity; /* The severity of the exception. */ ATTR INTVALtype; /* The type of the exception. */ ATTR INTVALexit_code;/* The exit code of the exception. */ ATTR PMC *stacktrace; /* The stacktrace of an exception. */ ATTR INTVALhandled; /* Whether the exception has been handled. */ ATTR PMC *handler_iter; /* An iterator of handlers (for rethrow). */ ATTR Parrot_Context *handler_ctx; /* A stored context for handler iterator. */ None of these is a continuation. The throw opcode passes the address of the next opcode to Parrot_ex_throw_from_op, but Petfo only uses it in: address= VTABLE_invoke(interp, handler, dest); and the ExceptionHandler PMC's invoke() does not use that parameter at all. This first draft of a patch adds an attribute to the exception pmc to hold a return continuation, creates a retcontinuation pmc in the throw opcode and assigns it to that attribute, and patches new_ret_continuation to initialize the new continuation's from_ctx attribute in the same way new_continuation does. This last item is there to fix a segfault. I don't understand parrot's continuations well enough yet to have any idea why they were different, so I just guessed. I don't know if it's wrong, but it doesn't seem to fail any extra tests. Added a simple test case. It looks like Allison applied your patch in r30123 but forgot to close this ticket. Now seems like a good time to resolve it. Thanks for the patch.
Re: Resumable exceptions
Not long ago, Patrick R. Michaud proclaimed... Here's a simple test for resumable exceptions that I'm trying to get to work. I'm probably coding/understanding something wrong, so any suggestions or pointers would be greatly appreciated. .sub main :main push_eh catcher 'foo'() pop_eh say 'ok 4' .return () catcher: .get_results ($P0, $S0) $P1 = $P0['retcont'] $P1() .end .sub 'foo' say 'ok 1' $P0 = new 'Exception' throw $P0 say 'ok 2' $P0 = new 'Exception' throw $P0 say 'ok 3' .end What I'm trying to do is to test the ability to resume after exceptions thrown by Cfoo. The Cmain sub above sets up a handler to catch exceptions, then calls Cfoo. The handler simply resumes any exception that is caught. The Cfoo sub prints 'ok 1', throws an exception, prints 'ok 2', throws another exception, and prints 'ok 3'. I can resume the first exception but not the second: $ ./parrot x.pir ok 1 ok 2 No exception handler and no message current instr.: 'foo' pc 46 (x.pir:20) called from Sub 'main' pc 29 (x.pir:10) $ Suggestions and corrections to my code welcomed. Check RT #58170 for this. Let me know if you need more detail on what's happening.
Resumable exceptions
Here's a simple test for resumable exceptions that I'm trying to get to work. I'm probably coding/understanding something wrong, so any suggestions or pointers would be greatly appreciated. .sub main :main push_eh catcher 'foo'() pop_eh say 'ok 4' .return () catcher: .get_results ($P0, $S0) $P1 = $P0['retcont'] $P1() .end .sub 'foo' say 'ok 1' $P0 = new 'Exception' throw $P0 say 'ok 2' $P0 = new 'Exception' throw $P0 say 'ok 3' .end What I'm trying to do is to test the ability to resume after exceptions thrown by Cfoo. The Cmain sub above sets up a handler to catch exceptions, then calls Cfoo. The handler simply resumes any exception that is caught. The Cfoo sub prints 'ok 1', throws an exception, prints 'ok 2', throws another exception, and prints 'ok 3'. I can resume the first exception but not the second: $ ./parrot x.pir ok 1 ok 2 No exception handler and no message current instr.: 'foo' pc 46 (x.pir:20) called from Sub 'main' pc 29 (x.pir:10) $ Suggestions and corrections to my code welcomed. Pm
Re: Resumable exceptions
Patrick R. Michaud wrote: What I'm trying to do is to test the ability to resume after exceptions thrown by Cfoo. The Cmain sub above sets up a handler to catch exceptions, then calls Cfoo. The handler simply resumes any exception that is caught. The Cfoo sub prints 'ok 1', throws an exception, prints 'ok 2', throws another exception, and prints 'ok 3'. I can resume the first exception but not the second: $ ./parrot x.pir ok 1 ok 2 No exception handler and no message current instr.: 'foo' pc 46 (x.pir:20) called from Sub 'main' pc 29 (x.pir:10) $ Suggestions and corrections to my code welcomed. The old exception system deleted a handler as soon as it was retrieved for an exception. For backward-compatibility, the new exception system replicates that mis-feature. The problem is, if you end up throwing an exception in the middle of handling another one, and didn't mark the handler somehow, you can easily get an infinite loop of thrown exceptions (when the handler gets some data it didn't expect from the second exception, and so throws another exception). In an ideal world: a) every exception handler would first check the type of the exception it was passed, and rethrow any exceptions it can't handle. b) exception handlers would only be deleted by 'pop_eh', and otherwise would simply pass out of scope after leaving the context where they were defined. Since (a) has to be in place before (b) can work, it was delayed. (And I just changed the name of the 'retcont' attribute to 'resume', to make it a little clearer.) Allison
[perl #57610] [PATCH] Resumable exceptions
, CONST_STRING(INTERP, retcont)) == 0) { +GET_ATTR_retcont(interp, SELF, value); +} else if (string_equal(INTERP, name, CONST_STRING(INTERP, stacktrace)) == 0) { GET_ATTR_stacktrace(interp, SELF, value); } @@ -579,6 +586,9 @@ Set an attribute value for the exception object. else if (string_equal(INTERP, name, CONST_STRING(INTERP, payload)) == 0) { SET_ATTR_payload(interp, SELF, value); } +else if (string_equal(INTERP, name, CONST_STRING(INTERP, retcont)) == 0) { +SET_ATTR_retcont(interp, SELF, value); +} else if (string_equal(INTERP, name, CONST_STRING(INTERP, stacktrace)) == 0) { SET_ATTR_stacktrace(interp, SELF, value); } diff --git a/src/sub.c b/src/sub.c index bcc7bdb..529ddc0 100644 --- a/src/sub.c +++ b/src/sub.c @@ -191,7 +191,7 @@ new_ret_continuation(PARROT_INTERP) Parrot_cont * const cc = mem_allocate_typed(Parrot_cont); cc-to_ctx = CONTEXT(interp); -cc-from_ctx= NULL;/* filled in during a call */ +cc-from_ctx= CONTEXT(interp);/* filled in during a call */ cc-dynamic_state = NULL; cc-runloop_id = 0; cc-seg = interp-code; diff --git a/t/op/exceptions.t b/t/op/exceptions.t index e49f647..1c4aa1f 100644 --- a/t/op/exceptions.t +++ b/t/op/exceptions.t @@ -6,7 +6,7 @@ use strict; use warnings; use lib qw( . lib ../lib ../../lib ); use Test::More; -use Parrot::Test tests = 29; +use Parrot::Test tests = 30; =head1 NAME @@ -608,6 +608,29 @@ Exception message: Class Foo already registered! after compile OUTPUT +pir_output_is( 'CODE', 'OUTPUT', Resumable exceptions ); +.sub main :main +push_eh _handler +new $P1, 'Exception' +say 'Before throwing' +throw $P1 +say 'After throwing' +end +_handler: +.local pmc e +.local string s +.local pmc c +.get_results (e, s) +say 'In the exception handler' +c = e['retcont'] +c() +.end +CODE +Before throwing +In the exception handler +After throwing +OUTPUT + # Local Variables: # mode: cperl # cperl-indent-level: 4 -- 1.5.5.1
resumable exceptions and LEAVE/KEEP/UNDO blocks
What happens if a resumable exception is propagated through a block with a LEAVE, KEEP, or UNDO block? S04 seems to be a bit vague on this point. It strikes me that what we want it to do is not execute them when the exception is propagated, because we don't know whether it's going to be resumed or not. If the exception is resumed by its handler, then that's fine, as we can call the blocks at the usual time (when the blocks they are attached to exit). If the exception is caught and handled and not resumed, that would leave us with having to find all the LEAVE c. blocks and call them in the right order when the exception handler exits. In either case, it looks like you have a problem. LEAVE c. blocks will often be used for things like database transactions, where we want to ensure that some lock obtained on entering the block is released promptly regardless of how the control flow jumps about. In such a context, throwing a resumable exception that skips the LEAVE block, farts about doing some potentially long computation in a higher-up scope, and only calls the LEAVE block to release the lock at some later date, seems to be far from the best choice. Sure, we can warn programmers to make their resumable-exception handlers short, or to only throw non-resumable exceptions from blocks that are likely to be called in such circumstances. I suppose that would be an acceptable resolution, but it has an aura of non--re-entrant signal handlers about it, so it seems like the sort of thing I would like to avoid if anyone is clever enough to think of something else to do. BTW, if one is handling a resumable exception, how does one resume it? I couldn't find anything explaining how. Having a .resume method (or some cutesier name) on the Resumable role would seem to make sense. -- Customer Waiter, waiter! There's a fly in my soup! Waiter That's not a bug, it's a feature. http://surreal.istic.org/ The Answer of the Oracle Is Always Death. signature.asc Description: Digital signature
Re: resumable exceptions and LEAVE/KEEP/UNDO blocks
On Mon, Mar 05, 2007 at 01:06:46PM +, Daniel Hulme wrote: : What happens if a resumable exception is propagated through a block with : a LEAVE, KEEP, or UNDO block? S04 seems to be a bit vague on this point. : It strikes me that what we want it to do is not execute them when the : exception is propagated, because we don't know whether it's going to be : resumed or not. If the exception is resumed by its handler, then that's : fine, as we can call the blocks at the usual time (when the blocks they : are attached to exit). If the exception is caught and handled and not : resumed, that would leave us with having to find all the LEAVE c. : blocks and call them in the right order when the exception handler : exits. : : In either case, it looks like you have a problem. LEAVE c. blocks will : often be used for things like database transactions, where we want to : ensure that some lock obtained on entering the block is released : promptly regardless of how the control flow jumps about. In such a : context, throwing a resumable exception that skips the LEAVE block, : farts about doing some potentially long computation in a higher-up : scope, and only calls the LEAVE block to release the lock at some later : date, seems to be far from the best choice. Sure, we can warn : programmers to make their resumable-exception handlers short, or to only : throw non-resumable exceptions from blocks that are likely to be called : in such circumstances. I suppose that would be an acceptable resolution, : but it has an aura of non--re-entrant signal handlers about it, so it : seems like the sort of thing I would like to avoid if anyone is clever : enough to think of something else to do. I don't see a problem here. I think you maybe missed the bit that says: A CCATCH block sees the lexical scope in which it was defined, but its caller is the dynamic location that threw the exception. That is, the stack is not unwound until some exception handler chooses to unwind it by handling the exception in question. Exiting blocks are not run until the decision is made to unwind the stack, which is *after* the exception handlers are run. So all the exception trampoline has to do to resume is just return; the resume continuation doesn't really have to be a real continuation in this case. : BTW, if one is handling a resumable exception, how does one resume it? I : couldn't find anything explaining how. Having a .resume method (or some : cutesier name) on the Resumable role would seem to make sense. To resume a resumable exception the correct thing to do is very likely nothing. The outermost warning handler is what generally resumes otherwise uncaught resumables. If you catch a warning, it defaults to resuming when handled unless you rethrow it as fatal. Larry
Re: resumable exceptions and LEAVE/KEEP/UNDO blocks
On Mon, Mar 05, 2007 at 09:01:13AM -0800, Larry Wall wrote: I don't see a problem here. I think you maybe missed the bit that says: A CCATCH block sees the lexical scope in which it was defined, but its caller is the dynamic location that threw the exception. That is, the stack is not unwound until some exception handler chooses to unwind it by handling the exception in question. Yes, I did. I was grepping specifically for the bit on resumable exceptions and the quoted bit is 80 lines up so I missed it completely. Thanks for pointing me at it. [...] To resume a resumable exception the correct thing to do is very likely nothing. The outermost warning handler is what generally resumes otherwise uncaught resumables. If you catch a warning, it defaults to resuming when handled unless you rethrow it as fatal. OK, that makes sense. The reason that came up was because on Friday I had a good idea for a language feature that would have made a task I had been doing that day much easier. When I checked the spec, though, I found out it was already in. This is happening increasingly often, which should be reassuring to all concerned. -- Listen to your users, but ignore what they say. - Nathaniel Borenstein http://surreal.istic.org/ Calm down, it's only ones and zeroes. signature.asc Description: Digital signature
Re: [unclassified] Re: resumable exceptions
I was involved in the C++ standardization process, and argued for resumption as opposed to termination only in exceptions. I was somewhat of a pioneer, implementing C++ exceptions for my team to use before commercial compilers had them. After all, why start a new project with an old paradigm? Anyway, as passionate as I was about resumption, or at least making it not impossible to implement resumption, at the next ANSI meeting the terminate-only camp made compelling arguments. I don't remember what the killer argument was. But I do remember bits and pieces: people with real-world experience on systems that have resumable exceptions in some form ended up never using them; it complicates the implementation; it is not necessary since callbacks fill that role already (e.g. new_handler instead of resuming from the bad_alloc exception); and the semantic concepts of what an exception means. To elaborate, the code throws when it can't deal with the situation. What does it mean if the throw worked like a function call and returned? Code would have to watch out for that, which defeats the point of making the exception a clean get-away. Really, after fixing something the function has to start over at the top; e.g. try allocating again... that's what a loop around the whole try/catch does, and is today common practice! So on providing different places to jump to depending on whether the exception is fixed or not fixed, how is that different from a normal catch block which can re-throw or drop out? Consider the error upon trying to open a file. If the open command contains a callback handler as part of itself, it can try again and eventually return, transparantly to the caller who just sees that the file was (eventually) opened. But what if the caller needs to back up and repeat some steps because of that failure, to get another running start at the command so to speak? That's what you can do with a mechanism more elaborate than the callback built into the command. But you can do that with a try/catch now, having termination semantics, already. Besides syntactic sugar, how is resuming different from that? --John Larry Wall wrote: On Wed, Jun 14, 2006 at 08:59:02PM -0700, Chip Salzenberg wrote: : Are Parrot exceptions now, in fact, resumable? If they are, is that : important? Is anyone actually resuming execution after exception handlers : are called? I think we _can_ keep resumability, but I'm not sure I want us : to, and I definitely don't want to bother if no one wants it. The current thought for Perl 6 is that warnings are essentially just resumable control exceptions that by default are caught only by the outermost exception handler, which by default resumes them. But any exception handler in the dynamic scope may then catch one and turn it fatal. This gives us dynamic as well as lexical control of warnings without inventing a mechanism separate from existing control exceptions. On the other hand, I think we've also said that exceptions are resumable only if the thrower includes as part of the exception object a continuation to resume at, which presumably warn() does. So maybe you don't need to do anything special to make exceptions resumable for Perl 6, assuming throwing the exception doesn't clobber the continuation somehow. On the gripping hand, it looks like this is missing from the specs... Larry
Re: [unclassified] Re: resumable exceptions
On Fri, Jun 16, 2006 at 10:51:24PM -0500, John M. Dlugosz wrote: : Anyway, as passionate as I was about resumption, or at least making it : not impossible to implement resumption, at the next ANSI meeting the : terminate-only camp made compelling arguments. Well, interestingly, I used to be in the terminate-only camp. : I don't remember what the killer argument was. But I do remember bits : and pieces: people with real-world experience on systems that have : resumable exceptions in some form ended up never using them; it : complicates the implementation; it is not necessary since callbacks fill : that role already (e.g. new_handler instead of resuming from the : bad_alloc exception); and the semantic concepts of what an exception : means. To elaborate, the code throws when it can't deal with the : situation. What does it mean if the throw worked like a function call : and returned? In general, it would simply mean that you called warn() instead of fail(). If you call warn() you're expecting to gamely attempt to continue. If you call fail(), you're not expecting to return. Warning is all about expressing misgivings when you're not sure. If we throw a warning like we throw an exception, then someone in the dynamic context may well be more sure than we are whether the warning should be suppressed or fatalized. : Code would have to watch out for that, which defeats the : point of making the exception a clean get-away. Really, after fixing : something the function has to start over at the top; e.g. try allocating : again... that's what a loop around the whole try/catch does, and is : today common practice! : : So on providing different places to jump to depending on whether the : exception is fixed or not fixed, how is that different from a normal : catch block which can re-throw or drop out? It doesn't give the code in question the option of raising objections without giving up. How would you like it if the only way you could complain to your management was by turning in a letter of resignation and hoping they'll rehire you immediately? Instead, we use office memos/email that can convey either warnings or resignations. : Consider the error upon trying to open a file. If the open command : contains a callback handler as part of itself, it can try again and : eventually return, transparantly to the caller who just sees that the : file was (eventually) opened. But what if the caller needs to back up : and repeat some steps because of that failure, to get another running : start at the command so to speak? That's what you can do with a : mechanism more elaborate than the callback built into the command. But : you can do that with a try/catch now, having termination semantics, : already. Besides syntactic sugar, how is resuming different from that? Sure, and that's what fatal exceptions are good for. If you are giving up, throwing a terminate-only exception is exactly what you want to do. But look at this from the other direction--how do we unify warnings with exceptions such that people aren't forced to invent out-of-band warning systems that duplicate most of what throwing exceptions already does? In a sense, the whole idea of resumably exception is looking at it sideways from what you really want. An exception is just some bad news that you're trying to pass up the dynamic stack to get more direction on. Even if you don't define resumably exceptions at all, in a language where continuations are first class objects, the *throwing* code can install one of those into any exception that can hold one, and bang, you've got resumable exceptions whether you want them or not. The point you made earlier is one that I agree with, which is that the *user* interface has to be clear at the point of the throw. You don't want to define your interface so that fail() ever returns. It's like a return: it documents that the following statement is not reached. But we can treat warn() as a variant that does document the fact that it installs a resume continuation, and then attempts to throw a kind of exception that will be resumed by default. We could go as far as to have another variant, despair() or some such, that throws a resumable exception that is not expected to resume by default, but someone in the dynamic scope might call our resume continuation anyway. So that function call would document that we *probably* won't resume here but don't count on it. If we do resume, we'll still try to carry on somehow. Note also that this cleans up the local $SIG{__WARN__} mess of Perl 5. Perl 5 compartmentalized warnings from exceptions, but we're just not gonna do that anymore. It makes much more sense to unify them, I think. The latest version of S04 documents how all this is expected to work, in particular the notion that warnings are just considered control exceptions so that they bypass most CATCH blocks but can be managed via CONTROL blocks. Larry
Re: resumable exceptions
On Wed, Jun 14, 2006 at 08:59:02PM -0700, Chip Salzenberg wrote: : Are Parrot exceptions now, in fact, resumable? If they are, is that : important? Is anyone actually resuming execution after exception handlers : are called? I think we _can_ keep resumability, but I'm not sure I want us : to, and I definitely don't want to bother if no one wants it. The current thought for Perl 6 is that warnings are essentially just resumable control exceptions that by default are caught only by the outermost exception handler, which by default resumes them. But any exception handler in the dynamic scope may then catch one and turn it fatal. This gives us dynamic as well as lexical control of warnings without inventing a mechanism separate from existing control exceptions. On the other hand, I think we've also said that exceptions are resumable only if the thrower includes as part of the exception object a continuation to resume at, which presumably warn() does. So maybe you don't need to do anything special to make exceptions resumable for Perl 6, assuming throwing the exception doesn't clobber the continuation somehow. On the gripping hand, it looks like this is missing from the specs... Larry
resumable exceptions
Are Parrot exceptions now, in fact, resumable? If they are, is that important? Is anyone actually resuming execution after exception handlers are called? I think we _can_ keep resumability, but I'm not sure I want us to, and I definitely don't want to bother if no one wants it. -- Chip Salzenberg [EMAIL PROTECTED]