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.
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.urlorremotefor 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.
