-
-
Notifications
You must be signed in to change notification settings - Fork 213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rcpp exception types are not correctly recognized when thrown across translation units #972
Comments
Hmpf. Patches welcome. Smells a litle like Tomas Kalibera telling us not to Any ideas as to how do make this more solid? |
I think the problem is that different 'versions' of the various Rcpp objects are getting compiled into the different shared objects generated by Here's what I see with some debug logging. Note that there's some complications from all the nested try-catch C++ contexts generated by attributes, as well as the R-level errors getting thrown around...
So the A similar story for the Rcpp interrupt exception:
... but I'm not sure why the interrupt exception gets properly caught this time. |
This seems relevant, from the GCC manual:
Tentative list of potential issues:
|
Now that I've done some more exploration, I think that my example might not be a good test of what I had described. This is because the functions are wrapped in R code that calls Rcpp code, and, as Kevin noted, the exceptions are caught by the Rcpp code that wraps the thrower functions, converted to R errors or interrupts, and then Rcpp converts those to C++ exceptions in the catcher function. So in short, the object that's thrown is not the same object that's caught. The example in https://github.com/jcheng5/exceptiontest is a better test, since the catcher function calls the thrower function's C++ code directly, without all the wrapping and unwrapping. Another observation: I think that in the vast majority of cases, a C++ function in one package won't directly call a C++ function (which throws a Rcpp exception) in another package. If C++ code in one package calls C++ code in another package, it's much more common for the callee to be wrapped in an R function, and so the caller will go through R, and the exception wrapping and unwrapping that I've described. So in practice, this issue may be a very rare one. |
R itself has a |
I'm fairly sure R dynamically links to the runtime by default, and that's true in all the cases we're testing here. (Although I guess that's not true on Windows?) I think the problem is that different versions of Rcpp objects are ending up in different translations units (and so the compiled shared libraries generated by |
Maybe it is an unreasonable assumption? The foreign function interface is in C and |
I just tried with a locally compiled R. Having R, I did not add
|
On macOS, I still see this behavior when using the CRAN binary of R, regardless of whether I compile with the system toolchain or the LLVM toolchain. I also see it with a locally-compiled version of R, even with R and all packages built from sources with the same toolchain. So (at least on macOS) it seems there could be something deeper going on. I still think the root cause of the issue is that different 'versions' of these exception objects are entering each shared library, and those versions aren't considered compatible for some reason. As an aside, I also see this behavior on Linux, when using the CRAN binary of R and the default gcc compiler (ie: in theory, all the same toolchain). But I haven't tried compiling R myself there yet. https://learning.oreilly.com/library/view/c-coding-standards/0321113586/ch63.html seems to suggest what I think we probably all agree on: throwing exceptions across modules isn't a good idea... |
Ah when I got a success it was with everything compiled with gcc. Maybe clang is the issue? Edit: And probably small differences in the gcc toolchain, as suggests your Linux experiment. In any case it seems this pattern should simply be avoided. |
From Kevin's link (C++ Coding Standards by Sutter and Alexandrescu, item 62):
If I understand it correctly, then it is not possible to be 100% sure that it is safe to use the C++ exception system for cases like this. Also from that chapter:
|
I encountered a similar error with Stan (which often loads a DLL with a C++ function compiled by
or you may need to specify additionally
or perhaps
I don't have a Mac and am not getting the unexpected behavior on Linux, so can someone else confirm? |
I think we all agree -- did you see what I put in the Rcpp FAQ vignette a few days about this? ## Can we use exceptions and stop() across shared libraries?
Within limits, yes. Code that is generated via Rcpp Attributes (see
\citet{CRAN:Rcpp:Attributes} and Section~\ref{using-attributes}) generally
handles this correctly and gracefully via the `try-catch` layer it adds
shielding the exception from propagating to another, separate dynamic
library.
However, this mechanism relies on dynamic linking with the (system library)
`libgcc` providing the C++ standard library (as well as on using the same C++
standard library across all compiled components). But this library is linked
statically on Windows putting a limitation on the use of `stop()` from within
Rcpp Modules \citep{CRAN:Rcpp:Modules}. Some more background on the linking
requirement is [in this SO
question](https://stackoverflow.com/questions/2424836/exceptions-are-not-caught-in-gcc-program). |
I had not seen that until now, but I agree. |
I think also ensuring that |
I'm not sure if this is an instance of the same core issue, but I'm seeing the same behavior in one of my packages, even though the exception is thrown and catched by the code in my shared library:
I have an R-side test that checks whether the thrown exception's message is the expected one, which fails some times because only the generic "c++ exception (unknown reason)" is thrown. What's more puzzling is that this is only failing on the following CRAN systems:
Debian clang devel works fine, and the OSX build on Travis doesn't show the issue either. I can of course adjust the test to ignore the message, but that still means the user will only get the generic one if a problem occurs. EDIT: turns out it's not only a problem with exception types, a segmentation fault actually occurs in the aforementioned CRAN systems ( |
It would be nicer if all this worked, but alas. You documented this well, but this is to me a restatement of what was already said aboce. Eg a quote (from a C++ authority)/
and
|
* This works around the fact that, in certain circumstances, we cannot rely on errors propagating with proper `std::invalid_argument` class / error message. See also RcppCore/Rcpp#972
I found the a very similar if not identical issue. See this SO question. In some cases, I also get a The output from Valgrind though seems to suggest that the error happens quite earlier. Here is the example. Take the following C++ file: // openmp-exception-issue.cpp
#include <exception>
#include <stdexcept>
#include <Rcpp.h>
// [[Rcpp::export()]]
double that_cpp_func(int const n_it){
std::exception_ptr Ptr = nullptr;
bool is_set = false;
double out(0.);
for(int i = 0; i < n_it; ++i)
try
{
if(i > -1)
throw std::runtime_error("boh :(");
out += i;
}
catch (...)
{
if(!is_set){
Ptr = std::current_exception();
is_set = true;
}
}
if(Ptr)
std::rethrow_exception(Ptr);
return out;
} Setup R on Docker with Fedora as described in the SO question and run (you likely need to change the path): /root/R-devel/bin/R -d valgrind -e "Rcpp::sourceCpp('/sdir/openmp-exception-issue.cpp'); that_cpp_func(100)" The output can be found here. The first error shows that there are invalid reads at the end of the catch block which is from memory which is free'd at |
I saw your (by now two) SO questions, and this here is likely the relevant context. It is not clear to me that what you desire can be done, but I also have not really dig into the particulars of your questions. But as first sight it appears to be neither a new nor solved problem. (Edit: Missed a 'not') |
Thank you for the quick answer.
This is also my conclusion.
I think an important point is that using |
I am not sure we can generalize to that conclusion. Don't all your example involve MPI and added complications? Exceptions work in general and can be caught, trouble arises (really simplified summary) due to a mix of C and C++ differences, R's setup and use of different compilation units. |
There is no parallel computation in the example I provide here yet the issue persists on the setup? Yes, I do see your point that it works in a lot settings! But it will fail it seems on CRAN's r-devel-linux-x86_64-fedora-clang regardless of whether any parallel computation is done (in some maybe all cases?). This is a big deal for any R package. |
That it does work or fail on a given system is also known; some of this behaviour is AFAIK listed as "implementation dependent". Which is what you see here: works in some places, but not others. Sadly success in one place cannot be interpolated to all others. Exceptions are useful, and difficult to get right at scale. Some "guidelines" (including this one) just outlaw them completely. |
Note the usage of libstdc++: this most likely implies that you're somehow getting both libc++ and libstdc++ mixed into the same process. |
I see, thank you! I wonder if it is the same issue I am having on CRAN's r-devel-linux-x86_64-fedora-clang in my mdgc package (version 0.1.2). CRAN note that
which is what I am doing in the example. One of many errors I have gotten with my mdgc package only on CRAN's r-devel-linux-x86_64-fedora-clang is:
which is consistent with the Valgrind output. |
This issue is stale (365 days without activity) and will be closed in 31 days unless new activity is seen. Please feel free to re-open it is still a concern, possibly with additional data. |
If there are Rcpp exceptions that are thrown from one translation unit and caught in another, they are sometimes not recognized with the correct type.
In this example, there are two functions that throw different kinds of exceptions,
Rcpp::exception
andRcpp::internal::InterruptedException
, and another function that catches exceptions and returns a string describing which type it caught.When the
Rcpp::exception
is thrown, the catcher recognizes it as astd::exception
, which is the parent class.When the
Rcpp::internal::InterruptedException
is thrown, it is recogized correctly. (This is actually a little surprising to me, since we have other cases where aRcpp::internal::InterruptedException
is not recognized, and this is the problem in practice for us.)In our use case, we want to know when an interrupt occurs during the execution of an
Rcpp::Function
and act accordingly, but since theRcpp::internal::InterruptedException
is not properly recognized, we aren't able to handle the interrupt correctly.Here is a package that demonstrates the issue with
Rcpp::internal::InterruptedException
not being recognized: https://github.com/jcheng5/exceptiontest. And here is a related issue: r-lib/later#55cc: @kevinushey
The text was updated successfully, but these errors were encountered: