[Rcpp-devel] RCPP_USE_UNWIND_PROTECT by default

Iñaki Ucar iucar at fedoraproject.org
Mon Feb 7 11:31:00 CET 2022


Thanks all. My summary here differs a bit though:

1. The point that a longjump does not necessarily represent an error
is crucial here. That's the rationale behind Lionel's implementation
of this exception. My understanding then is that client packages
should *never ever* catch it, and thus it shouldn't inherit from
std::exception.

2. UnwindProtect is a mechanism introduced in R back in 2017 for fast
and safe C++ stack unwinding, so I think it's time we stick to it.
When enabled, Rcpp leverages this via Rcpp_fast_eval. Before that,
Rcpp_eval was/is... kind of hackish (no offense intended; it was the
best thing to do before R properly supported C++ via UnwindProtect),
because it relied on tryCatch, which sometimes resulted in several
tryCatch stacked together. Rcpp_fast_eval is called in a number of
places in Rcpp, and thus client packages would greatly benefit from
finally switching to UnwindProtect, including V8.

3. But then, V8 *really needs* to catch R errors in the JS virtual
machine. So the solution... is to add *a* tryCatch in the proper place
(not a matrioska of tryCatches, as it happens without UnwindProtect).
And this is exactly what Jeroen did [1]. I think that the solution is
brilliant and the proper way to go here (as it happens, that tryCatch
was in place before, for other reasons I suppose, and now it has been
repurposed to properly catch errors too).

4. Another way to address this was mentioned by Lionel: Rcpp_eval is
still there. Arguably though, this interface is not very
user-friendly, and Rcpp::Function is much nicer. I don't think that
Rcpp::Function should switch to Rcpp_eval, but we could offer some
kind of Rcpp::TryCatchFunction (ok, the name can be improved) that
uses Rcpp_eval, or some kind of wrapper class that forces
Rcpp::Function to use Rcpp_eval instead.

[1] https://github.com/jeroen/V8/commit/3258ce61dd02eaed51edef5391dc5afcd456d16c

Iñaki


On Mon, 7 Feb 2022 at 03:11, Kevin Ushey <kevinushey at gmail.com> wrote:
>
> Thanks, I think I understand the issue better now. Ultimately, it comes down to the fact that UnwindProtect doesn't really allow handling of R conditions in the "regular" way as e.g. tryCatch() does; instead, it gives us just enough to properly unwind the C++ stack when an R error occurs. If I can summarize:
>
> 1. Client packages can catch the Rcpp LongjumpException and handle it themselves if they need to, but it's not obvious how to do this correctly.
> 2. R error messages are printed by R when the error is emitted.
> 3. It's not clear to the LongJumpException handler what R error triggered the longjmp exception.
>
> In particular, for (1), Rcpp is normally responsible for unprotecting its unwind token, and then continuing the unwind:
>
> https://github.com/RcppCore/Rcpp/blob/1c8a1946db56e4d8041593906eddcb0da8ba07a0/inst/include/Rcpp/exceptions.h#L148-L157
>
> If we want to make it safe for client packages to catch these exceptions, we'd need to provide a helper for cleaning up Rcpp's unwind state, or make that happen automatically. (Maybe the token's protection status could be managed as part of the exception's lifetime? Not sure.) However, the fact that the LongjumpException needs special handling implies that it probably shouldn't inherit from std::exception.
>
> For (2), I don't see a clean way of handling this beyond setting the 'show.error.messages' option to false. Unfortunately, because UnwindProtect is not a "real" R condition handler, it gets processed only after the "regular" R error machinery runs (which includes printing the error before actually jumping).
>
> I'm similarly not aware of any solution for (3).
>
> In other words, my understanding is that if you're a client of Rcpp and you want to be able to catch and handle R errors at the C++ level, UnwindProtect() isn't the right tool -- you'll want a proper tryCatch() handler.
>
> It does also imply (as had been shown) that switching to unwind-protect would be a behavior change in Rcpp, which may not be ideal.
>
> Thanks,
> Kevin
>
>
>
>
> On Sun, Feb 6, 2022 at 9:39 AM Jeroen Ooms <jeroenooms at gmail.com> wrote:
>>
>> On Sun, Feb 6, 2022 at 6:25 PM Kevin Ushey <kevinushey at gmail.com> wrote:
>> >
>> > If I tweak your example package code so that all exceptions are caught via a catch (...) {} block, I can see something like:
>> >
>> > > uptest::uptest()
>> > Error in (function ()  : Ouch from R
>> > Caught some other exception.
>> >
>> > The fact that we're still printing the R error message seems undesirable, but it looks like the exception can be caught -- just not via std::exception.
>>
>> Yes that is also what Iñaki and me alluded to above. But I think this
>> way, what we are catching is not the R error itself (which seems lost
>> after it was printed), but rather a token that tells the application
>> we should let Rcpp unwind all the way back to the R console. Which is
>> what happens if we don't catch it, then it makes sense. But if we _do_
>> want to handle the situation in C++, this doesn't give us much to work
>> with.
>
> _______________________________________________
> Rcpp-devel mailing list
> Rcpp-devel at lists.r-forge.r-project.org
> https://lists.r-forge.r-project.org/cgi-bin/mailman/listinfo/rcpp-devel



-- 
Iñaki Úcar


More information about the Rcpp-devel mailing list