Skip to content

Commit 83e3a67

Browse files
Merge branch 'release/1.1.0'
2 parents 61c9d52 + 6e3bc2a commit 83e3a67

File tree

96 files changed

+2468
-1935
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+2468
-1935
lines changed

.github/workflows/R-CMD-check.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ jobs:
2727
- {os: ubuntu-latest, r: 'oldrel' }
2828
- {os: ubuntu-latest, r: 'oldrel-1' }
2929
- {os: ubuntu-latest, r: 'oldrel-2' }
30-
- {os: ubuntu-latest, r: '3.6' }
30+
# - {os: ubuntu-latest, r: '3.6' }
3131
- {os: ubuntu-latest, r: 'release' , plan: multicore, label: 'w/ multicore' }
3232
- {os: ubuntu-latest, r: 'release' , plan: multisession, label: 'w/ multisession' }
33+
- {os: windows-latest, r: 'release', future_version: develop, label: 'w/ future-develop' }
34+
- {os: windows-latest, r: 'release', globals_version: develop, future_version: develop, label: 'w/ future-develop' }
3335
- {os: ubuntu-latest, r: 'release', future_version: develop, label: 'w/ future-develop' }
36+
- {os: ubuntu-latest, r: 'release', globals_version: develop, future_version: develop, label: 'w/ future-develop' }
3437

3538
env:
3639
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
@@ -48,6 +51,8 @@ jobs:
4851
R_FUTURE_PLAN: ${{ matrix.config.plan }}
4952
## FIXME: Eventually update to 'R_FUTURE_GLOBALS_ONREFERENCE=error'
5053
R_FUTURE_GLOBALS_ONREFERENCE: "warning"
54+
R_PARALLELLY_VERSION: ${{ matrix.config.parallelly_version }}
55+
R_GLOBALS_VERSION: ${{ matrix.config.globals_version }}
5156
R_FUTURE_VERSION: ${{ matrix.config.future_version }}
5257

5358
steps:
@@ -63,20 +68,24 @@ jobs:
6368

6469
- uses: r-lib/actions/setup-r-dependencies@v2
6570
with:
66-
extra-packages: any::rcmdcheck
71+
extra-packages: any::rcmdcheck any::remotes
6772
needs: check
6873

6974
- name: Install package itself (special case)
7075
run: |
7176
install.packages(".", repos = NULL, type = "source") ## needed by parallel workers
7277
lapply(c("future.batchtools", "future.callr"), FUN = function(pkg) if (grepl(pkg, "${{ matrix.config.future_plan }}")) install.packages(pkg))
78+
remotes::install_github("futureverse/parallelly", ref="develop")
7379
shell: Rscript {0}
7480

7581
- name: Test with specific future version?
7682
run: |
83+
globals_version <- Sys.getenv("R_GLOBALS_VERSION")
84+
if (nzchar(globals_version)) {
85+
remotes::install_github("futureverse/globals", ref=globals_version)
86+
}
7787
future_version <- Sys.getenv("R_FUTURE_VERSION")
7888
if (nzchar(future_version)) {
79-
install.packages("remotes")
8089
remotes::install_github("futureverse/future", ref=future_version)
8190
}
8291
shell: Rscript {0}

.github/workflows/doFuture.tests.extra.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ jobs:
2020
- { r: "release", strategies: multicore }
2121
- { r: "release", strategies: multisession }
2222
- { r: "release", strategies: "future.callr::callr" }
23+
- { r: "release", strategies: "future.mirai::mirai_multisession" }
2324
- { r: "release", strategies: "future.batchtools::batchtools_local" }
2425
- { r: "release", strategies: sequential, future_version: develop, label: 'w/ future-develop' }
25-
- { r: "release", strategies: sequential, future_version: feature/evalFuture-3, label: 'w/ future-feature/evalFuture-3' }
2626

2727
env:
2828
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/test-coverage.yaml

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,62 @@
1-
on: [push]
1+
on:
2+
workflow_dispatch: # Enables manual triggering
23

3-
name: covr
4+
name: test-coverage.yaml
45

5-
jobs:
6-
covr:
7-
if: "! contains(github.event.head_commit.message, '[ci skip]')"
8-
9-
timeout-minutes: 45
6+
permissions: read-all
107

8+
jobs:
9+
test-coverage:
1110
runs-on: ubuntu-latest
12-
13-
name: covr
14-
15-
strategy:
16-
fail-fast: false
17-
1811
env:
1912
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
20-
R_KEEP_PKG_SOURCE: yes
21-
R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
22-
## Test in other locale (optional)
23-
LANGUAGE: ${{ matrix.config.language }}
24-
## R CMD check
25-
_R_CHECK_CRAN_INCOMING_: false
26-
_R_CHECK_MATRIX_DATA_: true
27-
_R_CHECK_SUGGESTS_ONLY_: true
28-
_R_CHECK_THINGS_IN_TEMP_DIR_: true
29-
RCMDCHECK_ERROR_ON: note
30-
## Specific to futures
31-
R_FUTURE_RNG_ONMISUSE: error
3213

