Skip to content

Commit e9bf7ea

Browse files
A future that is interrupted after being canceled is now always reported as canceled
1 parent a79e258 commit e9bf7ea

7 files changed

Lines changed: 71 additions & 2 deletions

‎DESCRIPTION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Package: future
2-
Version: 1.80.0-9020
2+
Version: 1.80.0-9021
33
Title: Unified Parallel and Distributed Processing in R for Everyone
44
Depends:
55
R (>= 3.2.0)

‎NEWS.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Significant Changes
44

5+
* A future that is interrupted after being canceled is now always
6+
reported as canceled, whereas an interruption that is not caused by
7+
`cancel()` is still a `FutureInterruptError`.
8+
59
* `digest` is no longer a hard dependency. The package now uses
610
`tools::md5sum(bytes = ...)`, which is available in R (>= 4.5.0), to
711
generate unique IDs for R objects. For older versions of R, it uses
@@ -12,6 +16,10 @@
1216

1317
## Bug Fixes
1418

19+
* `value()` on a future that was canceled with `cancel()` and
20+
interrupted could signal a `FutureInterruptError` instead of a
21+
`FutureCanceledError`, e.g. for 'multicore' futures.
22+
1523
* When launching a 'cluster' or 'multisession' future timed out while
1624
waiting for an available worker, it would produce an obscure
1725
error instead of an informative timeout error.

‎R/core_api-value.R‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ value.Future <- function(future, stdout = TRUE, signal = TRUE, drop = FALSE, ...
8686
future <- run(future)
8787
}
8888

89-
result <- result(future)
89+
## An interruption of a future that was canceled is a cancellation
90+
result <- withCallingHandlers(result(future), FutureInterruptError = function(ex) {
91+
if (!inherits(ex, "FutureCanceledError") && "cancel" %in% future[["actions"]]) {
92+
stop(canceledFutureError(future))
93+
}
94+
})
9095
stop_if_not(inherits(result, "FutureResult"))
9196

9297
value <- result[["value"]]

‎R/protected_api-result.R‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,14 @@ result.Future <- function(future, ...) {
121121

122122
.Defunct(msg = "Future objects with an internal version of 1.7 or earlier are defunct. This error is likely coming from a third-party package or other R code. Please report this to the maintainer of the 'future' package so this can be resolved.", package = .packageName)
123123
}
124+
125+
126+
## Creates a FutureCanceledError for a future that was canceled, and records
127+
## it as the result of the future
128+
canceledFutureError <- function(future) {
129+
msg <- sprintf("Future (%s) of class %s was canceled", sQuoteLabel(future), class(future)[1])
130+
result <- FutureCanceledError(msg, future = future)
131+
future[["result"]] <- result
132+
future[["state"]] <- "canceled"
133+
result
134+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#' @tags cancel
2+
#' @tags detritus-files
3+
#' @tags detritus-connections
4+
#' @tags multisession multicore
5+
6+
library(future)
7+
options(future.debug = FALSE)
8+
9+
## A sequential future is resolved already when created, so there is
10+
## nothing to cancel
11+
strategies <- setdiff(supportedStrategies(), "sequential")
12+
13+
for (strategy in strategies) {
14+
message(sprintf("plan('%s') ...", strategy))
15+
plan(strategy)
16+
17+
n0 <- nbrOfFreeWorkers()
18+
19+
## Cancel a running future
20+
f <- future({ Sys.sleep(1.0); 42 })
21+
stopifnot(!resolved(f))
22+
f <- cancel(f)
23+
24+
## Canceled futures always triggers a FutureCanceledError,
25+
## regardless whether they were interrupted or not
26+
v <- tryCatch(value(f), error = identity)
27+
print(class(v))
28+
stopifnot(inherits(v, "FutureCanceledError"))
29+
30+
## Canceled futures always release their workers
31+
stopifnot(nbrOfFreeWorkers() == n0)
32+
33+
message(sprintf("plan('%s') ... done", strategy))
34+
}
35+
36+
message("Shut down future backend")
37+
plan(sequential)
38+
gc()

‎inst/testme/test-interrupts-from-worker-itself.R‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ for (strategy in strategies) {
4747
}, FutureError = identity)
4848
print(v)
4949

50+
## An interruption that was not caused by cancel() is never a cancellation
51+
stopifnot(!inherits(v, "FutureCanceledError"))
52+
5053
## A future interrupting itself is not supported on all backends
5154
stopifnot(
5255
# (i) Ideally the SIGINT interrupt condition is caught, but ...

‎tests/test-cancel-canceled-error.R‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#! /usr/bin/env Rscript
2+
## This runs testme test script inst/testme/test-cancel-canceled-error.R
3+
## Don't edit - it was autogenerated by inst/testme/deploy.R
4+
future:::testme("cancel-canceled-error")

0 commit comments

Comments
 (0)