Недопустимые чтения с libc++ с использованием OpenMP с current_exception и exception_ptr при вызове из R через Rcpp

Я следовал решению, предложенному здесь для обработки ошибок, возникающих внутри блоков OpenMP в C++. Очевидно, это не работает при использовании Rcpp, но это только в Fedora, с Clang-11 и с libc++ (без libc++ работает нормально). Рассмотрим следующий файл C++:

// openmp-exception-issue.cpp
#ifdef _OPENMP
#include <omp.h>
#endif
#include <exception>
#include <stdexcept>
#include <iostream>

// from https://github.com/boennecd/mdgc/blob/b749ef33e0b324fa5f07d3d528097b8c22fb586c/src/openmp-exception_ptr.h#L10-L43
class openmp_exception_ptr {
  std::exception_ptr Ptr = nullptr;
  bool is_set = false;
public:
  inline void rethrow_if_error(){
#ifdef _OPENMP
    if(this->Ptr)
      std::rethrow_exception(this->Ptr);
#endif
  }

  template <typename Function, typename... Parameters>
  void run(Function f, Parameters... params)
  {
#ifdef _OPENMP
    try
    {
      f(params...);
    }
    catch (...)
    {
#pragma omp critical(openmp_exception_ptr)
      {
        if(!is_set){
          this->Ptr = std::current_exception();
          is_set = true;
        }
      }
    }
#else
   f(params...);
#endif
  }
};

int do_work(int const x){
    if(x > -1)
      throw std::runtime_error("boh :(");
    return x;
}

// [[Rcpp::plugins(openmp)]]
#include <Rcpp.h>

// [[Rcpp::export()]]
double that_cpp_func(int const n_it){
  openmp_exception_ptr obj;
  double out(0.);
#pragma omp parallel num_threads(4)
  {
#pragma omp for schedule(static) reduction(+:out) nowait
  for(int i = 0; i < n_it; ++i){
    obj.run([&]() -> void {
      out += do_work(i);
    });
  }
  }

  try 
  {
    obj.rethrow_if_error();
  } 
  catch(...)
  {
    Rcpp::Rcout << "Caught an error\n" << out << '\n';
  }

  return out;
}

Затем используйте Rcpp с настройкой R, как описано для CRAN's r-devel-linux-x86_64-fedora-clang с использованием Docker, начиная с rhub/fedora-clang, я получаю следующую ошибку:

R -d valgrind -e "Rcpp::sourceCpp('openmp-exception-issue.cpp'); that_cpp_func(100)"
# ==2897== Memcheck, a memory error detector
# ==2897== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
# ==2897== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
# ==2897== Command: /root/R-devel/bin/exec/R -e Rcpp::sourceCpp('openmp-exception-issue.cpp');~+~that_cpp_func(100)
# ==2897== 
# 
# R Under development (unstable) (2021-02-22 r80031) -- "Unsuffered Consequences"
# Copyright (C) 2021 The R Foundation for Statistical Computing
# Platform: x86_64-pc-linux-gnu (64-bit)
# 
# R is free software and comes with ABSOLUTELY NO WARRANTY.
# You are welcome to redistribute it under certain conditions.
# Type 'license()' or 'licence()' for distribution details.
# 
#   Natural language support but running in an English locale
# 
# R is a collaborative project with many contributors.
# Type 'contributors()' for more information and
# 'citation()' on how to cite R or R packages in publications.
# 
# Type 'demo()' for some demos, 'help()' for on-line help, or
# 'help.start()' for an HTML browser interface to help.
# Type 'q()' to quit R.
# 
# > Rcpp::sourceCpp('openmp-exception-issue.cpp'); that_cpp_func(100)
# In file included from openmp-exception-issue.cpp:52:
# In file included from /root/R-devel/library/Rcpp/include/Rcpp.h:57:
# /root/R-devel/library/Rcpp/include/Rcpp/DataFrame.h:136:18: warning: unused variable 'data' [-Wunused-variable]
#             SEXP data = Parent::get__();
#                  ^
# 1 warning generated.
# ==2897== Syscall param sched_setaffinity(mask) points to unaddressable byte(s)
# ==2897==    at 0x550F55D: syscall (in /usr/lib64/libc-2.32.so)
# ==2897==    by 0x539AD1C: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x536AAE9: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535774A: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x5357B8C: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FB77: that_cpp_func(int) (openmp-exception-issue.cpp:55)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==    by 0x4EED11: R_execClosure (eval.c:0)
# ==2897==    by 0x4EE288: Rf_applyClosure (eval.c:1823)
# ==2897==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
# ==2897== 
# ==2897== Invalid read of size 8
# ==2897==    at 0x71EC2DF: __cxa_end_catch (in /usr/lib64/libstdc++.so.6.0.28)
# ==2897==    by 0x10B0FEED: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:38)
# ==2897==    by 0x10B0FEED: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FEED: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==    by 0x4EED11: R_execClosure (eval.c:0)
# ==2897==  Address 0x9d69a30 is 96 bytes inside a block of size 144 free'd
# ==2897==    at 0x483A9F5: free (vg_replace_malloc.c:538)
# ==2897==    by 0xB6AE48A: __cxa_decrement_exception_refcount (in /usr/lib64/libc++abi.so.1.0)
# ==2897==    by 0x10B0FECB: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:34)
# ==2897==    by 0x10B0FECB: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FECB: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==  Block was alloc'd at
# ==2897==    at 0x4839809: malloc (vg_replace_malloc.c:307)
# ==2897==    by 0x71EC0C3: __cxa_allocate_exception (in /usr/lib64/libstdc++.so.6.0.28)
# ==2897==    by 0x10B0FE41: do_work (openmp-exception-issue.cpp:47)
# ==2897==    by 0x10B0FE41: operator() (openmp-exception-issue.cpp:63)
# ==2897==    by 0x10B0FE41: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:27)
# ==2897==    by 0x10B0FE41: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FE41: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897== 
# ==2897== Invalid read of size 4
# ==2897==    at 0x71EC2E9: __cxa_end_catch (in /usr/lib64/libstdc++.so.6.0.28)
# ==2897==    by 0x10B0FEED: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:38)
# ==2897==    by 0x10B0FEED: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FEED: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==    by 0x4EED11: R_execClosure (eval.c:0)
# ==2897==  Address 0x9d69a08 is 56 bytes inside a block of size 144 free'd
# ==2897==    at 0x483A9F5: free (vg_replace_malloc.c:538)
# ==2897==    by 0xB6AE48A: __cxa_decrement_exception_refcount (in /usr/lib64/libc++abi.so.1.0)
# ==2897==    by 0x10B0FECB: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:34)
# ==2897==    by 0x10B0FECB: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FECB: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==  Block was alloc'd at
# ==2897==    at 0x4839809: malloc (vg_replace_malloc.c:307)
# ==2897==    by 0x71EC0C3: __cxa_allocate_exception (in /usr/lib64/libstdc++.so.6.0.28)
# ==2897==    by 0x10B0FE41: do_work (openmp-exception-issue.cpp:47)
# ==2897==    by 0x10B0FE41: operator() (openmp-exception-issue.cpp:63)
# ==2897==    by 0x10B0FE41: run<(lambda at openmp-exception-issue.cpp:62:13)> (openmp-exception-issue.cpp:27)
# ==2897==    by 0x10B0FE41: .omp_outlined._debug__ (openmp-exception-issue.cpp:62)
# ==2897==    by 0x10B0FE41: .omp_outlined. (openmp-exception-issue.cpp:58)
# ==2897==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
# ==2897==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
# ==2897==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
# ==2897==    by 0x10B0FBCA: that_cpp_func(int) (openmp-exception-issue.cpp:58)
# ==2897==    by 0x10B0FF7A: sourceCpp_1_that_cpp_func (openmp-exception-issue.cpp:89)
# ==2897==    by 0x49DA3A: R_doDotCall (dotcode.c:598)
# ==2897==    by 0x4A0510: do_dotcall (dotcode.c:1281)
# ==2897==    by 0x4D50BC: Rf_eval (eval.c:830)
# ==2897==  
# 29 more errors...
# ==2897== For lists of detected and suppressed errors, rerun with: -s
# ==2897== ERROR SUMMARY: 32 errors from 18 contexts (suppressed: 0 from 0)

Я не уверен, почему это происходит и почему это происходит только с Clang-11, использующим libc++. Глядя на первую ошибку, память выделяется в throw std::runtime_error("boh :(");, освобождается в this->Ptr = std::current_exception(); и впоследствии читается в конце блока catch в openmp_exception_ptr.run.


