Skip to content

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

Merged
merged 6 commits into from
Jul 21, 2021

Conversation

jimhester
Copy link
Member

The internal protection can be skipped if we are already in an
unwind_protect call.

Fixes #141

The internal protection can be skipped if we are already in an
unwind_protect call.

Fixes #141
Comment on lines 31 to 180
}
#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;
}
Copy link
Member Author

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

return preserve_list;
}

static int* get_should_unwind_protect() {
Copy link
Member Author

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.

Copy link
Member Author

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.

@@ -42,6 +221,7 @@ SEXP unwind_protect(Fun&& code) {

std::jmp_buf jmpbuf;
if (setjmp(jmpbuf)) {
*preserved.should_unwind_protect_ = TRUE;
Copy link
Member Author

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.

SEXP list_ = get_preserve_list();

public:
int* should_unwind_protect_ = get_should_unwind_protect();
Copy link
Member Author

@jimhester jimhester Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int* should_unwind_protect_ = get_should_unwind_protect();
R_Boolean* should_unwind_protect_ = get_should_unwind_protect();

@@ -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");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WTF

@jimhester jimhester requested a review from sbearrows July 20, 2021 13:14
@jimhester
Copy link
Member Author

@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.

Copy link
Contributor

@sbearrows sbearrows left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@jimhester jimhester merged commit 46ee0bb into master Jul 21, 2021
@jimhester jimhester deleted the nested_unwind_protect branch July 21, 2021 14:13
@jimhester
Copy link
Member Author

Thanks for reviewing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

There should be a way to opt out of unwind_protect for a block of code.
2 participants