Skip to content

Commit c521a85

Browse files
Merge branch 'release/1.1.1'
2 parents 83e3a67 + e0f9ef4 commit c521a85

18 files changed

+458
-96
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: doFuture
2-
Version: 1.1.0
2+
Version: 1.1.1
33
Title: Use Foreach to Parallelize via the Future Framework
44
Depends:
55
foreach (>= 1.5.0),

NEWS.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
# Version 1.1.1 (2025-06-06)
2+
3+
## Bug Fixes
4+
5+
* `foreach(...) %dofuture% { ... }` would signal errors, despite
6+
using `.errorhandling = "pass"` or `.errorhandling = "remove"`.
7+
This was originally by design, because "all errors should be
8+
errors", but I have since reconsidered and concluded it was a
9+
design mistake. Now `.errorhandling` works also with `%dofuture%`.
10+
11+
112
# Version 1.1.0 (2025-05-19)
213

314
## New Features
@@ -15,7 +26,9 @@
1526
error through R's message condition mechanism.
1627

1728
* Add support for `with(registerDoFuture(), { ... })` to temporarily
18-
use the doFuture adapter.
29+
use the doFuture adapter. Can also be used as
30+
`with(registerDoFuture(), local = TRUE)` to temporarily register it
31+
within a function.
1932

2033
* Add `registerDoFuture(flavor = "%dofuture%")`, which makes the
2134
`%dopar%` infix operator behave as if `%dofuture%` would have been

R/001.import_future_functions.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
## To be imported from 'future', if available
22
.debug <- NULL
33