Простой код

Этот код также вызывает аналогичные (но меньше) ошибки:

// openmp-exception-issue.cpp
#include <omp.h>
#include <exception>
#include <stdexcept>

// [[Rcpp::plugins(openmp)]]
#include <Rcpp.h>

// [[Rcpp::export()]]
double that_cpp_func(int const n_it){
  std::exception_ptr Ptr = nullptr;
  bool is_set = false;
  double out(0.);

#pragma omp parallel for num_threads(4) reduction(+:out)
  for(int i = 0; i < n_it; ++i)
    try
    {
      if(i > -1)
        throw std::runtime_error("boh :(");
      out += i;
    }
    catch (...)
    {
#pragma omp critical
      if(!is_set){
        Ptr = std::current_exception();
        is_set = true;
      }
    }

  if(Ptr)
    Rcpp::Rcout << "Caught an error\n" << out << '\n';

  return out;
}

Оригинальный вопрос

Я следовал решению, предложенному здесь для обработки ошибок, возникающих внутри блоков OpenMP в C++. В качестве примера рассмотрим следующий код:

// openmp-exception-issue.cpp
#ifdef _OPENMP
#include <omp.h>
#endif
#include <exception>
#include <stdexcept>
#include <iostream>

// from https://github.com/boennecd/mdgc/blob/b749ef33e0b324fa5f07d3d528097b8c22fb586c/src/openmp-exception_ptr.h#L10-L43
class openmp_exception_ptr {
  std::exception_ptr Ptr = nullptr;
  bool is_set = false;
public:
  inline void rethrow_if_error(){
#ifdef _OPENMP
    if(this->Ptr)
      std::rethrow_exception(this->Ptr);
#endif
  }

  template <typename Function, typename... Parameters>
  void run(Function f, Parameters... params)
  {
#ifdef _OPENMP
    try
    {
      f(params...);
    }
    catch (...)
    {
#pragma omp critical(openmp_exception_ptr)
      {
        if(!is_set){
          this->Ptr = std::current_exception();
          is_set = true;
        }
      }
    }
#else
   f(params...);
#endif
  }
};

int main(){
  openmp_exception_ptr obj;
#ifdef _OPENMP
#pragma omp parallel for num_threads(4)
#endif
  for(int i = 0; i < 10000; ++i){
    obj.run([&]() -> void {
      throw std::runtime_error("boh :(");
    });
  }

  try 
  {
    obj.rethrow_if_error();
  } 
  catch(...)
  {
    std::cout << "Caught an error\n";
  }
}

Кажется, это отлично работает с версией Clang Fedora 11.0.0-2.fc33 в Fedora:

clang++-11 -stdlib=libc++ -std=gnu++14 -DNDEBUG -O3 -o issue-example openmp-exception-issue.cpp -fopenmp
valgrind ./issue-example
# ==1844== Memcheck, a memory error detector
# ...
# ==1844== Syscall param sched_setaffinity(mask) points to unaddressable byte(s)
# ==1844==    at 0x4CA755D: syscall (in /usr/lib64/libc-2.32.so)
# ==1844==    by 0x4B30D1C: ??? (in /usr/lib64/libomp.so)
# ==1844==    by 0x4B00AE9: ??? (in /usr/lib64/libomp.so)
# ==1844==    by 0x4AED74A: ??? (in /usr/lib64/libomp.so)
# ==1844==    by 0x4AEDB8C: ??? (in /usr/lib64/libomp.so)
# ==1844==    by 0x40132E: main (in /sdir/issue-example)
# ==1844==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
# ==1844== 
# Caught an error
# ...
# 
# ==1844== LEAK SUMMARY:
# ==1844==    definitely lost: 0 bytes in 0 blocks
# ==1844==    indirectly lost: 0 bytes in 0 blocks
# ==1844==      possibly lost: 0 bytes in 0 blocks
# ==1844==    still reachable: 81,248 bytes in 12 blocks
# ==1844==         suppressed: 0 bytes in 0 blocks
# ==1844== Rerun with --leak-check=full to see details of leaked memory
# ==1844== 
# ==1844== For lists of detected and suppressed errors, rerun with: -s
# ==1844== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

