From d64765c9f9a8fa886a88fb4c095db1d7a88e0758 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:34:04 +0100 Subject: [PATCH 01/12] Workbench launcher concept --- DESCRIPTION | 3 ++- NAMESPACE | 1 + R/launchers.R | 45 +++++++++++++++++++++++++++++++++++++++++ R/mirai-package.R | 2 ++ man/workbench_config.Rd | 30 +++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 man/workbench_config.Rd diff --git a/DESCRIPTION b/DESCRIPTION index df9c869f2..ac6cdee9a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,7 +31,8 @@ Imports: Suggests: cli, litedown, - otel + otel, + rstudioapi Enhances: parallel, promises diff --git a/NAMESPACE b/NAMESPACE index 73ee648f9..4b689e309 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -61,6 +61,7 @@ export(stop_cluster) export(stop_mirai) export(unresolved) export(with_daemons) +export(workbench_config) importFrom(nanonext,"opt<-") importFrom(nanonext,.advance) importFrom(nanonext,.context) diff --git a/R/launchers.R b/R/launchers.R index 52a7bff10..74ad0d5c3 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -94,6 +94,23 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL dots <- if (...length()) parse_dots(envir, ...) else envir[["dots"]] tls <- envir[["tls"]] + if (length(remote) == 2L) { + requireNamespace("rstudioapi", quietly = TRUE) || stop(._[["rstudio_api"]]) + rstudioapi::launcherAvailable() + cluster <- remote[["name"]] + container <- rstudioapi::launcherContainer(remote[["image"]]) + lapply( + seq_len(n), + function(x) rstudioapi::launcherSubmitJob( + sprintf("mirai_daemon_%d", x), + cluster = cluster, + command = launch_remote(), + container = container + ) + ) + return(invisible()) + } + command <- remote[["command"]] rscript <- remote[["rscript"]] quote <- remote[["quote"]] @@ -392,6 +409,34 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" list(command = "/bin/sh", args = args, rscript = rscript, quote = NULL) } +#' Workbench Remote Launch Configuration +#' +#' Generates a remote configuration for launching daemons using the default +#' launcher configured in Posit Workbench. +#' +#' @inherit remote_config return +#' +#' @seealso [ssh_config()], [cluster_config()], and [remote_config()] for other +#' remote launch configurations. +#' +#' @examples +#' tryCatch(workbench_config(), error = identity) +#' +#' \dontrun{ +#' +#' # Launch 2 daemons using the Workbench default launcher: +#' daemons(n = 2, url = host_url(), remote = workbench_config()) +#' } +#' +#' @export +#' +workbench_config <- function() { + requireNamespace("rstudioapi", quietly = TRUE) || stop(._[["rstudio_api"]]) + rstudioapi::launcherAvailable() + cluster <- rstudioapi::launcherGetInfo()[["clusters"]][[1L]] + list(name = cluster[["name"]], image = cluster[["defaultImage"]]) +} + #' URL Constructors #' #' `host_url()` constructs a valid host URL (at which daemons may connect) based diff --git a/R/mirai-package.R b/R/mirai-package.R index e21efdc87..81c57e40d 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -87,6 +87,8 @@ n_one = "`n` must be 1 or greater", n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", + rstudio_api = "workbench launcher requires the `rstudioapi` package", + rstudio_unavailable = "workbench launcher requires a compatible environment", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", diff --git a/man/workbench_config.Rd b/man/workbench_config.Rd new file mode 100644 index 000000000..f210490ad --- /dev/null +++ b/man/workbench_config.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/launchers.R +\name{workbench_config} +\alias{workbench_config} +\title{Workbench Remote Launch Configuration} +\usage{ +workbench_config() +} +\value{ +A list in the required format to be supplied to the \code{remote} argument +of \code{\link[=daemons]{daemons()}} or \code{\link[=launch_remote]{launch_remote()}}. +} +\description{ +Generates a remote configuration for launching daemons using the default +launcher configured in Posit Workbench. +} +\examples{ +tryCatch(workbench_config(), error = identity) + +\dontrun{ + +# Launch 2 daemons using the Workbench default launcher: +daemons(n = 2, url = host_url(), remote = workbench_config()) +} + +} +\seealso{ +\code{\link[=ssh_config]{ssh_config()}}, \code{\link[=cluster_config]{cluster_config()}}, and \code{\link[=remote_config]{remote_config()}} for other +remote launch configurations. +} From b09b0e7c8f644265e05f3222ce4ab39a5ee57806 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:19:09 +0100 Subject: [PATCH 02/12] Add news item and test --- NEWS.md | 1 + R/launchers.R | 4 ++-- R/mirai-package.R | 1 - tests/tests.R | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index f642c56c9..7a63c88f4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,7 @@ #### New Features +* Adds `workbench_config()` to launch remote daemons using the default-configured Kubernetes or traditional cluster on Posit Workbench. * New synchronous mode: `daemons(sync = TRUE)` causes mirai to run synchronously within the current process. This facilitates testing and debugging, e.g. via interactive `browser()` instances (#439). diff --git a/R/launchers.R b/R/launchers.R index 74ad0d5c3..d1fb43ebb 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -412,7 +412,7 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" #' Workbench Remote Launch Configuration #' #' Generates a remote configuration for launching daemons using the default -#' launcher configured in Posit Workbench. +#' configured Kubernetes or traditional cluster in Posit Workbench. #' #' @inherit remote_config return #' @@ -424,7 +424,7 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" #' #' \dontrun{ #' -#' # Launch 2 daemons using the Workbench default launcher: +#' # Launch 2 daemons using the Workbench default: #' daemons(n = 2, url = host_url(), remote = workbench_config()) #' } #' diff --git a/R/mirai-package.R b/R/mirai-package.R index 81c57e40d..d8e035623 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -88,7 +88,6 @@ n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", rstudio_api = "workbench launcher requires the `rstudioapi` package", - rstudio_unavailable = "workbench launcher requires a compatible environment", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", diff --git a/tests/tests.R b/tests/tests.R index 581fa1a66..587677e9a 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -45,6 +45,7 @@ test_true(grepl("5555", local_url(tcp = TRUE, port = 5555), fixed = TRUE)) test_type("list", ssh_config("ssh://remotehost")) test_type("list", ssh_config("ssh://remotehost", tunnel = TRUE)) test_type("list", cluster_config()) +test_type("list", tryCatch(workbench_config(), error = function(cnd) list())) test_true(is_mirai_interrupt(r <- mirai:::mk_interrupt_error())) test_print(r) test_true(is_mirai_error(r <- `class<-`("Error in: testing\n", c("miraiError", "errorValue", "try-error")))) From c78d1e5fb768acf4249471309f97b182f542d0dd Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Tue, 15 Jul 2025 14:08:14 +0100 Subject: [PATCH 03/12] Remove dependency on rstudioapi package --- DESCRIPTION | 3 +-- R/launchers.R | 22 +++++++++++++++------- R/mirai-package.R | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ac6cdee9a..df9c869f2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -31,8 +31,7 @@ Imports: Suggests: cli, litedown, - otel, - rstudioapi + otel Enhances: parallel, promises diff --git a/R/launchers.R b/R/launchers.R index d1fb43ebb..b2d22fa47 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -95,13 +95,13 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL tls <- envir[["tls"]] if (length(remote) == 2L) { - requireNamespace("rstudioapi", quietly = TRUE) || stop(._[["rstudio_api"]]) - rstudioapi::launcherAvailable() + submit_job <- .subset2(rstudio(), ".rs.api.launcher.submitJob") + new_container <- .subset2(rstudio(), ".rs.api.launcher.newContainer") cluster <- remote[["name"]] - container <- rstudioapi::launcherContainer(remote[["image"]]) + container <- new_container(remote[["image"]]) lapply( seq_len(n), - function(x) rstudioapi::launcherSubmitJob( + function(x) submit_job( sprintf("mirai_daemon_%d", x), cluster = cluster, command = launch_remote(), @@ -431,9 +431,8 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" #' @export #' workbench_config <- function() { - requireNamespace("rstudioapi", quietly = TRUE) || stop(._[["rstudio_api"]]) - rstudioapi::launcherAvailable() - cluster <- rstudioapi::launcherGetInfo()[["clusters"]][[1L]] + get_info <- .subset2(rstudio(), ".rs.api.launcher.getInfo") + cluster <- get_info()[["clusters"]][[1L]] list(name = cluster[["name"]], image = cluster[["defaultImage"]]) } @@ -521,3 +520,12 @@ find_dot <- function(args) { any(sel) || stop(._[["dot_required"]], call. = FALSE) sel } + +rstudio <- function() { + idx <- match("tools:rstudio", search()) + is.na(idx) && stop(._[["rstudio_api"]]) + tools <- as.environment(idx) + feature_available <- .subset2(tools, ".rs.api.launcher.jobsFeatureAvailable") + is.function(feature_available) && feature_available() || stop(._[["rstudio_api"]]) + tools +} diff --git a/R/mirai-package.R b/R/mirai-package.R index d8e035623..6209c667d 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -87,7 +87,7 @@ n_one = "`n` must be 1 or greater", n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", - rstudio_api = "workbench launcher requires the `rstudioapi` package", + rstudio_api = "workbench launcher requires a Posit Workbench environment", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", From 6163b6b7a63dce36ec8c4f7cd215d49bd2d7fbf9 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:21:25 +0100 Subject: [PATCH 04/12] workbench_config() -> cloud_config() --- NAMESPACE | 2 +- NEWS.md | 2 +- R/launchers.R | 25 +++++++++++++++++-------- R/mirai-package.R | 3 ++- man/cloud_config.Rd | 35 +++++++++++++++++++++++++++++++++++ man/workbench_config.Rd | 30 ------------------------------ tests/tests.R | 2 +- 7 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 man/cloud_config.Rd delete mode 100644 man/workbench_config.Rd diff --git a/NAMESPACE b/NAMESPACE index 4b689e309..e52fd5af6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,6 +26,7 @@ export(.flat) export(.progress) export(.stop) export(call_mirai) +export(cloud_config) export(cluster_config) export(collect_mirai) export(daemon) @@ -61,7 +62,6 @@ export(stop_cluster) export(stop_mirai) export(unresolved) export(with_daemons) -export(workbench_config) importFrom(nanonext,"opt<-") importFrom(nanonext,.advance) importFrom(nanonext,.context) diff --git a/NEWS.md b/NEWS.md index 7a63c88f4..ebef74200 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,7 @@ #### New Features -* Adds `workbench_config()` to launch remote daemons using the default-configured Kubernetes or traditional cluster on Posit Workbench. +* Adds `cloud_config()` to launch remote daemons using a cloud / cloud-based managed platform. Currently supports Posit Workbench. * New synchronous mode: `daemons(sync = TRUE)` causes mirai to run synchronously within the current process. This facilitates testing and debugging, e.g. via interactive `browser()` instances (#439). diff --git a/R/launchers.R b/R/launchers.R index b2d22fa47..fc369b59a 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -409,28 +409,37 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" list(command = "/bin/sh", args = args, rscript = rscript, quote = NULL) } -#' Workbench Remote Launch Configuration +#' Cloud Remote Launch Configuration #' -#' Generates a remote configuration for launching daemons using the default -#' configured Kubernetes or traditional cluster in Posit Workbench. +#' Generates a remote configuration for launching daemons via cloud / +#' cloud-based managed platforms. +#' +#' @param platform \[default "posit"\] character name of the platform +#' (case-insensitive). Currently the only option is "posit" to use the Posit +#' Workbench launcher. #' #' @inherit remote_config return #' #' @seealso [ssh_config()], [cluster_config()], and [remote_config()] for other -#' remote launch configurations. +#' types of remote launch configuration. #' #' @examples -#' tryCatch(workbench_config(), error = identity) +#' tryCatch(cloud_config(), error = identity) #' #' \dontrun{ #' -#' # Launch 2 daemons using the Workbench default: -#' daemons(n = 2, url = host_url(), remote = workbench_config()) +#' # Launch 2 daemons using the Posit Workbench default: +#' daemons(n = 2, url = host_url(), remote = cloud_config(platform = "posit")) #' } #' #' @export #' -workbench_config <- function() { +cloud_config <- function(platform = "posit") { + switch( + tolower(platform), + posit = TRUE, + stop(._[["platform_unsupported"]]) + ) get_info <- .subset2(rstudio(), ".rs.api.launcher.getInfo") cluster <- get_info()[["clusters"]][[1L]] list(name = cluster[["name"]], image = cluster[["defaultImage"]]) diff --git a/R/mirai-package.R b/R/mirai-package.R index 6209c667d..41624c542 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -87,7 +87,8 @@ n_one = "`n` must be 1 or greater", n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", - rstudio_api = "workbench launcher requires a Posit Workbench environment", + platform_unsupported = "`platform` is currently not supported", + rstudio_api = "cannot be used outside of a Posit Workbench environment", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", diff --git a/man/cloud_config.Rd b/man/cloud_config.Rd new file mode 100644 index 000000000..31f368d6f --- /dev/null +++ b/man/cloud_config.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/launchers.R +\name{cloud_config} +\alias{cloud_config} +\title{Cloud Remote Launch Configuration} +\usage{ +cloud_config(platform = "posit") +} +\arguments{ +\item{platform}{[default "posit"] character name of the platform +(case-insensitive). Currently the only option is "posit" to use the Posit +Workbench launcher.} +} +\value{ +A list in the required format to be supplied to the \code{remote} argument +of \code{\link[=daemons]{daemons()}} or \code{\link[=launch_remote]{launch_remote()}}. +} +\description{ +Generates a remote configuration for launching daemons via cloud / +cloud-based managed platforms. +} +\examples{ +tryCatch(cloud_config(), error = identity) + +\dontrun{ + +# Launch 2 daemons using the Posit Workbench default: +daemons(n = 2, url = host_url(), remote = cloud_config(platform = "posit")) +} + +} +\seealso{ +\code{\link[=ssh_config]{ssh_config()}}, \code{\link[=cluster_config]{cluster_config()}}, and \code{\link[=remote_config]{remote_config()}} for other +types of remote launch configuration. +} diff --git a/man/workbench_config.Rd b/man/workbench_config.Rd deleted file mode 100644 index f210490ad..000000000 --- a/man/workbench_config.Rd +++ /dev/null @@ -1,30 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/launchers.R -\name{workbench_config} -\alias{workbench_config} -\title{Workbench Remote Launch Configuration} -\usage{ -workbench_config() -} -\value{ -A list in the required format to be supplied to the \code{remote} argument -of \code{\link[=daemons]{daemons()}} or \code{\link[=launch_remote]{launch_remote()}}. -} -\description{ -Generates a remote configuration for launching daemons using the default -launcher configured in Posit Workbench. -} -\examples{ -tryCatch(workbench_config(), error = identity) - -\dontrun{ - -# Launch 2 daemons using the Workbench default launcher: -daemons(n = 2, url = host_url(), remote = workbench_config()) -} - -} -\seealso{ -\code{\link[=ssh_config]{ssh_config()}}, \code{\link[=cluster_config]{cluster_config()}}, and \code{\link[=remote_config]{remote_config()}} for other -remote launch configurations. -} diff --git a/tests/tests.R b/tests/tests.R index 587677e9a..3955e1cfe 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -45,7 +45,7 @@ test_true(grepl("5555", local_url(tcp = TRUE, port = 5555), fixed = TRUE)) test_type("list", ssh_config("ssh://remotehost")) test_type("list", ssh_config("ssh://remotehost", tunnel = TRUE)) test_type("list", cluster_config()) -test_type("list", tryCatch(workbench_config(), error = function(cnd) list())) +test_class("error", tryCatch(cloud_config(), error = identity)) test_true(is_mirai_interrupt(r <- mirai:::mk_interrupt_error())) test_print(r) test_true(is_mirai_error(r <- `class<-`("Error in: testing\n", c("miraiError", "errorValue", "try-error")))) From 89521923fef8550f8a82b04c873297f56e19bcb8 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:41:56 +0100 Subject: [PATCH 05/12] Improve error paths --- R/launchers.R | 25 +++++++++++++++---------- R/mirai-package.R | 2 +- tests/tests.R | 3 ++- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/R/launchers.R b/R/launchers.R index fc369b59a..91083b645 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -95,8 +95,10 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL tls <- envir[["tls"]] if (length(remote) == 2L) { - submit_job <- .subset2(rstudio(), ".rs.api.launcher.submitJob") - new_container <- .subset2(rstudio(), ".rs.api.launcher.newContainer") + tools <- posit_tools() + is.environment(tools) || stop(._[["posit_api"]]) + submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") + new_container <- .subset2(tools, ".rs.api.launcher.newContainer") cluster <- remote[["name"]] container <- new_container(remote[["image"]]) lapply( @@ -437,12 +439,15 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" cloud_config <- function(platform = "posit") { switch( tolower(platform), - posit = TRUE, + posit = { + tools <- posit_tools() + is.environment(tools) || stop(._[["posit_api"]]) + get_info <- .subset2(tools, ".rs.api.launcher.getInfo") + cluster <- get_info()[["clusters"]][[1L]] + list(name = cluster[["name"]], image = cluster[["defaultImage"]]) + }, stop(._[["platform_unsupported"]]) ) - get_info <- .subset2(rstudio(), ".rs.api.launcher.getInfo") - cluster <- get_info()[["clusters"]][[1L]] - list(name = cluster[["name"]], image = cluster[["defaultImage"]]) } #' URL Constructors @@ -530,11 +535,11 @@ find_dot <- function(args) { sel } -rstudio <- function() { - idx <- match("tools:rstudio", search()) - is.na(idx) && stop(._[["rstudio_api"]]) +posit_tools <- function() { + idx <- match("tools:rstudio", search(), nomatch = 0L) + idx || return() tools <- as.environment(idx) feature_available <- .subset2(tools, ".rs.api.launcher.jobsFeatureAvailable") - is.function(feature_available) && feature_available() || stop(._[["rstudio_api"]]) + is.function(feature_available) && feature_available() || return() tools } diff --git a/R/mirai-package.R b/R/mirai-package.R index 41624c542..c295c7c55 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -88,7 +88,7 @@ n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", platform_unsupported = "`platform` is currently not supported", - rstudio_api = "cannot be used outside of a Posit Workbench environment", + posit_api = "this launch configuration can only be used from Posit Workbench", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", diff --git a/tests/tests.R b/tests/tests.R index 3955e1cfe..a38d94d90 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -45,7 +45,8 @@ test_true(grepl("5555", local_url(tcp = TRUE, port = 5555), fixed = TRUE)) test_type("list", ssh_config("ssh://remotehost")) test_type("list", ssh_config("ssh://remotehost", tunnel = TRUE)) test_type("list", cluster_config()) -test_class("error", tryCatch(cloud_config(), error = identity)) +test_error(cloud_config(platform = ""), "not supported") +test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") test_true(is_mirai_interrupt(r <- mirai:::mk_interrupt_error())) test_print(r) test_true(is_mirai_error(r <- `class<-`("Error in: testing\n", c("miraiError", "errorValue", "try-error")))) From 07cebd8cb9fc24b3722a8e6dab06affbbf19c837 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:34:11 +0100 Subject: [PATCH 06/12] Standardize cloud config structure --- R/launchers.R | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/R/launchers.R b/R/launchers.R index 91083b645..d6d5f27b4 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -95,22 +95,28 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL tls <- envir[["tls"]] if (length(remote) == 2L) { - tools <- posit_tools() - is.environment(tools) || stop(._[["posit_api"]]) - submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") - new_container <- .subset2(tools, ".rs.api.launcher.newContainer") - cluster <- remote[["name"]] - container <- new_container(remote[["image"]]) - lapply( - seq_len(n), - function(x) submit_job( - sprintf("mirai_daemon_%d", x), - cluster = cluster, - command = launch_remote(), - container = container + platform <- remote[["platform"]] + args <- remote[["args"]] + platform == "posit" && { + tools <- posit_tools() + is.environment(tools) || stop(._[["posit_api"]]) + submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") + new_container <- .subset2(tools, ".rs.api.launcher.newContainer") + cluster <- args[["name"]] + container <- new_container(args[["image"]]) + cmds <- launch_remote(n) + lapply( + cmds, + function(cmd) submit_job( + sprintf("mirai_daemon_%d", random(4L)), + cluster = cluster, + command = cmd, + container = container + ) ) - ) - return(invisible()) + return(cmds) + } + stop(._[["platform_unsupported"]]) } command <- remote[["command"]] @@ -437,8 +443,9 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" #' @export #' cloud_config <- function(platform = "posit") { - switch( - tolower(platform), + platform <- tolower(platform) + args <- switch( + platform, posit = { tools <- posit_tools() is.environment(tools) || stop(._[["posit_api"]]) @@ -448,6 +455,7 @@ cloud_config <- function(platform = "posit") { }, stop(._[["platform_unsupported"]]) ) + list(platform = platform, args = args) } #' URL Constructors From dee3b57b2ae8e86c05b354dcacd09656c2a758da Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:43:08 +0100 Subject: [PATCH 07/12] Launcher job name --- R/launchers.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/launchers.R b/R/launchers.R index d6d5f27b4..0be09784b 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -108,7 +108,7 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL lapply( cmds, function(cmd) submit_job( - sprintf("mirai_daemon_%d", random(4L)), + sprintf("mirai_daemon_%s", random(3L)), cluster = cluster, command = cmd, container = container From c9439fcab773c227007a18f053a084d893c21019 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:33:08 +0100 Subject: [PATCH 08/12] Mock cloud_config() tests --- tests/tests.R | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/tests.R b/tests/tests.R index a38d94d90..8f661f032 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -46,7 +46,14 @@ test_type("list", ssh_config("ssh://remotehost")) test_type("list", ssh_config("ssh://remotehost", tunnel = TRUE)) test_type("list", cluster_config()) test_error(cloud_config(platform = ""), "not supported") -test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") +if (is.null(mirai:::posit_tools())) { + test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") + ns <- new.env(parent = emptyenv()) + `[[<-`(ns, ".rs.api.launcher.jobsFeatureAvailable", function() TRUE) + `[[<-`(ns, ".rs.api.launcher.getInfo", function() list(clusters = list(list(name = "Kubernetes", defaultImage = "1.a.b.reg.prov.com/int-r-sess:ubuntu2204-20250609")))) + attach(ns, name = "tools:rstudio") +} +test_type("list", cloud_config(platform = "posit")) test_true(is_mirai_interrupt(r <- mirai:::mk_interrupt_error())) test_print(r) test_true(is_mirai_error(r <- `class<-`("Error in: testing\n", c("miraiError", "errorValue", "try-error")))) From 032a4dfc02d8dcfc0da66ff4dcbe9482d92859fc Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:56:37 +0100 Subject: [PATCH 09/12] Mock cloud launcher tests --- tests/tests.R | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/tests.R b/tests/tests.R index 8f661f032..48035891c 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -45,15 +45,6 @@ test_true(grepl("5555", local_url(tcp = TRUE, port = 5555), fixed = TRUE)) test_type("list", ssh_config("ssh://remotehost")) test_type("list", ssh_config("ssh://remotehost", tunnel = TRUE)) test_type("list", cluster_config()) -test_error(cloud_config(platform = ""), "not supported") -if (is.null(mirai:::posit_tools())) { - test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") - ns <- new.env(parent = emptyenv()) - `[[<-`(ns, ".rs.api.launcher.jobsFeatureAvailable", function() TRUE) - `[[<-`(ns, ".rs.api.launcher.getInfo", function() list(clusters = list(list(name = "Kubernetes", defaultImage = "1.a.b.reg.prov.com/int-r-sess:ubuntu2204-20250609")))) - attach(ns, name = "tools:rstudio") -} -test_type("list", cloud_config(platform = "posit")) test_true(is_mirai_interrupt(r <- mirai:::mk_interrupt_error())) test_print(r) test_true(is_mirai_error(r <- `class<-`("Error in: testing\n", c("miraiError", "errorValue", "try-error")))) @@ -69,6 +60,23 @@ for (i in 0:4) test_null(register_serial("test_klass1", serialize, unserialize)) test_null(register_serial(c("test_klass2", "test_klass3"), list(serialize, serialize), list(unserialize, unserialize))) test_equal(length(mirai:::.[["serial"]][[3L]]), 3L) +# cloud launcher tests +test_error(cloud_config(platform = ""), "not supported") +is.null(mirai:::posit_tools()) && { + ns <- new.env(parent = emptyenv()) + `[[<-`(ns, ".rs.api.launcher.jobsFeatureAvailable", function() TRUE) + `[[<-`(ns, ".rs.api.launcher.getInfo", function() list(clusters = list(list(name = "Kubernetes", defaultImage = "1.a.b.reg.prov.com/int-r-sess:ubuntu2204-20250609")))) + `[[<-`(ns, ".rs.api.launcher.newContainer", function(image) image) + `[[<-`(ns, ".rs.api.launcher.submitJob", function(...) NULL) + attach(ns, name = "tools:rstudio") + cfg <- cloud_config(platform = "posit") + test_type("list", cfg) + test_zero(daemons(url = local_url(), dispatcher = FALSE)) + test_class("miraiLaunchCmd", launch_remote(n = 2L, remote = cfg)) + test_zero(daemons(0)) + detach() + test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") +} # mirai and daemons tests connection && { Sys.sleep(1L) From caee205455aba640899ffa2ab8c51ba0520b482c Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:16:27 +0100 Subject: [PATCH 10/12] Test all the lines --- tests/tests.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tests.R b/tests/tests.R index 48035891c..5ab30ef23 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -73,6 +73,8 @@ is.null(mirai:::posit_tools()) && { test_type("list", cfg) test_zero(daemons(url = local_url(), dispatcher = FALSE)) test_class("miraiLaunchCmd", launch_remote(n = 2L, remote = cfg)) + cfg$platform <- "wrong" + test_error(launch_remote(n = 2L, remote = cfg), "not supported") test_zero(daemons(0)) detach() test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") From 986c1c8d9980af86faac4c1a4b8e061c7bc2c8ea Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 24 Jul 2025 23:06:41 +0100 Subject: [PATCH 11/12] Refactor a bit --- R/launchers.R | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/R/launchers.R b/R/launchers.R index 0be09784b..e648da159 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -97,26 +97,10 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL if (length(remote) == 2L) { platform <- remote[["platform"]] args <- remote[["args"]] - platform == "posit" && { - tools <- posit_tools() - is.environment(tools) || stop(._[["posit_api"]]) - submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") - new_container <- .subset2(tools, ".rs.api.launcher.newContainer") - cluster <- args[["name"]] - container <- new_container(args[["image"]]) - cmds <- launch_remote(n) - lapply( - cmds, - function(cmd) submit_job( - sprintf("mirai_daemon_%s", random(3L)), - cluster = cluster, - command = cmd, - container = container - ) - ) - return(cmds) - } - stop(._[["platform_unsupported"]]) + platform != "posit" && stop(._[["platform_unsupported"]]) + tools <- posit_tools() + is.environment(tools) || stop(._[["posit_api"]]) + return(posit_workbench_launch(n, args, tools)) } command <- remote[["command"]] @@ -551,3 +535,20 @@ posit_tools <- function() { is.function(feature_available) && feature_available() || return() tools } + +posit_workbench_launch <- function(n, args, tools) { + submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") + new_container <- .subset2(tools, ".rs.api.launcher.newContainer") + cluster <- args[["name"]] + container <- new_container(args[["image"]]) + cmds <- launch_remote(n) + lapply(cmds, function(cmd) + submit_job( + sprintf("mirai_daemon_%s", random(3L)), + cluster = cluster, + command = cmd, + container = container + ) + ) + cmds +} From 8655ae54c654ff3898cc8231ef78cf5fee79aa79 Mon Sep 17 00:00:00 2001 From: shikokuchuo <53399081+shikokuchuo@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:56:44 +0100 Subject: [PATCH 12/12] cloud_config() -> posit_workbench_config() --- NAMESPACE | 2 +- NEWS.md | 2 +- R/launchers.R | 47 +++++++------------ R/mirai-package.R | 3 +- ...ud_config.Rd => posit_workbench_config.Rd} | 21 ++++----- tests/tests.R | 13 ++--- 6 files changed, 32 insertions(+), 56 deletions(-) rename man/{cloud_config.Rd => posit_workbench_config.Rd} (52%) diff --git a/NAMESPACE b/NAMESPACE index e52fd5af6..9d390ca9a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -26,7 +26,6 @@ export(.flat) export(.progress) export(.stop) export(call_mirai) -export(cloud_config) export(cluster_config) export(collect_mirai) export(daemon) @@ -52,6 +51,7 @@ export(nextcode) export(nextget) export(nextstream) export(on_daemon) +export(posit_workbench_config) export(register_serial) export(remote_config) export(require_daemons) diff --git a/NEWS.md b/NEWS.md index ebef74200..0b4713ed6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,7 +2,7 @@ #### New Features -* Adds `cloud_config()` to launch remote daemons using a cloud / cloud-based managed platform. Currently supports Posit Workbench. +* Adds `posit_workbench_config()` to launch remote daemons using the default Posit Workbench launcher (currently only supports Rstudio Pro sessions). * New synchronous mode: `daemons(sync = TRUE)` causes mirai to run synchronously within the current process. This facilitates testing and debugging, e.g. via interactive `browser()` instances (#439). diff --git a/R/launchers.R b/R/launchers.R index e648da159..a1c0f4a1e 100644 --- a/R/launchers.R +++ b/R/launchers.R @@ -95,12 +95,9 @@ launch_remote <- function(n = 1L, remote = remote_config(), ..., .compute = NULL tls <- envir[["tls"]] if (length(remote) == 2L) { - platform <- remote[["platform"]] - args <- remote[["args"]] - platform != "posit" && stop(._[["platform_unsupported"]]) tools <- posit_tools() is.environment(tools) || stop(._[["posit_api"]]) - return(posit_workbench_launch(n, args, tools)) + return(posit_workbench_launch(n, remote, tools)) } command <- remote[["command"]] @@ -401,14 +398,10 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" list(command = "/bin/sh", args = args, rscript = rscript, quote = NULL) } -#' Cloud Remote Launch Configuration -#' -#' Generates a remote configuration for launching daemons via cloud / -#' cloud-based managed platforms. +#' Posit Workbench Launch Configuration #' -#' @param platform \[default "posit"\] character name of the platform -#' (case-insensitive). Currently the only option is "posit" to use the Posit -#' Workbench launcher. +#' Generates a remote configuration for launching daemons via the default +#' launcher in Posit Workbench. Currently only supports Rstudio Pro sessions. #' #' @inherit remote_config return #' @@ -416,30 +409,22 @@ cluster_config <- function(command = "sbatch", options = "", rscript = "Rscript" #' types of remote launch configuration. #' #' @examples -#' tryCatch(cloud_config(), error = identity) +#' tryCatch(posit_workbench_config(), error = identity) #' #' \dontrun{ #' #' # Launch 2 daemons using the Posit Workbench default: -#' daemons(n = 2, url = host_url(), remote = cloud_config(platform = "posit")) +#' daemons(n = 2, url = host_url(), remote = posit_workbench_config() #' } #' #' @export #' -cloud_config <- function(platform = "posit") { - platform <- tolower(platform) - args <- switch( - platform, - posit = { - tools <- posit_tools() - is.environment(tools) || stop(._[["posit_api"]]) - get_info <- .subset2(tools, ".rs.api.launcher.getInfo") - cluster <- get_info()[["clusters"]][[1L]] - list(name = cluster[["name"]], image = cluster[["defaultImage"]]) - }, - stop(._[["platform_unsupported"]]) - ) - list(platform = platform, args = args) +posit_workbench_config <- function() { + tools <- posit_tools() + is.environment(tools) || stop(._[["posit_api"]]) + get_info <- .subset2(tools, ".rs.api.launcher.getInfo") + cluster <- get_info()[["clusters"]][[1L]] + list(name = cluster[["name"]], image = cluster[["defaultImage"]]) } #' URL Constructors @@ -536,15 +521,15 @@ posit_tools <- function() { tools } -posit_workbench_launch <- function(n, args, tools) { +posit_workbench_launch <- function(n, remote, tools) { submit_job <- .subset2(tools, ".rs.api.launcher.submitJob") new_container <- .subset2(tools, ".rs.api.launcher.newContainer") - cluster <- args[["name"]] - container <- new_container(args[["image"]]) + cluster <- remote[["name"]] + container <- new_container(remote[["image"]]) cmds <- launch_remote(n) lapply(cmds, function(cmd) submit_job( - sprintf("mirai_daemon_%s", random(3L)), + sprintf("mirai_daemon_%s", random(4L)), cluster = cluster, command = cmd, container = container diff --git a/R/mirai-package.R b/R/mirai-package.R index c295c7c55..b61f84a1e 100644 --- a/R/mirai-package.R +++ b/R/mirai-package.R @@ -87,8 +87,7 @@ n_one = "`n` must be 1 or greater", n_zero = "the number of daemons must be zero or greater", numeric_n = "`n` must be numeric, did you mean to provide `url`?", - platform_unsupported = "`platform` is currently not supported", - posit_api = "this launch configuration can only be used from Posit Workbench", + posit_api = "requires Posit Workbench (Rstudio Pro session)", sync_daemons = "mirai: initial sync with daemon(s) [%d secs elapsed]", sync_dispatcher = "mirai: initial sync with dispatcher [%d secs elapsed]", synchronous = "daemons cannot be launched for synchronous compute profiles", diff --git a/man/cloud_config.Rd b/man/posit_workbench_config.Rd similarity index 52% rename from man/cloud_config.Rd rename to man/posit_workbench_config.Rd index 31f368d6f..aa855b11a 100644 --- a/man/cloud_config.Rd +++ b/man/posit_workbench_config.Rd @@ -1,31 +1,26 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/launchers.R -\name{cloud_config} -\alias{cloud_config} -\title{Cloud Remote Launch Configuration} +\name{posit_workbench_config} +\alias{posit_workbench_config} +\title{Posit Workbench Launch Configuration} \usage{ -cloud_config(platform = "posit") -} -\arguments{ -\item{platform}{[default "posit"] character name of the platform -(case-insensitive). Currently the only option is "posit" to use the Posit -Workbench launcher.} +posit_workbench_config() } \value{ A list in the required format to be supplied to the \code{remote} argument of \code{\link[=daemons]{daemons()}} or \code{\link[=launch_remote]{launch_remote()}}. } \description{ -Generates a remote configuration for launching daemons via cloud / -cloud-based managed platforms. +Generates a remote configuration for launching daemons via the default +launcher in Posit Workbench. Currently only supports Rstudio Pro sessions. } \examples{ -tryCatch(cloud_config(), error = identity) +tryCatch(posit_workbench_config(), error = identity) \dontrun{ # Launch 2 daemons using the Posit Workbench default: -daemons(n = 2, url = host_url(), remote = cloud_config(platform = "posit")) +daemons(n = 2, url = host_url(), remote = posit_workbench_config() } } diff --git a/tests/tests.R b/tests/tests.R index 5ab30ef23..b8827a769 100644 --- a/tests/tests.R +++ b/tests/tests.R @@ -60,8 +60,7 @@ for (i in 0:4) test_null(register_serial("test_klass1", serialize, unserialize)) test_null(register_serial(c("test_klass2", "test_klass3"), list(serialize, serialize), list(unserialize, unserialize))) test_equal(length(mirai:::.[["serial"]][[3L]]), 3L) -# cloud launcher tests -test_error(cloud_config(platform = ""), "not supported") +# Posit workbench launcher tests is.null(mirai:::posit_tools()) && { ns <- new.env(parent = emptyenv()) `[[<-`(ns, ".rs.api.launcher.jobsFeatureAvailable", function() TRUE) @@ -69,15 +68,13 @@ is.null(mirai:::posit_tools()) && { `[[<-`(ns, ".rs.api.launcher.newContainer", function(image) image) `[[<-`(ns, ".rs.api.launcher.submitJob", function(...) NULL) attach(ns, name = "tools:rstudio") - cfg <- cloud_config(platform = "posit") + cfg <- posit_workbench_config() test_type("list", cfg) - test_zero(daemons(url = local_url(), dispatcher = FALSE)) + test_true(daemons(url = local_url(), dispatcher = FALSE)) test_class("miraiLaunchCmd", launch_remote(n = 2L, remote = cfg)) - cfg$platform <- "wrong" - test_error(launch_remote(n = 2L, remote = cfg), "not supported") - test_zero(daemons(0)) + test_false(daemons(0)) detach() - test_error(cloud_config(platform = "posit"), "can only be used from Posit Workbench") + test_error(posit_workbench_config(), "requires Posit Workbench") } # mirai and daemons tests connection && {