Skip to content

Commit ead3177

Browse files
Merge branch 'release/0.12.0'
2 parents 355efac + 74676c8 commit ead3177

24 files changed

+265
-142
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,10 @@ jobs:
8282

8383
- name: Check
8484
run: |
85+
if (getRversion() < "3.5.0") Sys.setenv(RCMDCHECK_ERROR_ON = "error")
8586
rcmdcheck::rcmdcheck(
86-
args = c("--no-manual", "--as-cran"),
87+
build_args = if (getRversion() < "3.5.0") "--no-build-vignettes",
88+
args = c("--no-manual", "--as-cran", if (getRversion() < "3.5.0") c("--no-vignettes", "--no-build-vignettes", "--ignore-vignettes")),
8789
check_dir = "check"
8890
)
8991
shell: Rscript {0}

.github/workflows/covr.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
covr:
77
if: "! contains(github.event.head_commit.message, '[ci skip]')"
88

9-
timeout-minutes: 10
9+
timeout-minutes: 20
1010

1111
runs-on: ubuntu-20.04
1212

@@ -42,7 +42,7 @@ jobs:
4242
shell: Rscript {0}
4343

4444
- name: Cache R packages
45-
uses: actions/cache@v2
45+
uses: actions/cache@v3
4646
with:
4747
path: ${{ env.R_LIBS_USER }}
4848
key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}

.github/workflows/future_tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
shell: Rscript {0}
4949