где я полагаю, что ошибка является ложным срабатыванием.

Проблемы, которые мне не удалось воспроизвести на небольшом примере, связаны с моим пакетом R здесь , только в Fedora, с Clang-11 и с libc++ (отлично работает без libc++). Он также работает с Ubuntu (clang 6 и GCC 10), Windows, macOS и Solaris.

Если я запускаю R CMD check --use-valgrind --as-cran --no-stop-on-test-error --run-donttest mdgc_0.1.2.tar.gz в файле tar.gz из R CMD build, я получаю указанные ниже ошибки в файле mdgc.Rcheck/tests/testthat.Rout.

#... 
==1370== 
==1370== Invalid read of size 8
==1370==    at 0x71EC2DF: __cxa_end_catch (in /usr/lib64/libstdc++.so.6.0.28)
==1370==    by 0x1095A26D: run<(lambda at cpp-to-R.cpp:192:23)> (openmp-exception_ptr.h:38)
==1370==    by 0x1095A26D: .omp_outlined._debug__ (cpp-to-R.cpp:192)
==1370==    by 0x1095A26D: .omp_outlined. (cpp-to-R.cpp:179)
==1370==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
==1370==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
==1370==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
==1370==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
==1370==    by 0x10959813: eval_log_lm_terms(SEXPREC*, arma::Col<int> const&, arma::Mat<double> const&, arma::Col<double> const&, int, double, double, int, bool, unsigned int, bool, bool) (cpp-to-R.cpp:179)
# ...
==1370==  Address 0x7d76800 is 96 bytes inside a block of size 144 free'd
==1370==    at 0x483A9F5: free (vg_replace_malloc.c:538)
==1370==    by 0xFC7E48A: __cxa_decrement_exception_refcount (in /usr/lib64/libc++abi.so.1.0)
==1370==    by 0x1095A24A: run<(lambda at cpp-to-R.cpp:192:23)> (openmp-exception_ptr.h:34)
==1370==    by 0x1095A24A: .omp_outlined._debug__ (cpp-to-R.cpp:192)
==1370==    by 0x1095A24A: .omp_outlined. (cpp-to-R.cpp:179)
==1370==    by 0x53B27C2: __kmp_invoke_microtask (in /usr/lib64/libomp.so)
==1370==    by 0x5358068: ??? (in /usr/lib64/libomp.so)
==1370==    by 0x535BFF2: __kmp_fork_call (in /usr/lib64/libomp.so)
==1370==    by 0x534A740: __kmpc_fork_call (in /usr/lib64/libomp.so)
==1370==    by 0x10959813: eval_log_lm_terms(SEXPREC*, arma::Col<int> const&, arma::Mat<double> const&, arma::Col<double> const&, int, double, double, int, bool, unsigned int, bool, bool) (cpp-to-R.cpp:179)
# ...
==1370==  Block was alloc'd at
==1370==    at 0x4839809: malloc (vg_replace_malloc.c:307)
==1370==    by 0x71EC0C3: __cxa_allocate_exception (in /usr/lib64/libstdc++.so.6.0.28)
==1370==    by 0x109696CE: restrictcdf::cdf<restrictcdf::likelihood, restrictcdf::likelihood::out_type>::cdf(restrictcdf::likelihood&, arma::Col<double> const&, arma::Col<double> const&, arma::Col<double> const&, arma::Mat<double> const&, bool, bool) (restrict-cdf.h:235)
==1370==    by 0x10987D8B: mdgc::log_ml_term::approximate(arma::Mat<double> const&, arma::Col<double> const&, arma::Mat<double>&, arma::Col<double>&, int, double, double, bool, bool, unsigned long, bool) const (logLik.cpp:266)
==1370==    by 0x1095A1EC: operator() (cpp-to-R.cpp:193)
==1370==    by 0x1095A1EC: run<(lambda at cpp-to-R.cpp:192:23)> (openmp-exception_ptr.h:27)
# ... 
# 31 more related errors ...
==1370== 
==1370== For lists of detected and suppressed errors, rerun with: -s
==1370== ERROR SUMMARY: 32 errors from 18 contexts (suppressed: 0 from 0)

Однако я не понимаю, почему это не работает. Проходим первую ошибку:


person Benjamin Christoffersen    schedule 24.02.2021    source источник