-
Notifications
You must be signed in to change notification settings - Fork 50
Support more efficient nesting of unwind_protect #207
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
Conversation
The internal protection can be skipped if we are already in an unwind_protect call. Fixes #141
inst/include/cpp11/protect.hpp
Outdated
} | ||
#endif | ||
} | ||
|
||
void release(SEXP token) { | ||
if (token == R_NilValue) { | ||
return; | ||
} | ||
|
||
#ifdef CPP11_USE_PRESERVE_OBJECT | ||
R_ReleaseObject(token); | ||
return; | ||
#endif | ||
|
||
SEXP before = CAR(token); | ||
|
||
SEXP after = CDR(token); | ||
|
||
if (before == R_NilValue && after == R_NilValue) { | ||
Rf_error("should never happen"); | ||
} | ||
|
||
SETCDR(before, after); | ||
|
||
if (after != R_NilValue) { | ||
SETCAR(after, before); | ||
} | ||
} | ||
|
||
private: | ||
// We deliberately avoid using safe[] in the below code, as this code runs | ||
// when the shared library is loaded and will not be wrapped by | ||
// `CPP11_UNWIND`, so if an error occurs we will not catch the C++ exception | ||
// that safe emits. | ||
static void set_option(SEXP name, SEXP value) { | ||
static SEXP opt = SYMVALUE(Rf_install(".Options")); | ||
SEXP t = opt; | ||
while (CDR(t) != R_NilValue) { | ||
if (TAG(CDR(t)) == name) { | ||
opt = CDR(t); | ||
SET_TAG(opt, name); | ||
SETCAR(opt, value); | ||
return; | ||
} | ||
t = CDR(t); | ||
} | ||
SETCDR(t, Rf_allocList(1)); | ||
opt = CDR(t); | ||
SET_TAG(opt, name); | ||
SETCAR(opt, value); | ||
} | ||
|
||
// The list_ singleton is stored in a XPtr within an R global option. | ||
// | ||
// It is not constructed as a static variable directly since many | ||
// translation units may be compiled, resulting in unrelated instances of each | ||
// static variable. | ||
// | ||
// We cannot store it in the cpp11 namespace, as cpp11 likely will not be loaded by | ||
// packages. | ||
// We cannot store it in R's global environment, as that is against CRAN | ||
// policies. | ||
// We instead store it as an XPtr in the global options, which avoids issues | ||
// both copying and serializing. | ||
static SEXP get_preserve_xptr_addr() { | ||
static SEXP preserve_xptr_sym = Rf_install("cpp11_preserve_xptr"); | ||
SEXP preserve_xptr = Rf_GetOption1(preserve_xptr_sym); | ||
|
||
if (TYPEOF(preserve_xptr) != EXTPTRSXP) { | ||
return R_NilValue; | ||
} | ||
auto addr = R_ExternalPtrAddr(preserve_xptr); | ||
if (addr == nullptr) { | ||
return R_NilValue; | ||
} | ||
return static_cast<SEXP>(addr); | ||
} | ||
|
||
static void set_preserve_xptr(SEXP value) { | ||
static SEXP preserve_xptr_sym = Rf_install("cpp11_preserve_xptr"); | ||
|
||
SEXP xptr = PROTECT(R_MakeExternalPtr(value, R_NilValue, R_NilValue)); | ||
set_option(preserve_xptr_sym, xptr); | ||
UNPROTECT(1); | ||
} | ||
|
||
static SEXP get_preserve_list() { | ||
static SEXP preserve_list = R_NilValue; | ||
|
||
if (TYPEOF(preserve_list) != LISTSXP) { | ||
preserve_list = get_preserve_xptr_addr(); | ||
if (TYPEOF(preserve_list) != LISTSXP) { | ||
preserve_list = Rf_cons(R_NilValue, R_NilValue); | ||
R_PreserveObject(preserve_list); | ||
set_preserve_xptr(preserve_list); | ||
} | ||
} | ||
|
||
return preserve_list; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this code was just moved here from lower down in the file, because it needs to be defined before the unwind_protect()
calls below
inst/include/cpp11/protect.hpp
Outdated
return preserve_list; | ||
} | ||
|
||
static int* get_should_unwind_protect() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we define a global logical sexp to track if we are inside an unwind protect call.
We need to use a singleton managed by R rather than a global in the header file so we don't have multiple global variables in each compilation unit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible we should move this code outside of this class, I put it here for convenience, since we were already using this same strategy for the preserve list.
inst/include/cpp11/protect.hpp
Outdated
@@ -42,6 +221,7 @@ SEXP unwind_protect(Fun&& code) { | |||
|
|||
std::jmp_buf jmpbuf; | |||
if (setjmp(jmpbuf)) { | |||
*preserved.should_unwind_protect_ = TRUE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is important, otherwise we don't correctly set the flag after there is an error.
inst/include/cpp11/protect.hpp
Outdated
SEXP list_ = get_preserve_list(); | ||
|
||
public: | ||
int* should_unwind_protect_ = get_should_unwind_protect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int* should_unwind_protect_ = get_should_unwind_protect(); | |
R_Boolean* should_unwind_protect_ = get_should_unwind_protect(); |
inst/include/cpp11/protect.hpp
Outdated
@@ -34,6 +211,13 @@ class unwind_exception : public std::exception { | |||
template <typename Fun, typename = typename std::enable_if<std::is_same< | |||
decltype(std::declval<Fun&&>()()), SEXP>::value>::type> | |||
SEXP unwind_protect(Fun&& code) { | |||
REprintf("%s\n", *preserved.should_unwind_protect_ == TRUE ? "TRUE" : "FALSE"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WTF
@sbearrows could you look this over, it should be a bit simpler than what we reviewed together yesterday. I decided not to write an additional test for this, as it doesn't seem obvious to me how you would test it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Thanks for reviewing! |
The internal protection can be skipped if we are already in an
unwind_protect call.
Fixes #141