3314
steps:
3415
- uses: actions/checkout@v4
3516

17+
- name: Assert CODECOV_TOKEN is set
18+
run: |
19+
if [[ -z "${{secrets.CODECOV_TOKEN}}" ]]; then
20+
>&2 echo "::error::ERROR: 'secrets.CODECOV_TOKEN' not set"
21+
exit 1
22+
fi
23+
3624
- uses: r-lib/actions/setup-r@v2
3725
with:
3826
use-public-rspm: true
3927

4028
- uses: r-lib/actions/setup-r-dependencies@v2
4129
with:
42-
extra-packages: |
43-
any::rcmdcheck
44-
any::remotes
45-
any::sessioninfo
46-
any::covr
47-
needs: check
30+
extra-packages: any::covr, any::xml2
31+
needs: coverage
4832

49-
- name: Install dependencies
33+
- name: Install itself
5034
run: |
51-
remotes::install_deps(dependencies = TRUE)
52-
install.packages(".", repos=NULL, type="source")
35+
install.packages(".", repos = NULL, type = "source")
5336
shell: Rscript {0}
5437

55-
- name: Session info
56-
run: |
57-
options(width = 100)
58-
pkgs <- installed.packages()[, "Package"]
59-
sessioninfo::session_info(pkgs, include_base = TRUE)
60-
shell: Rscript {0}
61-
6238
- name: Test coverage
6339
run: |
64-
## 1. Get 'Repository Upload Token' from Codecov:
65-
## https://app.codecov.io/gh/<org>/<repo>/settings
66-
## 2. Set 'CODECOV_TOKEN' in GitHub Secrets:
67-
## https://github.com/<org>/<repo>/settings/environments/
68-
coverage <- covr::package_coverage(quiet = FALSE)
69-
print(coverage)
70-
covr::codecov(coverage = coverage, token="${{secrets.CODECOV_TOKEN}}")
40+
cov <- covr::package_coverage(
41+
quiet = FALSE,
42+
clean = FALSE,
43+
install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
44+
)
45+
print(cov)
46+
covr::to_cobertura(cov)
7147
shell: Rscript {0}
48+
49+
- uses: codecov/codecov-action@v4
50+
with:
51+
fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }}
52+
file: ./cobertura.xml
53+
plugin: noop
54+
disable_search: true
55+
token: ${{ secrets.CODECOV_TOKEN }}
56+
57+
- name: Upload test results
58+
if: failure()
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: coverage-test-failures
62+
path: ${{ runner.temp }}/package

DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Package: doFuture
2-
Version: 1.0.2
2+
Version: 1.1.0
33
Title: Use Foreach to Parallelize via the Future Framework
44
Depends:
55
foreach (>= 1.5.0),
6-
future (>= 1.32.0)
6+
future (>= 1.49.0)
77
Imports:
88
future.apply,
99
globals,
@@ -25,6 +25,7 @@ License: LGPL (>= 2.1)
2525
LazyLoad: TRUE
2626
URL: https://doFuture.futureverse.org, https://github.com/futureverse/doFuture
2727
BugReports: https://github.com/futureverse/doFuture/issues
28+
Language: en-US
2829
Encoding: UTF-8
2930
RoxygenNote: 7.3.2
3031
Roxygen: list(markdown = TRUE)

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Generated by roxygen2: do not edit by hand
22

3+
S3method(with,DoPar)
34
export("%dofuture%")
45
export(registerDoFuture)
56
export(withDoRNG)
@@ -8,9 +9,12 @@ importFrom(foreach,getErrorIndex)
89
importFrom(foreach,getErrorValue)
910
importFrom(foreach,getResult)
1011
importFrom(foreach,makeAccum)
12+
importFrom(foreach,registerDoSEQ)
1113
importFrom(foreach,setDoPar)
1214
importFrom(future,Future)
1315
importFrom(future,FutureError)
16+
importFrom(future,FutureInterruptError)
17+
importFrom(future,cancel)
1418
importFrom(future,future)
1519
importFrom(future,getGlobalsAndPackages)
1620
importFrom(future,nbrOfWorkers)

