Skip to content

Commit 30fdafc

Browse files
authored
Merge pull request #1043 from jpritikin/master
Make Rcpp::exception thread-safe
2 parents f9425ee + caa35fb commit 30fdafc

File tree

6 files changed

+114
-70
lines changed

6 files changed

+114
-70
lines changed

ChangeLog

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
2020-02-09 Joshua Pritikin <[email protected]>
2+
3+
* inst/include/Rcpp.h: Include exceptions_impl.h
4+
* inst/include/Rcpp/exceptions.h: Make thread-safe
5+
* inst/include/Rcpp/exceptions_impl.h: New home for code moved
6+
from src/api.cpp
7+
18
2020-01-05 Dirk Eddelbuettel <[email protected]>
29

310
* inst/include/Rcpp/exceptions.h: A few more #nocov tags

inst/NEWS.Rd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
\item Finalizer calls clear external pointer first (Kirill Müller and
1515
Dirk in \ghpr{1038}).
1616
\item Scalar operations with a rhs matrix no longer change the
17-
matrix value (Qiang in \ghpr{1040} fixing (again) \ghit{365}).
17+
matrix value (Qiang in \ghpr{1040} fixing (again) \ghit{365}).
18+
\item \code{Rcpp::exception} and \code{Rcpp::stop} are now
19+
thread-safe. Be cautioned that most of Rcpp continues to be not
20+
thread-safe.
1821
}
1922
\item Changes in Rcpp Attributes:
2023
\itemize{

inst/include/Rcpp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
#include <Rcpp/Formula.h>
5757
#include <Rcpp/DataFrame.h>
5858

59+
#include <Rcpp/exceptions_impl.h>
60+
5961
#if !defined(RCPP_FORCE_OLD_DATE_DATETIME_VECTORS)
6062
#define RCPP_NEW_DATE_DATETIME_VECTORS 1
6163
#endif

inst/include/Rcpp/exceptions.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,22 @@
2727
#define RCPP_DEFAULT_INCLUDE_CALL true
2828
#endif
2929

30-
#define GET_STACKTRACE() stack_trace( __FILE__, __LINE__ )
30+
#define GET_STACKTRACE() R_NilValue
3131

3232
namespace Rcpp {
3333

34+
// Throwing an exception must be thread-safe to avoid surprises w/ OpenMP.
3435
class exception : public std::exception {
3536
public:
3637
explicit exception(const char* message_, bool include_call = RCPP_DEFAULT_INCLUDE_CALL) : // #nocov start
3738
message(message_),
38-
include_call_(include_call){
39-
rcpp_set_stack_trace(Shield<SEXP>(stack_trace()));
39+
include_call_(include_call) {
40+
record_stack_trace();
4041
}
4142
exception(const char* message_, const char*, int, bool include_call = RCPP_DEFAULT_INCLUDE_CALL) :
4243
message(message_),
43-
include_call_(include_call){
44-
rcpp_set_stack_trace(Shield<SEXP>(stack_trace()));
44+
include_call_(include_call) {
45+
record_stack_trace();
4546
}
4647
bool include_call() const {
4748
return include_call_;
@@ -50,9 +51,12 @@ namespace Rcpp {
5051
virtual const char* what() const throw() {
5152
return message.c_str(); // #nocov end
5253
}
54+
inline void copy_stack_trace_to_r() const;
5355
private:
5456
std::string message;
5557
bool include_call_;
58+
std::vector<std::string> stack;
59+
inline void record_stack_trace();
5660
};
5761

5862
// simple helper
@@ -340,7 +344,8 @@ inline SEXP exception_to_condition_template( const Exception& ex, bool include_c
340344
}
341345

342346
inline SEXP rcpp_exception_to_r_condition(const Rcpp::exception& ex) {
343-
return exception_to_condition_template(ex, ex.include_call());
347+
ex.copy_stack_trace_to_r();
348+
return exception_to_condition_template(ex, ex.include_call());
344349
}
345350

346351
inline SEXP exception_to_r_condition( const std::exception& ex){

inst/include/Rcpp/exceptions_impl.h

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// exceptions_impl.h: Rcpp R/C++ interface class library -- exceptions
2+
//
3+
// Copyright (C) 2020 Dirk Eddelbuettel, Romain Francois, and Joshua N. Pritikin
4+
//
5+
// This file is part of Rcpp.
6+
//
7+
// Rcpp is free software: you can redistribute it and/or modify it
8+
// under the terms of the GNU General Public License as published by
9+
// the Free Software Foundation, either version 2 of the License, or
10+
// (at your option) any later version.
11+
//
12+
// Rcpp is distributed in the hope that it will be useful, but
13+
// WITHOUT ANY WARRANTY; without even the implied warranty of
14+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
// GNU General Public License for more details.
16+
//
17+
// You should have received a copy of the GNU General Public License
18+
// along with Rcpp. If not, see <http://www.gnu.org/licenses/>.
19+
20+
namespace Rcpp {
21+
#if defined(__GNUC__) || defined(__clang__)
22+
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
23+
// do nothing
24+
#else
25+
#include <execinfo.h>
26+
27+
// Extract mangled name e.g. ./test(baz+0x14)[0x400962]
28+
static std::string demangler_one(const char* input) {
29+
static std::string buffer;
30+
buffer = input;
31+
size_t last_open = buffer.find_last_of('(');
32+
size_t last_close = buffer.find_last_of(')');
33+
if (last_open == std::string::npos ||
34+
last_close == std::string::npos) {
35+
return input; // #nocov
36+
}
37+
std::string function_name = buffer.substr(last_open + 1, last_close - last_open - 1);
38+
// Strip the +0x14 (if it exists, which it does not in earlier versions of gcc)
39+
size_t function_plus = function_name.find_last_of('+');
40+
if (function_plus != std::string::npos) {
41+
function_name.resize(function_plus);
42+
}
43+
buffer.replace(last_open + 1, function_name.size(), demangle(function_name));
44+
return buffer;
45+
}
46+
#endif
47+
#endif
48+
49+
// thread-safe; invoked prior to throwing the exception
50+
inline void exception::record_stack_trace()
51+
{
52+
#if defined(__GNUC__) || defined(__clang__)
53+
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
54+
// C++ stack not available on this system
55+
#else // ! (defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__ANDROID__)
56+
57+
/* inspired from http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/ */
58+
const size_t max_depth = 100;
59+
int stack_depth;
60+
void *stack_addrs[max_depth];
61+
62+
stack_depth = backtrace(stack_addrs, max_depth);
63+
char **stack_strings = backtrace_symbols(stack_addrs, stack_depth);
64+
65+
std::transform(stack_strings + 1, stack_strings + stack_depth,
66+
std::back_inserter(stack), demangler_one);
67+
free(stack_strings); // malloc()ed by backtrace_symbols
68+
#endif
69+
#endif
70+
}
71+
72+
// not thread-safe; invoked after catching the exception
73+
inline void exception::copy_stack_trace_to_r() const
74+
{
75+
if (!stack.size()) {
76+
rcpp_set_stack_trace(R_NilValue);
77+
return;
78+
}
79+
80+
CharacterVector res(stack.size());
81+
std::copy(stack.begin(), stack.end(), res.begin());
82+
List trace = List::create(_["file" ] = "",
83+
_["line" ] = -1,
84+
_["stack"] = res);
85+
trace.attr("class") = "Rcpp_stack_trace";
86+
rcpp_set_stack_trace(trace);
87+
}
88+
};

src/api.cpp

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -32,35 +32,6 @@ using namespace Rcpp;
3232
#include <cxxabi.h>
3333
#endif
3434

35-
#if defined(__GNUC__) || defined(__clang__)
36-
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
37-
// do nothing
38-
#else
39-
#include <execinfo.h>
40-
41-
// Extract mangled name e.g. ./test(baz+0x14)[0x400962]
42-
static std::string demangler_one(const char* input) {
43-
static std::string buffer;
44-
buffer = input;
45-
size_t last_open = buffer.find_last_of('(');
46-
size_t last_close = buffer.find_last_of(')');
47-
if (last_open == std::string::npos ||
48-
last_close == std::string::npos) {
49-
return input; // #nocov
50-
}
51-
std::string function_name = buffer.substr(last_open + 1, last_close - last_open - 1);
52-
// Strip the +0x14 (if it exists, which it does not in earlier versions of gcc)
53-
size_t function_plus = function_name.find_last_of('+');
54-
if (function_plus != std::string::npos) {
55-
function_name.resize(function_plus);
56-
}
57-
buffer.replace(last_open + 1, function_name.size(), demangle(function_name));
58-
return buffer;
59-
}
60-
#endif
61-
#endif
62-
63-
6435
namespace Rcpp {
6536

6637
namespace internal {
@@ -282,42 +253,10 @@ SEXP rcpp_can_use_cxx11() { // #nocov start
282253

283254
// [[Rcpp::register]]
284255
SEXP stack_trace(const char* file, int line) {
285-
#if defined(__GNUC__) || defined(__clang__)
286-
#if defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__MUSL__) || defined(__HAIKU__) || defined(__ANDROID__)
287-
// Simpler version for Windows and *BSD
288-
List trace = List::create(_["file"] = file,
289-
_[ "line" ] = line,
290-
_[ "stack" ] = "C++ stack not available on this system");
291-
trace.attr("class") = "Rcpp_stack_trace";
292-
return trace;
293-
#else // ! (defined(_WIN32) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) || defined(__sun) || defined(_AIX) || defined(__ANDROID__)
294-
295-
/* inspired from http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/ */
296-
const size_t max_depth = 100;
297-
int stack_depth;
298-
void *stack_addrs[max_depth];
299-
char **stack_strings;
300-
301-
stack_depth = backtrace(stack_addrs, max_depth);
302-
stack_strings = backtrace_symbols(stack_addrs, stack_depth);
303-
304-
std::string current_line;
305-
306-
CharacterVector res(stack_depth - 1);
307-
std::transform(stack_strings + 1, stack_strings + stack_depth, res.begin(), demangler_one);
308-
free(stack_strings); // malloc()ed by backtrace_symbols
309-
310-
List trace = List::create(_["file" ] = file,
311-
_["line" ] = line,
312-
_["stack"] = res);
313-
trace.attr("class") = "Rcpp_stack_trace";
314-
return trace;
315-
#endif
316-
#else /* !defined( __GNUC__ ) */
317-
return R_NilValue;
318-
#endif
256+
return R_NilValue;
319257
}
320258

259+
321260
// // [ [ Rcpp::register ] ]
322261
// void print(SEXP s) {
323262
// Rf_PrintValue(s); // defined in Rinternals.h

0 commit comments

Comments
 (0)