4+
make_rng_seeds <- import_future("make_rng_seeds")
5+
next_random_seed <- import_future("next_random_seed")
6+
set_random_seed <- import_future("set_random_seed")
7+
48
## Import private functions from 'future'
59
import_future_functions <- function() {
610
.debug <<- import_future(".debug", mode = "environment", default = new.env(parent = emptyenv()))

R/condition-handlers.R

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#' @importFrom future FutureInterruptError
2-
onDoFutureInterrupt <- function(int, op_name = "%dofuture%", debug = FALSE) {
2+
onInterrupt <- function(int, op_name, debug = FALSE) {
33
if (debug) {
4-
mdebug_push("onDoFutureInterrupt() ...")
4+
mdebug_push("onInterrupt() ...")
55
mdebug(sprintf("Received <%s>", class(int)[1]))
66
on.exit(mdebug_pop())
77
}
@@ -12,16 +12,16 @@ onDoFutureInterrupt <- function(int, op_name = "%dofuture%", debug = FALSE) {
1212
msg <- sprintf("'%s' interrupted at %s, while running on %s (pid %s)", op_name, format(when, format = "%FT%T"), sQuote(host), pid)
1313

1414
## By signaling the interrupt as an error, the next handler, which should
15-
## be onDoFutureError(), will take care of canceling outstanding futures
15+
## be onError(), will take care of canceling outstanding futures
1616
stop(FutureInterruptError(msg))
17-
} ## onDoFutureInterrupt()
17+
}
1818

1919

2020

2121
#' @importFrom future cancel resolve value
22-
onDoFutureError <- function(ex, futures, debug = FALSE) {
22+
onError <- function(ex, futures, debug = FALSE) {
2323
if (debug) {
24-
mdebug_push("onDoFutureError() ...")
24+
mdebug_push("onError() ...")
2525
mdebug(sprintf("Received <%s>", class(ex)[1]))
2626
on.exit(mdebug_pop())
2727
}
@@ -40,4 +40,4 @@ onDoFutureError <- function(ex, futures, debug = FALSE) {
4040
if (debug) mdebug(sprintf("Signaling: <%s>", class(ex)[1]))
4141

4242
stop(ex)
43-
} ## onDoFutureError()
43+
}

R/doFuture.R

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,9 @@ function(obj, expr, envir, data) { #nolint
378378

379379
fs
380380
}, interrupt = function(int) {
381-
onDoFutureInterrupt(int, op_name = "%dopar%", debug = debug)
381+
onInterrupt(int, op_name = "%dopar%", debug = debug)
382382
}, error = function(e) {
383-
onDoFutureError(e, futures = fs, debug = debug)
383+
onError(e, futures = fs, debug = debug)
384384
}) ## tryCatch()
385385
rm(list = c("chunks", "globals", "packages", "labels"))
386386
stop_if_not(length(fs) == nchunks)
@@ -430,7 +430,7 @@ function(obj, expr, envir, data) { #nolint
430430
f <- fs[[idx]]
431431
label <- f$label
432432
if (is.null(label)) label <- "<none>"
433-
message <- sprintf("UNRELIABLE VALUE: One of the foreach() iterations (%s) unexpectedly generated random numbers without declaring so. There is a risk that those random numbers are not statistically sound and the overall results might be invalid. To fix this, use '%%dorng%%' from the 'doRNG' package instead of '%%dopar%%'. This ensures that proper, parallel-safe random numbers are produced via the L'Ecuyer-CMRG method. To disable this check, set option 'doFuture.rng.onMisuse' to \"ignore\".", sQuote(label))
433+
message <- sprintf("UNRELIABLE VALUE: One of the foreach() iterations (%s) unexpectedly generated random numbers without declaring so. There is a risk that those random numbers are not statistically sound and the overall results might be invalid. To fix this, use '%%dorng%%' from the 'doRNG' package instead of '%%dopar%%'. This ensures that proper, parallel-safe random numbers are produced. To disable this check, set option 'doFuture.rng.onMisuse' to \"ignore\".", sQuote(label))
434434
cond$message <- message
435435
if (inherits(cond, "warning")) {
436436
warning(cond)
@@ -448,9 +448,9 @@ function(obj, expr, envir, data) { #nolint
448448
resolve(fs, result = TRUE, stdout = TRUE, signal = TRUE)
449449
}
450450
}, interrupt = function(int) {
451-
onDoFutureInterrupt(int, op_name = "%dopar%", debug = debug)
451+
onInterrupt(int, op_name = "%dopar%", debug = debug)
452452
}, error = function(e) {
453-
onDoFutureError(e, futures = fs, debug = debug)
453+
onError(e, futures = fs, debug = debug)
454454
}) ## tryCatch()
455455

456456
## Gather values

R/doFuture2.R

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,24 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
7070
options[name] <- opts[name]
7171
}
7272
options(future.disposable = NULL)
73-
74-
errors <- options[["errors"]]
75-
if (is.null(errors)) {
76-
errors <- "future"
77-
} else if (is.character(errors)) {
78-
if (length(errors) != 1L) {
79-
stop(sprintf("Element 'errors' of '.options.future' should be of length one': [n = %d] %s", length(errors), paste(sQuote(errors), collapse = ", ")))
80-
}
81-
if (! errors %in% c("future", "foreach")) {
82-
stop(sprintf("Unknown value of '.options.future' element 'errors': %s", sQuote(errors)))
83-
}
73+
74+
error_handling <- obj$errorHandling
75+
if (!identical(error_handling, "stop")) {
76+
errors <- "foreach"
8477
} else {
85-
stop("Unknown type of '.options.future' element 'errors': ", mode(errors))
78+
errors <- options[["errors"]]
79+
if (is.null(errors)) {
80+
errors <- "future"
81+
} else if (is.character(errors)) {
82+
if (length(errors) != 1L) {
83+
stop(sprintf("Element 'errors' of '.options.future' should be of length one': [n = %d] %s", length(errors), paste(sQuote(errors), collapse = ", ")))
84+
}
85+
if (! errors %in% c("future", "foreach")) {
86+
stop(sprintf("Unknown value of '.options.future' element 'errors': %s", sQuote(errors)))
87+
}
88+
} else {
89+
stop("Unknown type of '.options.future' element 'errors': ", mode(errors))
90+
}
8691
}
8792

8893

@@ -172,7 +177,6 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
172177
if (is.null(seed)) seed <- eval(formals(future)$seed)
173178
if (debug) mdebugf("seed = %s", deparse(seed))
174179

175-
make_rng_seeds <- import_future("make_rng_seeds")
176180
seeds <- make_rng_seeds(nX, seed = seed)
177181

178182
## Future expression (with or without setting the RNG state) and
@@ -184,8 +188,6 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
184188
mdebug("RNG seeds:")
185189
mstr(seeds)
186190
}
187-
next_random_seed <- import_future("next_random_seed")
188-
set_random_seed <- import_future("set_random_seed")
189191
## If RNG seeds are used (given or generated), make sure to reset
190192
## the RNG state afterward
191193
oseed <- next_random_seed()
@@ -452,9 +454,9 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
452454

453455
fs
454456
}, interrupt = function(int) {
455-
onDoFutureInterrupt(int, debug = debug)
457+
onInterrupt(int, op_name = "%dofuture%", debug = debug)
456458
}, error = function(e) {
457-
onDoFutureError(e, futures = fs, debug = debug)
459+
onError(e, futures = fs, debug = debug)
458460
}) ## tryCatch()
459461
rm(list = c("globals", "packages", "labels", "seeds"))
460462
stop_if_not(length(fs) == nchunks)
@@ -519,7 +521,7 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
519521
iterations <- seq_to_human(chunk)
520522
iterations <- sprintf("At least one of iterations %s", iterations)
521523
}
522-
message <- sprintf("UNRELIABLE VALUE: %s of the foreach() %%dofuture%% { ... }, part of chunk #%d (%s), unexpectedly generated random numbers without declaring so. There is a risk that those random numbers are not statistically sound and the overall results might be invalid. To fix this, specify foreach() argument '.options.future = list(seed = TRUE)'. This ensures that proper, parallel-safe random numbers are produced via the L'Ecuyer-CMRG method. To disable this check, set option 'doFuture.rng.onMisuse' to \"ignore\".", iterations, idx, sQuote(label))
524+
message <- sprintf("UNRELIABLE VALUE: %s of the foreach() %%dofuture%% { ... }, part of chunk #%d (%s), unexpectedly generated random numbers without declaring so. There is a risk that those random numbers are not statistically sound and the overall results might be invalid. To fix this, specify foreach() argument '.options.future = list(seed = TRUE)'. This ensures that proper, parallel-safe random numbers are produced. To disable this check, set option 'doFuture.rng.onMisuse' to \"ignore\".", iterations, idx, sQuote(label))
523525
cond$message <- message
524526
if (inherits(cond, "warning")) {
525527
warning(cond)
@@ -534,9 +536,9 @@ doFuture2 <- function(obj, expr, envir, data) { #nolint
534536
}
535537
values
536538
}, interrupt = function(int) {
537-
onDoFutureInterrupt(int, debug = debug)
539+
onInterrupt(int, op_name = "%dofuture%", debug = debug)
538540
}, error = function(e) {
539-
onDoFutureError(e, futures = fs, debug = debug)
541+
onError(e, futures = fs, debug = debug)
540542
}) ## tryCatch()
541543
rm(list = "chunks")
542544
stop_if_not(length(values) == nchunks)
@@ -633,7 +635,6 @@ elements in 'X' (= %d). There were in total %d chunks and %d elements (%s)",
633635
## ... or as traditionally with %dopar%, which throws an error
634636
## or return the combined results
635637
## NOTE: This is adopted from foreach:::doSEQ()
636-
error_handling <- obj$errorHandling
637638
if (debug) {
638639
mdebugf("processing errors (handler = %s)", sQuote(error_handling))
639640
}

R/dofuture_OP.R

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414
#' When using `%dofuture%`:
1515
#'
1616
#' * there is no need to use `registerDoFuture()`
17+
#'
1718
#' * there is no need to use `%dorng%` of the **doRNG** package
1819
#' (but you need to specify `.options.future = list(seed = TRUE)`
1920
#' whenever using random numbers in the `expr` expression)
21+
#'
2022
#' * global variables and packages are identified automatically by
2123
#' the \pkg{future} framework
22-
#' * errors are relayed as-is (with `%dopar%` they captured and modified)
24+
#'
25+
#' * errors are relayed as-is with the default `.errorhandling = "stop"`,
26+
#' whereas with `%dopar%` they are captured and modified.
2327
#'
2428
#'
2529
#' @section Global variables and packages:
@@ -59,12 +63,12 @@
5963
#' workers, and scheduling ("chunking") strategy.
6064
#'
6165
#' RNG reproducibility is achieved by pregenerating the random seeds for all
62-
#' iterations by using L'Ecuyer-CMRG RNG streams. In each
66+
#' iterations by parallel RNG streams. In each
6367
#' iteration, these seeds are set before evaluating the foreach expression.
6468
#' _Note, for large number of iterations this may introduce a large overhead._
6569
#'
6670
#' If `seed = TRUE`, then \code{\link[base:Random]{.Random.seed}}
67-
#' is used if it holds a L'Ecuyer-CMRG RNG seed, otherwise one is created
71+
#' is used if it holds a parallel RNG seed, otherwise one is created
6872
#' randomly.
6973
#'
7074
#' If `seed = FALSE`, it is expected that none of the foreach iterations
@@ -75,9 +79,9 @@
7579
#' whether random numbers were generated or not.
7680
#'
7781
#' As input, `seed` may also take a fixed initial seed (integer),
78-
#' either as a full L'Ecuyer-CMRG RNG seed (vector of 1+6 integers), or
79-
#' as a seed generating such a full L'Ecuyer-CMRG seed. This seed will
80-
#' be used to generated one L'Ecuyer-CMRG RNG stream for each iteration.
82+
#' either as a full parallel RNG seed (vector of 1+6 integers), or
83+
#' as a seed generating such a full seed. This seed will
84+
#' be used to generated one parallel RNG stream for each iteration.
8185
#'
8286
#' An alternative to specifying the `seed` option via `.options.future`,
8387
#' is to use the \code{\link[future:%seed%]{%seed%}} operator. See