5050
- name: Cache R packages
51-
uses: actions/cache@v2
51+
uses: actions/cache@v3
5252
with:
5353
path: ${{ env.R_LIBS_USER }}
5454
key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ revdep/data.sqlite
2222
revdep/checks/*
2323
revdep/library/*
2424
docs/
25+
.Rdump

DESCRIPTION

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
Package: future.batchtools
2-
Version: 0.11.0
2+
Version: 0.12.0
33
Depends:
44
R (>= 3.2.0),
55
parallelly,
6-
future (>= 1.29.0)
6+
future (>= 1.31.0)
77
Imports:
88
batchtools (>= 0.9.13),
99
utils
1010
Suggests:
11+
globals,
1112
future.apply,
1213
listenv,
1314
markdown,

NEWS.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# Version 0.12.0 [2023-02-24]
2+
3+
## New Features
4+
5+
* Improved performance of batchtools futures by avoiding re-checking
6+
the **batchtools** status if the **batchtools** job has already
7+
been observed to be resolved. Checking the **batchtools** status
8+
is fairly expense, especially since each status check queries a set
9+
of files on the file system.
10+
11+
* Improved performance of batchtools futures by making the removal of
12+
the **batchtools** registry about 10-15 times faster.
13+
14+
## Bug Fixes
15+
16+
* `run()` for `BatchtoolsFuture` would update the RNG state, if the
17+
future would attach packages.
18+
19+
120
# Version 0.11.0 [2022-12-13]
221

322
## Significant Changes
@@ -27,7 +46,7 @@
2746

2847
* `run()` for BatchtoolsFuture now produce an informative
2948
BatchtoolsFutureError in case `batchtools::submitJobs()` fails, for
30-
instance, due to invalid job-scheduler rsource specifications.
49+
instance, due to invalid job-scheduler resource specifications.
3150

3251
* Add BatchtoolsFuture subclasses; abstract
3352
BatchtoolsUniprocessFuture, abstract BatchtoolsMultiprocessFuture,
@@ -281,7 +300,6 @@
281300

282301
* `print()` for BatchtoolsFuture returns the object invisibly.
283302

284-
285303
## Bug Fixes
286304

287305
* Calling `future_lapply()` with functions containing globals part of
@@ -295,6 +313,7 @@
295313

296314
# Version 0.5.0 [2017-06-02]
297315

316+
298317
# Version 0.4.0 [2017-05-16]
299318

300319
## New Features
@@ -359,4 +378,3 @@
359378

360379

361380
# Version 0.1.0 [2017-02-11]
362-

R/000.import.R

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import_from <- function(name, mode = "function", default = NULL, package) {
2+
ns <- getNamespace(package)
3+
if (exists(name, mode = mode, envir = ns, inherits = FALSE)) {
4+
get(name, mode = mode, envir = ns, inherits = FALSE)
5+
} else if (!is.null(default)) {
6+
default
7+
} else {
8+
stop(sprintf("No such '%s' %s: %s()", package, mode, name))
9+
}
10+
}
11+
12+
import_future <- function(...) {
13+
import_from(..., package = "future")
14+
}
15+
16+
import_parallelly <- function(...) {
17+
import_from(..., package = "parallelly")
18+
}

R/BatchtoolsFuture-class.R

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828
#' process one future at the time (`workers = 1L`), whereas HPC backends,
2929
#' where futures are resolved via separate jobs on a scheduler, can have
3030
#' multiple workers. In the latter, the default is `workers = NULL`, which
31-
#' will resolve to `getOption("future.batchtools.workers")`. If neither
32-
#' are specified, then the default is `100`.
31+
#' will resolve to
32+
#' \code{getOption("\link{future.batchtools.workers}")}.
33+
#' If neither are specified, then the default is `100`.
3334
#'
3435
#' @param finalize If TRUE, any underlying registries are
3536
#' deleted when this object is garbage collected, otherwise not.
@@ -230,6 +231,14 @@ status <- function(future, ...) {
230231
})
231232
} ## get_status()
232233

234+
## Known to be in its final state?
235+
if (getOption("future.batchtools.status.cache", TRUE)) {
236+
status <- future$.status
237+
if (identical(status, c("defined", "finished", "started", "submitted"))) {
238+
return(status)
239+
}
240+
}
241+
233242
config <- future$config
234243
reg <- config$reg
235244
if (!inherits(reg, "Registry")) return(NA_character_)
@@ -251,6 +260,9 @@ status <- function(future, ...) {
251260
if (result_has_errors(result)) status <- unique(c("error", status))
252261
}
253262

263+
## Cache result
264+
future$.status <- status
265+
254266
if (debug) mdebug("- status: ", paste(sQuote(status), collapse = ", "))
255267

256268
status
@@ -335,48 +347,82 @@ loggedOutput.BatchtoolsFuture <- function(future, ...) {
335347
#' @export
336348
#' @keywords internal
337349
resolved.BatchtoolsFuture <- function(x, ...) {
338-
## Has internal future state already been switched to be resolved
339-
resolved <- NextMethod()
340-
if (resolved) return(TRUE)
350+
signalEarly <- import_future("signalEarly")
351+
352+
## Is value already collected?
353+
if (!is.null(x$result)) {
354+
## Signal conditions early?
355+
signalEarly(x, ...)
356+
return(TRUE)
357+
}
358+
359+
## Assert that the process that created the future is
360+
## also the one that evaluates/resolves/queries it.
361+
assertOwner <- import_future("assertOwner")
362+
assertOwner(x)
341363

342364
## If not, checks the batchtools registry status
343365
resolved <- finished(x)
344366
if (is.na(resolved)) return(FALSE)
345-
367+
368+
## Signal conditions early? (happens only iff requested)
369+
if (resolved) signalEarly(x, ...)
370+
346371
resolved
347372
}
348373

349374
#' @importFrom future result
350375
#' @export
351376
#' @keywords internal
352377
result.BatchtoolsFuture <- function(future, cleanup = TRUE, ...) {
378+
379+
debug <- getOption("future.debug", FALSE)
380+
if (debug) {
381+
mdebug("result() for BatchtoolsFuture ...")
382+
on.exit(mdebug("result() for BatchtoolsFuture ... done"), add = TRUE)
383+
}
384+
353385
## Has the value already been collected?
354386
result <- future$result
355-
if (inherits(result, "FutureResult")) return(result)
387+
if (inherits(result, "FutureResult")) {
388+
if (debug) mdebug("- FutureResult already collected")
389+
return(result)
390+
}
356391

357392
## Has the value already been collected? - take two
358393
if (future$state %in% c("finished", "failed", "interrupted")) {
394+
if (debug) mdebug("- FutureResult already collected - take 2")
359395
return(NextMethod())
360396
}
361397

362398
if (future$state == "created") {
399+
if (debug) mdebug("- starting future ...")
363400
future <- run(future)
401+
if (debug) mdebug("- starting future ... done")
364402
}
365403

404+
if (debug) mdebug("- getting batchtools status")
366405
stat <- status(future)
367406
if (is_na(stat)) {
368407
label <- future$label
369408
if (is.null(label)) label <- "<none>"
370409
stopf("The result no longer exists (or never existed) for Future ('%s') of class %s", label, paste(sQuote(class(future)), collapse = ", ")) #nolint
371410
}
372411

412+
if (debug) mdebug("- waiting for batchtools job to finish ...")
373413
result <- await(future, cleanup = FALSE)
414+
if (debug) mdebug("- waiting for batchtools job to finish ... done")
374415
stop_if_not(inherits(result, "FutureResult"))
375416
future$result <- result
376417
future$state <- "finished"
377418

378-
if (cleanup) delete(future)
419+
if (cleanup) {
420+
if (debug) mdebugf("- delete %s ...", class(future)[1])
421+
delete(future)
422+
if (debug) mdebugf("- delete %s ... done", class(future)[1])
423+
}
379424

425+
if (debug) mdebug("- NextMethod()")
380426
NextMethod()
381427
}
382428

@@ -454,7 +500,9 @@ run.BatchtoolsFuture <- function(future, ...) {
454500
## will have the same state of (loaded, attached) packages.
455501

456502
reg$packages <- packages
457-
saveRegistry(reg = reg)
503+
with_stealth_rng({
504+
saveRegistry(reg = reg)
505+
})
458506

459507
mdebugf("Attaching %d packages (%s) ... DONE",
460508
length(packages), hpaste(sQuote(packages)))
@@ -538,6 +586,11 @@ run.BatchtoolsFuture <- function(future, ...) {
538586
## 6. Rerserve worker for future
539587
registerFuture(future)
540588

589+
## 7. Trigger early signalling
590+
if (inherits(future, "BatchtoolsUniprocessFuture")) {
591+
resolved(future)
592+
}
593+
541594
invisible(future)
542595
} ## run()
543596

@@ -553,6 +606,7 @@ await <- function(future, cleanup = TRUE,
553606
stop_if_not(is.finite(alpha), alpha > 0)
554607

555608
debug <- getOption("future.debug", FALSE)
609+
if (debug) mdebug("future.batchtools:::await() ...")
556610

557611
expr <- future$expr
558612
config <- future$config
@@ -571,10 +625,12 @@ await <- function(future, cleanup = TRUE,
571625

572626
res <- waitForJobs(ids = jobid, timeout = timeout, sleep = sleep_fcn,
573627
stop.on.error = FALSE, reg = reg)
574-
mdebugf("- batchtools::waitForJobs(): %s", res)
628+
if (debug) mdebugf("- batchtools::waitForJobs(): %s", res)
575629
stat <- status(future)
576-
mdebugf("- status(): %s", paste(sQuote(stat), collapse = ", "))
577-
mdebug("batchtools::waitForJobs() ... done")
630+
if (debug) {
631+
mdebugf("- status(): %s", paste(sQuote(stat), collapse = ", "))
632+
mdebug("batchtools::waitForJobs() ... done")
633+
}
578634

579635
finished <- is_na(stat) || any(c("finished", "error", "expired") %in% stat)
580636

@@ -587,20 +643,23 @@ await <- function(future, cleanup = TRUE,
587643
label <- future$label
588644
if (is.null(label)) label <- "<none>"
589645
if ("finished" %in% stat) {
590-
mdebug("- batchtools::loadResult() ...")
646+
if (debug) mdebug("- batchtools::loadResult() ...")
591647
result <- loadResult(reg = reg, id = jobid)
592-
mdebug("- batchtools::loadResult() ... done")
648+
if (debug) mdebug("- batchtools::loadResult() ... done")
649+
593650
if (inherits(result, "FutureResult")) {
594651
prototype_fields <- c(prototype_fields, "batchtools_log")
595-
result[["batchtools_log"]] <- try({
596-
mdebug("- batchtools::getLog() ...")
597-
on.exit(mdebug("- batchtools::getLog() ... done"))
652+
result[["batchtools_log"]] <- try(local({
653+
if (debug) {
654+
mdebug("- batchtools::getLog() ...")
655+
on.exit(mdebug("- batchtools::getLog() ... done"))
656+
}
598657
## Since we're already collected the results, the log file
599658
## should already exist, if it exists. Because of this,
600659
## only poll for the log file for a second before giving up.
601660
reg$cluster.functions$fs.latency <- 1.0
602661
getLog(id = jobid, reg = reg)
603-
}, silent = TRUE)
662+
}), silent = TRUE)
604663
if (result_has_errors(result)) cleanup <- FALSE
605664
}
606665
} else if ("error" %in% stat) {
@@ -644,6 +703,8 @@ await <- function(future, cleanup = TRUE,
644703
delete(future, delta = 0.5 * delta, ...)
645704
}
646705

706+
if (debug) mdebug("future.batchtools:::await() ... done")
707+
647708
result
648709
} # await()
649710

@@ -768,6 +829,10 @@ delete.BatchtoolsFuture <- function(future,
768829
with_stealth_rng({
769830
interval <- delta
770831
for (kk in seq_len(times)) {
832+
try(unlink(path, recursive = TRUE), silent = FALSE)
833+
if (!file_test("-d", path)) break
834+
try(removeRegistry(wait = 0.0, reg = reg), silent = FALSE)
835+
if (!file_test("-d", path)) break
771836
try(clearRegistry(reg = reg), silent = TRUE)
772837
try(removeRegistry(wait = 0.0, reg = reg), silent = FALSE)
773838
if (!file_test("-d", path)) break

R/options.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,5 @@ update_package_options <- function(debug = FALSE) {
165165
update_package_option("future.batchtools.expiration.tail", mode = "integer", debug = debug)
166166
update_package_option("future.batchtools.output", mode = "logical", debug = debug)
167167
update_package_option("future.batchtools.workers", mode = "numeric", debug = debug)
168+
update_package_option("future.batchtools.status.cache", mode = "logical", default = TRUE, debug = debug)
168169
}

R/temp_registry.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ temp_registry <- local({
3333
if (length(config) > 0L) {
3434
names <- names(config)
3535
for (name in names) reg[[name]] <- config[[name]]
36-
saveRegistry(reg)
36+
with_stealth_rng({
37+
saveRegistry(reg)
38+
})
3739
}
3840

3941
reg

0 commit comments

Comments
 (0)