NEWS.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
1+
# Version 1.1.0 (2025-05-19)
2+
3+
## New Features
4+
5+
* `foreach()` with `%dofuture%` or `%dopar%` will now exit early as
6+
soon as it detects an error in one of the iterations. It will also
7+
exit early if it detects a user interrupt (e.g. Ctrl-C). Remaining
8+
iterations are canceled and interrupted if the future backend
9+
supports it, releasing compute resources sooner and avoiding having
10+
to wait for remaining futures to be resolved.
11+
12+
* Add support for `foreach(..., .verbose = TRUE)`, which is part of
13+
the official `foreach()` API. When set, detailed debugging
14+
information useful for troubleshooting is outputted to standard
15+
error through R's message condition mechanism.
16+
17+
* Add support for `with(registerDoFuture(), { ... })` to temporarily
18+
use the doFuture adapter.
19+
20+
* Add `registerDoFuture(flavor = "%dofuture%")`, which makes the
21+
`%dopar%` infix operator behave as if `%dofuture%` would have been
22+
used. This makes it possible for you to use `%dofuture%`, even if
23+
you do not have the option to update the code that uses
24+
`%dopar%`. For instance, if you use one of the many packages that
25+
uses `foreach(...) %dopar% { ... }` internally, this flavor allows
26+
you to effectively make that the same as `foreach(...) %dofuture%
27+
{ ... }`.
28+
29+
## Bug Fixes
30+
31+
* `registerDoFuture()` would return an invalid `DoPar` object if
32+
there was no `%dopar%` registered. Now it returns an `DoPar` object
33+
as if `foreach::registerDoSEQ()` had been called before.
34+
35+
136
# Version 1.0.2 (2025-03-15)
237

338
## Miscellaneous

R/000.import.R

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import_from <- function(name, default = NULL, package) {
1+
import_from <- function(name, mode = "function", default = NULL, package) {
22
ns <- getNamespace(package)
3-
if (exists(name, mode = "function", envir = ns, inherits = FALSE)) {
4-
get(name, mode = "function", envir = ns, inherits = FALSE)
3+
if (exists(name, mode = mode, envir = ns, inherits = FALSE)) {
4+
get(name, mode = mode, envir = ns, inherits = FALSE)
55
} else if (!is.null(default)) {
66
default
77
} else {
8-
stop(sprintf("No such '%s' function: %s()", package, name))
8+
stop(sprintf("No such '%s' %s: %s()", package, mode, name))
99
}
1010
}
1111

12-
import_future <- function(name, default = NULL) {
13-
import_from(name, default = default, package = "future")
14-
}
15-
16-
import_future.apply <- function(name, default = NULL) {
17-
import_from(name, default = default, package = "future.apply")
12+
import_future <- function(name, mode = "function", default = NULL) {
13+
import_from(name, mode = mode, default = default, package = "future")
1814
}

R/001.import_future_functions.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## To be imported from 'future', if available
2+
.debug <- NULL
3+
4+
## Import private functions from 'future'
5+
import_future_functions <- function() {
6+
.debug <<- import_future(".debug", mode = "environment", default = new.env(parent = emptyenv()))
7+
}

R/condition-handlers.R

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#' @importFrom future FutureInterruptError
2+
onDoFutureInterrupt <- function(int, op_name = "%dofuture%", debug = FALSE) {
3+
if (debug) {
4+
mdebug_push("onDoFutureInterrupt() ...")
5+
mdebug(sprintf("Received <%s>", class(int)[1]))
6+
on.exit(mdebug_pop())
7+
}
8+
9+
when <- Sys.time()
10+
host <- Sys.info()[["nodename"]]
11+
pid <- Sys.getpid()
12+
msg <- sprintf("'%s' interrupted at %s, while running on %s (pid %s)", op_name, format(when, format = "%FT%T"), sQuote(host), pid)
13+
14+
## By signaling the interrupt as an error, the next handler, which should
15+
## be onDoFutureError(), will take care of canceling outstanding futures
16+
stop(FutureInterruptError(msg))
17+
} ## onDoFutureInterrupt()
18+
19+
20+
21+
#' @importFrom future cancel resolve value
22+
onDoFutureError <- function(ex, futures, debug = FALSE) {
23+
if (debug) {
24+
mdebug_push("onDoFutureError() ...")
25+
mdebug(sprintf("Received <%s>", class(ex)[1]))
26+
on.exit(mdebug_pop())
27+
}
28+
29+
## Canceling all futures
30+
msg <- sprintf("Caught %s. Canceling all iterations ...", class(ex)[1])
31+
warning(msg, immediate. = TRUE, call. = FALSE)
32+
futures <- cancel(futures)
33+
34+
## Make sure all workers finish before continuing
35+
futures <- resolve(futures)
36+
37+
## Collect all results
38+
for (f in futures) tryCatch(value(f), error = identity)
39+
40+
if (debug) mdebug(sprintf("Signaling: <%s>", class(ex)[1]))
41+
42+
stop(ex)
43+
} ## onDoFutureError()

0 commit comments

Comments
 (0)