Skip to contents

Starts a mirai daemon pool shared by every session in this R process. The pool is reference-counted: each call made inside a session holds it until that session ends, and a call made outside a session holds it until the app stops. The pool shuts down when the last holder releases it.

Usage

pool_async(workers = NULL, ..., everywhere = NULL)

Arguments

workers

Number of local daemons. Defaults to one less than the number of cores available to the process: the machine's cores, capped by a CPU quota or cpuset on its cgroup, as in a container or on Posit Connect.

...

Passed to mirai::daemons(), e.g. url or remote for remote pools. A dispatcher is always used, because cancellation requires one.

everywhere

An expression to evaluate once on every worker when the pool starts; see the section on preloading. Unevaluated, like the expression of reactiveAsync().

Value

TRUE invisibly if this call started or joined a shinyasync pool, FALSE if daemons were already managed elsewhere.

Details

A hold taken outside an app, in a script or at the console, is released when the next app in the process stops, since that is when shiny::onStop() fires. A script that never runs an app keeps its pool until the process exits, when the daemons exit with it; call mirai::daemons(0) to stop it earlier.

You need not call it. The first task that runs while no daemons are set starts a pool with the defaults, with a message saying so, and the session it runs in holds that pool as if it had called pool_async(). Call it yourself, before any task, to choose the number of workers, to use remote daemons or to preload the workers.

If you already manage mirai::daemons() yourself, skip this; calling it anyway does nothing, except that an everywhere expression is still loaded on your daemons, since you asked for it.

Tasks run first come, first served across the pool. mirai::status() reports the worker count and how many tasks are executing and awaiting a worker, for a task monitor that shows why a task has not started.

Preloading the workers

Objects an async expression refers to are sent to the worker on every run. For a large data frame, a model or a database connection, run the code that creates them once on every worker instead, with everywhere:

pool_async(workers = 4, everywhere = {
  sales <- arrow::read_parquet("data/sales.parquet")  # one copy per worker
  con <- DBI::dbConnect(RSQLite::SQLite(), "app.db")  # cannot be serialized
  rates <- fx_rates                                   # shipped once from the session
  .Last <- function() DBI::dbDisconnect(con)          # runs when the worker exits
})

sales_by_region <- reactiveAsync(summarise_sales(sales, con, input$region))

The expression runs on each worker with mirai::everywhere() when the pool starts, before any task, and again if the pool is restarted. Objects it refers to, such as fx_rates, are resolved in the calling environment and sent along once, as a task's globals are; packages attached in the session and used by the expression are attached on the workers. The names it assigns and the objects sent with it are resident: dependency capture leaves them out of what a task ships, even if the session has an object of the same name and whether the expression uses them directly or through a helper it ships, and print() lists them on a resident line.

Call it at the top level of app.R, so the preload runs once per process and the pool, with its connections, lives until the app stops. Called in server(), the pool is released when the last session ends and preloaded again when the next one arrives. One expression per pool: a later call with a different everywhere warns and keeps the first. The lazily started default pool has no preload.

Resident data is static: if the session reloads sales, the workers keep their copy. Data that changes belongs in a reactive read, which is sent per run. Every worker holds its own copy, so four workers and a 2 GB frame need 8 GB, and each worker opens its own connection, so three processes of four workers hold twelve. To close what the expression opened, assign a .Last function in it, as above: R runs .Last when the worker process exits, which happens when the pool shuts down and also when the app process dies, since a worker that loses its connection to the app exits on its own. The pool does not wait for it, so a slow disconnect never delays shutdown. It does not run if the worker process itself is killed by a signal. .Last is an assigned name like any other, so print() lists it as resident. A daemon that connects after the preload ran, as a remote daemon may, does not receive it; a task that uses a resident name on such a worker fails with a message saying so.