The program performs an illegal operation and cannot continue it’s current course of execution. It packs up the relevant information–location, backtrace, type, an error code, an error message, severity, and other details–and throws it as an exception. Parrot’s concurrency scheduler scans through the list of installed exception handlers but cannot find a suitable candidate to handle it. Crap. Panic.
In this situation libparrot calls the routine die_from_exception
, the
action of last resort to stop the actions of the interpreter and pass control
back to the embedding application. The application needs all the necessary
information from that exception object so it can optionally display it to the
user or use it to decide upon a new course of action for itself.
It’s worth asking ourselves what state the interpreter is in immediately
following an unhandled exception. Currently, the interpreter calls the
function I mentined above, die_from_exception
. This function prints out a
bunch of information to stderr
: The text of the error message from the
Exception object and a backtrace from the location where the exception was
thrown. After printing out this information and a little bookkeeping, it calls
Parrot_exit
to run any exit handler routines and finally call the libc
exit()
function to close the program.
That’s right, libparrot calls exit()
, it never passes control back to the
embedding application.
There are several things wrong here, and I’ll discuss them all in a bit. It’s
most important right now to talk about the state of the interpreter at this
point. Once we call Parrot_exit
, the exit handlers have fired and been
freed. Parrot_exit
also shuts down the GC, so we can no longer reliably
create new PMCs or STRINGS. If we attemp to re-enter the interpreter it
won’t have an active GC, which will cause memory use to explode.
Once we call Parrot_exit
, at least as it exists in trunk, we are
completely done with the interpreter and we cannot reliably create or use any
PMC or STRING objects, or call any other Parrot subroutines or methods again.
Since control flow never passes back to the embedding application, we can’t
do anything else, either. Even if we did jump back to the embedding
application, we couldn’t use PMCs or STRINGs to communicate error information
back to it because the interpreter is dead. This is all extremely bad. So what
do we do instead?
First and foremost, libparrot should rarely–if ever–print anything to
stderr
directly. This, if it happens at all, is the option of last resort.
Output formatting and the output vector should be the sole
providence of the embedding application. The first change we need to make is
to take all the necessary textual information and either package it up in a
form that the embedding application can use, or flip an error flag and provide
a series of API calls that the embedding API can use to get the information it
needs. A PMC seems like the natural choice for this, since our Exception
objects would already contain all the information we need: The location
information and the ability to
generate a backtrace, the type and severity information, the human-readable
message string, etc. So, it seems like this is what we really want to use. A
second-place offering would be something like a StringBuilder PMC, which we
could use to construct all the output as we would currently show on stderr,
but instead prepare it and dump it into a STRING for use by the embedding
application. It’s not ideal to package the backtrace up with the message, but
if that were our only option it’s what we would have to do.
I don’t think it’s what we have to do.
The reality is that the Parrot interpreter can probably be reused after
having exited so long as we don’t finalize the GC and we don’t run all the
exit handlers. Basically, so long as we don’t call Parrot_exit
(at least
as it is implemented in master now) we can call back into the interpreter
later. We need to create a new function, probably Parrot_jump_out
or
something, and use that most of the time when we are currently calling
Parrot_exit
. Then, we only call Parrot_exit
when we are actually closing
the interpreter down for good, probably during (or immediately after) the
call to Parrot_destroy
(which kills the interp and frees most of it’s
resources).
Once we have the jump-out semantics working correctly, we will be able to get out of the realm of the interpreter without destroying/disabling the GC, which means we can pass the raw Exception objects out to the embedding application for error diagnosis and handling. There is a lot to be excited about in this.
Tonight I updated the embed_api branch to Parrot master. There were a few conflicts and messes, but it was mostly painless. bluescreen is working to implement some new features and start implementing some tests for the new API. If things keep moving at this pace I think we are mergable within a week.