|
| 1 | +#' @tags backend sequential multisession cluster |
| 2 | +#' @tags detritus-connections |
| 3 | + |
| 4 | +library(future) |
| 5 | + |
| 6 | +SequentialFutureBackend <- future:::SequentialFutureBackend |
| 7 | +MultisessionFutureBackend <- future:::MultisessionFutureBackend |
| 8 | +ClusterFutureBackend <- future:::ClusterFutureBackend |
| 9 | +MulticoreFutureBackend <- future:::MulticoreFutureBackend |
| 10 | +stopWorkers <- future:::stopWorkers |
| 11 | + |
| 12 | +## NOTE: A backend factory is reached via attr(<strategy>, "factory"), which |
| 13 | +## holds the function object as it was when the package namespace was built. |
| 14 | +## Calls made through that copy are not attributed to these functions, which |
| 15 | +## is why the factories are called directly below. |
| 16 | + |
| 17 | +message("*** SequentialFutureBackend() ...") |
| 18 | + |
| 19 | +backend <- SequentialFutureBackend() |
| 20 | +print(class(backend)) |
| 21 | +stopifnot(inherits(backend, "SequentialFutureBackend"), |
| 22 | + inherits(backend, "FutureBackend")) |
| 23 | + |
| 24 | +## A sequential backend has a single worker, which is always free |
| 25 | +stopifnot(nbrOfWorkers(backend) == 1L) |
| 26 | +stopifnot(nbrOfFreeWorkers(backend) == 1L) |
| 27 | + |
| 28 | +print(backend) |
| 29 | + |
| 30 | +message("*** SequentialFutureBackend() ... DONE") |
| 31 | + |
| 32 | + |
| 33 | +message("*** MultisessionFutureBackend() ...") |
| 34 | + |
| 35 | +## 'workers' must be numeric |
| 36 | +res <- tryCatch(MultisessionFutureBackend(workers = "two"), error = identity) |
| 37 | +print(res) |
| 38 | +stopifnot(inherits(res, "error")) |
| 39 | + |
| 40 | +## ... and a single, finite value >= 1 |
| 41 | +res <- tryCatch(MultisessionFutureBackend(workers = c(1L, 2L)), error = identity) |
| 42 | +print(res) |
| 43 | +stopifnot(inherits(res, "error")) |
| 44 | + |
| 45 | +res <- tryCatch(MultisessionFutureBackend(workers = 0L), error = identity) |
| 46 | +print(res) |
| 47 | +stopifnot(inherits(res, "error")) |
| 48 | + |
| 49 | +## 'workers = 1' falls back to a sequential backend |
| 50 | +backend <- MultisessionFutureBackend(workers = 1L) |
| 51 | +print(class(backend)) |
| 52 | +stopifnot(inherits(backend, "SequentialFutureBackend")) |
| 53 | + |
| 54 | +## ... which can be overridden with I(1) |
| 55 | +backend <- MultisessionFutureBackend(workers = I(1L)) |
| 56 | +print(class(backend)) |
| 57 | +stopifnot(inherits(backend, "MultisessionFutureBackend")) |
| 58 | +stopifnot(nbrOfWorkers(backend) == 1L) |
| 59 | +stopWorkers(backend) |
| 60 | + |
| 61 | +## 'workers' may be given as a function |
| 62 | +backend <- MultisessionFutureBackend(workers = function() 2L) |
| 63 | +print(class(backend)) |
| 64 | +stopifnot(inherits(backend, "MultisessionFutureBackend"), |
| 65 | + inherits(backend, "ClusterFutureBackend")) |
| 66 | +stopifnot(nbrOfWorkers(backend) == 2L) |
| 67 | +stopifnot(nbrOfFreeWorkers(backend) == 2L) |
| 68 | + |
| 69 | +## A MultisessionFuture is what this backend produces |
| 70 | +stopifnot("MultisessionFuture" %in% backend[["futureClasses"]]) |
| 71 | + |
| 72 | +print(backend) |
| 73 | +stopWorkers(backend) |
| 74 | + |
| 75 | +message("*** MultisessionFutureBackend() ... DONE") |
| 76 | + |
| 77 | + |
| 78 | +message("*** ClusterFutureBackend() ...") |
| 79 | + |
| 80 | +backend <- ClusterFutureBackend(workers = 2L) |
| 81 | +print(class(backend)) |
| 82 | +stopifnot(inherits(backend, "ClusterFutureBackend"), |
| 83 | + inherits(backend, "FutureBackend")) |
| 84 | +stopifnot(nbrOfWorkers(backend) == 2L) |
| 85 | +stopifnot(nbrOfFreeWorkers(backend) == 2L) |
| 86 | + |
| 87 | +print(backend) |
| 88 | + |
| 89 | +## The backend can be shut down, and doing so is idempotent |
| 90 | +stopifnot(isTRUE(stopWorkers(backend))) |
| 91 | + |
| 92 | +message("*** ClusterFutureBackend() ... DONE") |
| 93 | + |
| 94 | + |
| 95 | +message("*** listFutures() ...") |
| 96 | + |
| 97 | +listFutures <- future:::listFutures |
| 98 | + |
| 99 | +## A backend without a 'reg' element cannot list its futures |
| 100 | +fake <- structure(list(), class = c("MultiprocessFutureBackend", |
| 101 | + "FutureBackend")) |
| 102 | +res <- tryCatch(listFutures(fake), error = identity) |
| 103 | +print(res) |
| 104 | +stopifnot(inherits(res, "FutureError"), |
| 105 | + grepl("does not implement listFutures", conditionMessage(res))) |
| 106 | + |
| 107 | +## nbrOfWorkers()/nbrOfFreeWorkers() are not implemented at this level either |
| 108 | +res <- tryCatch(nbrOfWorkers(fake), error = identity) |
| 109 | +print(res) |
| 110 | +stopifnot(inherits(res, "error"), grepl("not implemented", conditionMessage(res))) |
| 111 | + |
| 112 | +res <- tryCatch(nbrOfFreeWorkers(fake), error = identity) |
| 113 | +print(res) |
| 114 | +stopifnot(inherits(res, "error"), grepl("not implemented", conditionMessage(res))) |
| 115 | + |
| 116 | +## A backend without any futures lists none |
| 117 | +backend <- MultisessionFutureBackend(workers = 2L) |
| 118 | +data <- listFutures(backend) |
| 119 | +print(data) |
| 120 | +stopifnot(is.data.frame(data), nrow(data) == 0L) |
| 121 | +stopifnot(all(c("counter", "start", "label", "resolved") %in% names(data))) |
| 122 | +stopWorkers(backend) |
| 123 | + |
| 124 | +## With futures running, they are listed |
| 125 | +plan(multisession, workers = 2L) |
| 126 | +fs <- list(future({ Sys.sleep(0.5); 1L }, label = "one"), |
| 127 | + future({ Sys.sleep(0.5); 2L }, label = "two")) |
| 128 | +data <- listFutures(plan("backend"), debug = TRUE) |
| 129 | +print(data) |
| 130 | +stopifnot(is.data.frame(data), nrow(data) == 2L) |
| 131 | +stopifnot(all(c("one", "two") %in% data[["label"]])) |
| 132 | +stopifnot(is.logical(data[["resolved"]])) |
| 133 | + |
| 134 | +## Collect the values, so that no futures are left behind |
| 135 | +stopifnot(identical(unlist(value(fs)), c(1L, 2L))) |
| 136 | +plan(sequential) |
| 137 | + |
| 138 | +message("*** listFutures() ... DONE") |
| 139 | + |
| 140 | + |
| 141 | +message("*** MulticoreFutureBackend() ...") |
| 142 | + |
| 143 | +## IMPORTANT: Skip when testing with 'covr', because forked workers do not |
| 144 | +## write back their 'covr' traces, and may truncate them |
| 145 | +if (!covr_testing && parallelly::supportsMulticore() && |
| 146 | + availableCores("multicore") >= 2L) { |
| 147 | + backend <- MulticoreFutureBackend(workers = 2L) |
| 148 | + print(class(backend)) |
| 149 | + stopifnot(inherits(backend, "MulticoreFutureBackend")) |
| 150 | + stopifnot(nbrOfWorkers(backend) == 2L) |
| 151 | + stopWorkers(backend) |
| 152 | + |
| 153 | + ## 'workers = 1' falls back to a sequential backend |
| 154 | + backend <- MulticoreFutureBackend(workers = 1L) |
| 155 | + print(class(backend)) |
| 156 | + stopifnot(inherits(backend, "SequentialFutureBackend")) |
| 157 | +} else { |
| 158 | + message("Skipping MulticoreFutureBackend() tests") |
| 159 | +} |
| 160 | + |
| 161 | +message("*** MulticoreFutureBackend() ... DONE") |
0 commit comments