Skip to content

Commit fe15211

Browse files
authored
Create 1 preserve list per compilation unit (#331)
* Use 1 preserve list per compilation unit * NEWS bullet * Use dynamic initialization
1 parent ec27302 commit fe15211

File tree

3 files changed

+23
-73
lines changed

3 files changed

+23
-73
lines changed

NEWS.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# cpp11 (development version)
22

3+
* cpp11 now creates one protection list per compilation unit, rather than one global
4+
protection list shared across compilation units and across packages. This greatly
5+
reduces the complexity of managing the protection list state and should make it easier
6+
to make changes to the protection list structure in the future without breaking packages
7+
compiled with older versions of cpp11 (#330).
8+
39
* Nested calls to `cpp11::unwind_protect()` are no longer supported or
410
encouraged. Previously, this was something that could be done for performance
511
improvements, but ultimately this feature has proven to cause more problems

inst/include/cpp11/protect.hpp

+14-73
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,6 @@ class unwind_exception : public std::exception {
3131
unwind_exception(SEXP token_) : token(token_) {}
3232
};
3333

34-
namespace detail {
35-
// We deliberately avoid using safe[] in the below code, as this code runs
36-
// when the shared library is loaded and will not be wrapped by
37-
// `CPP11_UNWIND`, so if an error occurs we will not catch the C++ exception
38-
// that safe emits.
39-
inline void set_option(SEXP name, SEXP value) {
40-
static SEXP opt = SYMVALUE(Rf_install(".Options"));
41-
SEXP t = opt;
42-
while (CDR(t) != R_NilValue) {
43-
if (TAG(CDR(t)) == name) {
44-
opt = CDR(t);
45-
SET_TAG(opt, name);
46-
SETCAR(opt, value);
47-
return;
48-
}
49-
t = CDR(t);
50-
}
51-
SETCDR(t, Rf_allocList(1));
52-
opt = CDR(t);
53-
SET_TAG(opt, name);
54-
SETCAR(opt, value);
55-
}
56-
57-
} // namespace detail
58-
5934
#ifdef HAS_UNWIND_PROTECT
6035

6136
/// Unwind Protection from C longjmp's, like those used in R error handling
@@ -350,58 +325,24 @@ static struct {
350325
}
351326

352327
private:
353-
// The preserved list singleton is stored in a XPtr within an R global option.
354-
//
355-
// It is not constructed as a static variable directly since many
356-
// translation units may be compiled, resulting in unrelated instances of each
357-
// static variable.
358-
//
359-
// We cannot store it in the cpp11 namespace, as cpp11 likely will not be loaded by
360-
// packages.
361-
// We cannot store it in R's global environment, as that is against CRAN
362-
// policies.
363-
// We instead store it as an XPtr in the global options, which avoids issues
364-
// both copying and serializing.
365-
static SEXP get_preserve_xptr_addr() {
366-
static SEXP preserve_xptr_sym = Rf_install("cpp11_preserve_xptr");
367-
SEXP preserve_xptr = Rf_GetOption1(preserve_xptr_sym);
368-
369-
if (TYPEOF(preserve_xptr) != EXTPTRSXP) {
370-
return R_NilValue;
371-
}
372-
auto addr = R_ExternalPtrAddr(preserve_xptr);
373-
if (addr == nullptr) {
374-
return R_NilValue;
375-
}
376-
return static_cast<SEXP>(addr);
328+
// Each compilation unit purposefully gets its own preserve list.
329+
// This avoids issues with sharing preserve list state across compilation units
330+
// and across packages, which has historically caused many issues (#330).
331+
static SEXP get_preserve_list() {
332+
static SEXP out = init_preserve_list();
333+
return out;
377334
}
378335

379-
static void set_preserve_xptr(SEXP value) {
380-
static SEXP preserve_xptr_sym = Rf_install("cpp11_preserve_xptr");
381-
382-
SEXP xptr = PROTECT(R_MakeExternalPtr(value, R_NilValue, R_NilValue));
383-
detail::set_option(preserve_xptr_sym, xptr);
384-
UNPROTECT(1);
336+
static SEXP init_preserve_list() {
337+
// Initialize the list exactly once per compilation unit,
338+
// and let R manage its memory
339+
SEXP out = new_preserve_list();
340+
R_PreserveObject(out);
341+
return out;
385342
}
386343

387-
static SEXP get_preserve_list() {
388-
static SEXP preserve_list = R_NilValue;
389-
if (TYPEOF(preserve_list) != LISTSXP) {
390-
preserve_list = get_preserve_xptr_addr();
391-
if (TYPEOF(preserve_list) != LISTSXP) {
392-
preserve_list = Rf_cons(R_NilValue, Rf_cons(R_NilValue, R_NilValue));
393-
R_PreserveObject(preserve_list);
394-
set_preserve_xptr(preserve_list);
395-
}
396-
397-
// NOTE: Because older versions of cpp11 (<= 0.4.2) initialized the
398-
// precious_list with a single cell, we might need to detect and update
399-
// an existing empty precious list so that we have a second cell following.
400-
if (CDR(preserve_list) == R_NilValue)
401-
SETCDR(preserve_list, Rf_cons(R_NilValue, R_NilValue));
402-
}
403-
404-
return preserve_list;
344+
static SEXP new_preserve_list() {
345+
return Rf_cons(R_NilValue, Rf_cons(R_NilValue, R_NilValue));
405346
}
406347

407348
} preserved;

vignettes/internals.Rmd

+3
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ Calling `preserved.release()` on this returned token will release the protection
123123

124124
This scheme scales in O(1) time to release or insert an object vs O(N) or worse time with `R_PreserveObject()` / `R_ReleaseObject()`.
125125

126+
Each compilation unit has its own unique protection list, which avoids the need to manage a "global" protection list shared across compilation units within the same package and across packages.
127+
A previous version of cpp11 used a global protection list, but this caused [multiple issues](https://github.com/r-lib/cpp11/issues/330).
128+
126129
These functions are defined in [protect.hpp](https://github.com/r-lib/cpp11/blob/main/inst/include/cpp11/protect.hpp)
127130

128131
### Unwind Protect

0 commit comments

Comments
 (0)