Skip to content

Commit 67c1f4f

Browse files
committed
add isForkedChild() for tbb fork-safety checks
closes #243. tbb does not support being used after fork(). downstream packages can't reliably detect this themselves because rcppparallel — and therefore tbb — may have been loaded into the parent long before the downstream package gets a chance to record a baseline pid. capture getpid() in R_init_RcppParallel and expose RcppParallel::isForkedChild() (c++) plus isForkedChild() (r) so callers can fall back to a serial path when running inside a fork()'d child (e.g. under parallel::mclapply). windows is a no-op (always false) since it has no fork().
1 parent e4e33f4 commit 67c1f4f

8 files changed

Lines changed: 119 additions & 2 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ License: GPL (>= 3)
3535
URL: https://rcppcore.github.io/RcppParallel/, https://github.com/RcppCore/RcppParallel
3636
BugReports: https://github.com/RcppCore/RcppParallel/issues
3737
Biarch: TRUE
38-
RoxygenNote: 7.3.2
38+
RoxygenNote: 7.3.3
3939
Encoding: UTF-8

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ export(LdFlags)
55
export(RcppParallel.package.skeleton)
66
export(RcppParallelLibs)
77
export(defaultNumThreads)
8+
export(isForkedChild)
89
export(setThreadOptions)
910
export(tbbLibraryPath)

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11

22
## RcppParallel 6.0.0 (UNRELEASED)
33

4+
* RcppParallel now provides `isForkedChild()` (R) and
5+
`RcppParallel::isForkedChild()` (C++), which return `TRUE` when the current
6+
process is a `fork()` of the process in which RcppParallel was loaded.
7+
Packages dispatching parallel work from within `parallel::mclapply()` (or
8+
similar) should consult this and fall back to a serial path, as TBB does
9+
not support use after fork. (#243)
10+
411
* RcppParallel no longer includes tbb headers as part of the RcppParallel/TBB.h
512
header, and instead only exposes its TBB-specific APIs for parallel work.
613

R/fork.R

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#' Is the Current Process a Forked Child?
2+
#'
3+
#' Returns `TRUE` if the current process is a `fork()` of the process in which
4+
#' RcppParallel was originally loaded.
5+
#'
6+
#' Intel TBB — the parallel backend used by `parallelFor()` and
7+
#' `parallelReduce()` — does not support being used in a child process after
8+
#' `fork()`. Packages that may be invoked from within `parallel::mclapply()`
9+
#' or similar fork-based parallelism should call `isForkedChild()` and fall
10+
#' back to a serial code path when it returns `TRUE`.
11+
#'
12+
#' On Windows, which has no `fork()`, this always returns `FALSE`.
13+
#'
14+
#' @return A length-one logical.
15+
#'
16+
#' @examples
17+
#' \dontrun{
18+
#' library(RcppParallel)
19+
#' isForkedChild()
20+
#' parallel::mclapply(1:2, function(i) RcppParallel::isForkedChild())
21+
#' }
22+
#'
23+
#' @export
24+
isForkedChild <- function() {
25+
.Call("isForkedChild", PACKAGE = "RcppParallel")
26+
}

inst/include/RcppParallel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#endif
2323

2424
#include "RcppParallel/Backend.h"
25+
#include "RcppParallel/Fork.h"
2526
#include "RcppParallel/RVector.h"
2627
#include "RcppParallel/RMatrix.h"
2728

inst/include/RcppParallel/Fork.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef __RCPP_PARALLEL_FORK__
2+
#define __RCPP_PARALLEL_FORK__
3+
4+
namespace RcppParallel {
5+
6+
// Returns true if the current process is a fork() of the process in which
7+
// RcppParallel was originally loaded. Always returns false on Windows, which
8+
// has no fork().
9+
//
10+
// Intel TBB does not support being used after fork(). Code paths reachable
11+
// from fork()'d children (for example, via parallel::mclapply) should call
12+
// this and fall back to a serial implementation when it returns true.
13+
bool isForkedChild();
14+
15+
} // namespace RcppParallel
16+
17+
#endif // __RCPP_PARALLEL_FORK__

man/isForkedChild.Rd

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/init.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,52 @@
1-
21
#include <R.h>
32
#include <Rinternals.h>
43
#include <stdlib.h> // for NULL
54
#include <R_ext/Rdynload.h>
65

6+
#ifndef _WIN32
7+
# include <sys/types.h>
8+
# include <unistd.h>
9+
#endif
10+
11+
namespace RcppParallel {
12+
13+
#ifndef _WIN32
14+
static pid_t s_loadPid = 0;
15+
16+
bool isForkedChild()
17+
{
18+
return getpid() != s_loadPid;
19+
}
20+
#else
21+
bool isForkedChild()
22+
{
23+
return false;
24+
}
25+
#endif
26+
27+
} // namespace RcppParallel
28+
729
/* .Call calls */
830
extern "C" SEXP defaultNumThreads();
931

32+
extern "C" SEXP isForkedChild()
33+
{
34+
int forked = RcppParallel::isForkedChild() ? TRUE : FALSE;
35+
return Rf_ScalarLogical(forked);
36+
}
37+
1038
static const R_CallMethodDef CallEntries[] = {
1139
{"defaultNumThreads", (DL_FUNC) &defaultNumThreads, 0},
40+
{"isForkedChild", (DL_FUNC) &isForkedChild, 0},
1241
{NULL, NULL, 0}
1342
};
1443

1544
extern "C" void R_init_RcppParallel(DllInfo *dll)
1645
{
46+
#ifndef _WIN32
47+
RcppParallel::s_loadPid = getpid();
48+
#endif
49+
1750
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
1851
R_useDynamicSymbols(dll, FALSE);
1952
}

0 commit comments

Comments
 (0)