Skip to content

Commit fbaeabb

Browse files
authored
add isProcessForkedChild() for tbb fork-safety checks (#244)
* 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(). * rename isForkedChild() to isProcessForkedChild() avoids a name collision with parallelly::isForkedChild(), which has slightly different semantics (it detects any forked child, whereas we detect a fork() occurring after RcppParallel was loaded).
1 parent da5ac93 commit fbaeabb

8 files changed

Lines changed: 121 additions & 3 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(isProcessForkedChild)
89
export(setThreadOptions)
910
export(tbbLibraryPath)

NEWS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
# RcppParallel 6.0.0
1414

15+
* RcppParallel now provides `isProcessForkedChild()` (R) and
16+
`RcppParallel::isProcessForkedChild()` (C++), which return `TRUE` when the
17+
current process is a `fork()` of the process in which RcppParallel was
18+
loaded.
19+
Packages dispatching parallel work from within `parallel::mclapply()` (or
20+
similar) should consult this and fall back to a serial path, as TBB does
21+
not support use after fork. (#243)
22+
1523
* RcppParallel no longer includes tbb headers as part of the RcppParallel/TBB.h
1624
header, and instead only exposes its TBB-specific APIs for parallel work.
1725

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 `isProcessForkedChild()` and
10+
#' fall 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+
#' isProcessForkedChild()
20+
#' parallel::mclapply(1:2, function(i) RcppParallel::isProcessForkedChild())
21+
#' }
22+
#'
23+
#' @export
24+
isProcessForkedChild <- function() {
25+
.Call("isProcessForkedChild", 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 isProcessForkedChild();
14+
15+
} // namespace RcppParallel
16+
17+
#endif // __RCPP_PARALLEL_FORK__

man/isProcessForkedChild.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: 35 additions & 2 deletions
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 isProcessForkedChild()
17+
{
18+
return getpid() != s_loadPid;
19+
}
20+
#else
21+
bool isProcessForkedChild()
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 isProcessForkedChild()
33+
{
34+
int forked = RcppParallel::isProcessForkedChild() ? TRUE : FALSE;
35+
return Rf_ScalarLogical(forked);
36+
}
37+
1038
static const R_CallMethodDef CallEntries[] = {
11-
{"defaultNumThreads", (DL_FUNC) &defaultNumThreads, 0},
39+
{"defaultNumThreads", (DL_FUNC) &defaultNumThreads, 0},
40+
{"isProcessForkedChild", (DL_FUNC) &isProcessForkedChild, 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)