Skip to content

Commit da6285b

Browse files
committed
Enable OpenMP cancellation so in-loop errors exit early
Error paths inside parallel loops emit `!$omp cancel do`, but per the OpenMP spec cancel constructs are no-ops unless the cancel-var ICV is true, which requires OMP_CANCELLATION=true in the environment when the OpenMP runtime first initializes. Nothing set it, so an error raised inside a parallel loop recorded its message correctly (first-wins critical section) but every remaining iteration still ran -- wasted work, and statements after a failed in-loop check kept executing in that iteration's thread. Set OMP_CANCELLATION=true in .onLoad when unset (a pre-set value is respected). Caveats documented in ?declare: no effect if another package already initialized the OpenMP runtime, so early exit is best-effort -- error messages are always correct either way.
1 parent 590f063 commit da6285b

4 files changed

Lines changed: 41 additions & 0 deletions

File tree

R/declare.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818
#' and `OMP_DYNAMIC` (disable/enable runtime adjustment). Set them before
1919
#' calling a compiled function, e.g. `Sys.setenv(OMP_NUM_THREADS = "4")`.
2020
#'
21+
#' When an error is raised inside a parallel loop, quickr cancels the
22+
#' remaining iterations via OpenMP cancellation, which the OpenMP runtime
23+
#' only honors when `OMP_CANCELLATION=true` is set before the runtime first
24+
#' initializes in the process. quickr sets it when the package loads (unless
25+
#' already set), but this has no effect if another package initialized the
26+
#' OpenMP runtime first. Early exit is best-effort either way: the error
27+
#' message is always recorded correctly; without cancellation the remaining
28+
#' iterations simply run to completion before the error is raised.
29+
#'
2130
#' @param ... Declarations, typically calls like `type(x = double(n))`.
2231
#' @returns `NULL`, invisibly.
2332
#' @rawNamespace if (getRversion() < "4.4.0") export(declare)

R/zzz.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,14 @@
3333
S7::methods_register()
3434
asNamespace("dotty")$dotify()
3535
})
36+
# Generated error paths inside OpenMP loops emit `!$omp cancel do`, which
37+
# the OpenMP runtime treats as a no-op unless the cancel-var ICV is true.
38+
# The ICV is read from OMP_CANCELLATION when the runtime first initializes
39+
# in the process, so set it as early as quickr can; this has no effect if
40+
# another package already started the OpenMP runtime. Error *messages* are
41+
# recorded correctly either way -- cancellation only enables early exit.
42+
if (!nzchar(Sys.getenv("OMP_CANCELLATION"))) {
43+
Sys.setenv(OMP_CANCELLATION = "true")
44+
}
3645
# on_load_register_.AtNames.default()
3746
}

man/declare.Rd

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

tests/testthat/test-onload.R

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Unit test for package-load side effects
2+
3+
skip_on_cran()
4+
5+
test_that("OMP_CANCELLATION is set on load when unset, respected when preset", {
6+
withr::with_envvar(c(OMP_CANCELLATION = NA), {
7+
quickr:::.onLoad()
8+
expect_identical(Sys.getenv("OMP_CANCELLATION"), "true")
9+
})
10+
withr::with_envvar(c(OMP_CANCELLATION = "false"), {
11+
quickr:::.onLoad()
12+
expect_identical(Sys.getenv("OMP_CANCELLATION"), "false")
13+
})
14+
})

0 commit comments

Comments
 (0)