-
Notifications
You must be signed in to change notification settings - Fork 28
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
Using C++ interface for later::later() can trigger R's GC on the wrong thread #39
Comments
Now that I've looked at it some more, I think that maybe the root of the problem is that the The implication of this is that it is unsafe to store Rcpp objects anywhere that another thread may copy or destroy them. This includes putting them in a Here's a test script I wrote, trying to reproduce part the original issue: triggering a GC by growing a vector of Functions. However, it never triggers a GC when growing the vector, and I believe that it is because it all happens on the main R thread. Rcpp::cppFunction("
void copy_fun(Function f, int n = 1000) {
std::vector<Function> fv;
Function ident(\"identity\");
Rcerr << \"Pushing...\";
fv.push_back(f);
for (int i=1; i<n; i++) {
fv.push_back(ident);
}
Rcerr << \"done. \";
}
")
e <- new.env()
reg.finalizer(e, function(e) cat("Finalizer!\n"))
rm(e)
copy_fun(identity, 1e7) |
Here's an example that has a background thread that manipulates a This crashes reliably for me, and when I run it with
Rcpp::cppFunction(
depends = "BH",
includes = "
#include <pthread.h>
#include <boost/function.hpp>
// Rcpp::Function objects can be used as Tasks
typedef boost::function<void(void)> Task;
void dummy_function() {}
Task dummy_task = &dummy_function;
// Function to be executed on second thread
void* run_bg_thread(void *data) {
std::vector<Task>* vec = (std::vector<Task>*)data;
// Grow the vector by pushing dummy_task onto it repeatedly.
// When the vector grows, it will copy the Rcpp::Function that was added
// as the first element of vec, and its ref count will be changed.
for (int i=0; i<1e6; i++) {
vec->push_back(dummy_task);
}
return NULL;
}
",
"
void go(Function f) {
// Create a vector containing f. We'll give it to the other thread.
std::vector<Task>* vec = new std::vector<Task>;
vec->push_back(f);
// Start the thread.
pthread_t thread;
pthread_create(&thread, NULL, *run_bg_thread, (void*)vec);
// Start doing other stuff with f, so that refcount is manipulated.
for (int i=0; i<1e6; i++) {
Function tmp = f;
}
// wait for thread to finish
pthread_join(thread,NULL);
}
"
)
fun <- function() {}
go(fun) |
@jimhester has educated me on how Rcpp objects work. They are not smart pointers to SEXPs, as I had assumed; instead, each Rcpp object simply wraps an SEXP. That means that when they copied or destroyed, it uses R's reference counting mechanism, which definitely isn't thread-safe. https://github.com/RcppCore/Rcpp/blob/c3b26f6/inst/include/Rcpp/storage/PreserveStorage.h#L36-L41 |
Does that mean they can get arbitrarily large and expensive to copy? Should later be working harder to minimize the number of copies of Rcpp::Function? |
(Mostly meaning passing them by const reference) |
This is a bug that @jcheng5 and I have been tracking down. Here's an example that will crash R most of the time on Mac and Linux :
This will crash with weird errors like
and
Using the special builds of R,
RDsan
andRDassertthread
from https://github.com/wch/r-debug, I found that the cause of these weird errors is an R GC event occurring on the wrong thread. The GC is triggered when the background thread (as opposed to the main R thread) calls the C++ functionlater::later()
, and schedules a C function to be run. This can cause thestd::priority_queue<Callback>
inlater::CallbackRegistry
to reorganize itself, which can cause theRcpp::Function
to be copied, which can trigger a GC on the wrong thread.This can be see in the stack trace here:
https://gist.github.com/wch/1705437725df17e16e412946143c0d88#file-gistfile2-txt-L21-L60
With
RDassertthread
, it crashes every time, with:Using
RDassertthread -d gdb
provides a stack trace.The text was updated successfully, but these errors were encountered: