[Audacity-devel] Exception safety and Nyquist Lisp
A free multi-track audio editor and recorder
Brought to you by:
aosiniao
|
From: Paul L. <pau...@gm...> - 2017-08-18 13:52:34
|
Some idea jottings, mostly for James, and for future reference:
If we want more Audacity functions to be callable from Nyquist Lisp, and
these functions might throw, and we want my new error reporting to the user
to function just as elsewhere in Audacity, then what must be done?
- See what I did at commit 8e5975b10db0, but it's not satisfactory.
- Catch exceptions where Nyquist calls back into C++ land, translate
to error code.
- At the high level where Lisp gives control back to C++, detect the
error, try to reconstruct an equivalent exception, and re-throw.
- This is unsatisfactory because it doesn't generalize to all
possible exceptions. We are less free to let the callbacks into
C++ throw
other things if need be.
- Re-throw is really needed. You don't want just to absorb the
exception at a low level. You want to propagate through levels above
Nyquist so they can do their proper RAII and ultimately report
the cause of
failure to the user.
- I mention in comments that the missing thing I want to use is
std::exception_ptr.
- That's reference-counting of exception objects in the C++ runtime,
new in C++11. You can keep an exception object alive even after leaving
the catch block than handles it, and re-throw it later. Read more here:
http://en.cppreference.com/w/cpp/error/exception_ptr
- But on Mac at least I know we have not yet migrated to a version of
the library that includes it. And this is connected to the question of
abandoning Snow Leopard, which did not get reexamined this release.
- I don't know what minimal versions are needed on the other
platforms to get us exception_ptr.
- Given exception_ptr you could re-do commit 8e5975b10db0 properly and
more generally (as to possible exception types). You could do the
analogous in a few places, wrapping each foreign function, and testing for
an exception to re-throw at each place where Lisp runtime returns to C++.
- All this would propagate C++ exceptions, while preventing C++ stack
unwinding within the levels of stack written in C that implement the Lisp
interpreter.
But Lisp also has its exception handling -- you knew that, right?
- Relevant forms are throw, catch, and unwind-protect, the latter
allowing you to use the equivalent of the finally template function that is
much used in our C++ now.
- You can examine the code implementing the Lisp interpreter -- I read
it all a few years ago, and forgot much -- and you will see that setjmp and
longjmp implement it.
- If what is thrown with Lisp throw is not caught with Lisp catch, then
a top-level setjmp in the Lisp runtime stops it.
So: beyond the use of exception_ptr, do we want gateways between Lisp and
C++ that translate exceptions? These ideas would require a little bit of
hacking the Lisp runtime, but I am sure we could figure it out.
- Where the foreign function wrapper catches and saves exception_ptr, it
would also find the current jmp_buf in the Lisp runtime and longjmp it.
- Levels of Lisp code could then visit their unwind_protect handlers in
response to a C++ exception, so that Lisp code could write RAII too.
- We must be sure the implementation of Lisp catch (which examines the
Lisp symbol associated with a Lisp throw) will never match a C++ exception
and always pass control up the chain to the next jmp_buf.
- Where Lisp implements its top-level setjmp of last resort (in C), it
could return a code to caller indicating whether there was an unhandled
exception, both in case this is a Lisp exception, or it is a C++ exception.
- The C++ layer that called Lisp can detect this condition, and either
rethrow the exception_ptr, or, finding it null, could construct a new C++
exception object representing the Lisp exception, details of which we might
also have saved in the Lisp code, using some foreign function.
PRL
|