R/registerDoFuture.R

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#' Use the Foreach `%dopar%` Adapter with Futures
22
#'
3+
#' @description
34
#' The `registerDoFuture()` function makes the
45
#' \code{\link[foreach:\%dopar\%]{\%dopar\%}} operator of the
56
#' \pkg{foreach} package to process foreach iterations via any of
@@ -10,9 +11,12 @@
1011
#' and packages making using the \pkg{foreach} framework._
1112
#' Neither the developer nor the end user has to change any code.
1213
#'
14+
#' _**Recommendation**: If you have the option, use [`%dofuture%`] instead
15+
#' of `%dopar%`, for all the benefits explained in its help page._
16+
#'
1317
#' @param flavor Control how the adapter should behave.
1418
#' If `"%dopar%"`, it behaves as a classical foreach adapter.
15-
#' If `"%dofuture%"`, it behaves as if `%dofuture%` would have
19+
#' If `"%dofuture%"`, it behaves as if [`%dofuture%`] would have
1620
#' been used instead of `%dopar%`.
1721
#'
1822
#' @section Parallel backends:
@@ -63,12 +67,13 @@
6367
#' all packages automatically (via static code inspection). This is done
6468
#' exactly the same way regardless of future backend.
6569
#' This automatic identification of globals and packages is illustrated
66-
#' by the below example, which does _not_ specify
70+
#' by the below example, which does _not_ have specify
6771
#' `.export = c("my_stat")`. This works because the future framework
6872
#' detects that function `my_stat()` is needed and makes sure it is
6973
#' exported. If you would use, say, `cl <- parallel::makeCluster(2)`
7074
#' and `doParallel::registerDoParallel(cl)`, you would get a run-time
71-
#' error on \code{Error in \{ : task 1 failed - \"could not find function "my_stat" ...}.
75+
#' error on \code{Error in \{ : task 1 failed - \"could not find function
76+
#' "my_stat" ...}.
7277
#'
7378
#' Having said this, note that, in order for your "foreach" code to work
7479
#' everywhere and with other types of foreach adapters as well, you may
@@ -146,12 +151,11 @@
146151
#'
147152
#' However, if you think it necessary to register the \pkg{doFuture} backend
148153
#' in a function, please make sure to undo your changes when exiting the
149-
#' function. This can be done using:
154+
#' function. This can be achieve by:
150155
#'
151156
#' \preformatted{
152-
#' oldDoPar <- registerDoFuture()
153-
#' on.exit(with(oldDoPar, foreach::setDoPar(fun=fun, data=data, info=info)), add = TRUE)
154-
#' [...]
157+
#' with(registerDoFuture(), local = TRUE)
158+
#' ...
155159
#' }
156160
#'
157161
#' This is important, because the end-user might have already registered a

R/withDoRNG.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949
#' risk that those random numbers are not statistically sound and the overall
5050
#' results might be invalid. To fix this, use '%dorng%' from the 'doRNG'
5151
#' package instead of '%dopar%'. This ensures that proper, parallel-safe
52-
#' random numbers are produced via the L'Ecuyer-CMRG method. To disable this
53-
#' check, set option 'doFuture.rng.onMisuse' to "ignore".
52+
#' random numbers are produced. To disable this check, set option
53+
#' 'doFuture.rng.onMisuse' to "ignore".
5454
#' >
5555
#' ```
5656
#'

incl/with.R

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@ with(registerDoFuture(), {
22
y <- foreach(x = 1:3) %dopar% { x^2 }
33
})
44

5-
65
a_fcn_in_a_pkg <- function(xs) {
76
foreach(x = xs) %dopar% { x^2 }
87
}
98

109
with(registerDoFuture(flavor = "%dofuture%"), {
1110
y <- a_fcn_in_a_pkg(1:3)
1211
})
12+
13+
14+
my_fcn <- function(xs) {
15+
## Use registerDoFuture() for this function only and then
16+
## revert back to the previously set foreach adapter
17+
with(registerDoFuture(), local = TRUE)
18+
19+
foreach(x = xs) %dopar% { x^2 }
20+
}
21+

0 commit comments

Comments
 (0)