callq implements a multi process task queue that uses promises as the interface after pushing tasks to the queue. It supports both callr and mirai as backends for launching worker sessions.
The scope of callq is very similar to the scope of
promises::future_promise()
. It is mostly used in
shiny and
plumber applications to allow
executing long running computations in a background process while
leaving the main process available to process requests from new
sessions.
Compared to the promises::future_promises
solution it has a few
differences listed below:
- callq always uses persistent workers, ie, R sessions are created when
initializing the queue and always reused for evaluating the tasks.
This also happens with
future
depending on the backend,multisession
reuses sessions too. - it’s possible to keep state in callq workers. You can use
<<-
orlist2env
to assign to the global environment which is not cleanup up after each task execution. - callq allows manually specifying a worker to execute a task.
callq has been created to solve a very specific problem from this app. The constraints for it were:
- Models take time to load, thus we must have a persistent background session.
- Models are large objects, and we can’t have an object loaded per user session. Thus we need a global queue, shared between all user sessions.
- Models should be loaded directly in the background sessions.
- The app code should focus on app logic, not async handling.
Initially we only supported the callr backend. Thanks to great
discussions with @shikokuchuo we now
also support the mirai backend too. Maybe callq
wouldn’t even exist if
I knew how to do it with mirai in the first place. See also
mirai.promises.
You can install the development version of callq like so:
remotes::install_github("mlverse/callq")
Below we show how to use callq as a task queue in a shiny app that runs runs long computations.
The following app takes ~3s to start compared to the 10s that it would take if the ‘long-running’ computations were not running in background workers.
library(shiny)
library(promises)
library(callq)
q <- task_q$new(num_workers = 4)
ui <- fluidPage(
fluidRow(
plotOutput("one"),
plotOutput("two"),
),
fluidRow(
plotOutput("three"),
plotOutput("four"),
)
)
make_plot <- function(time) {
Sys.sleep(time)
runif(10)
}
server <- function(input, output, session) {
output$one <- renderPlot({q$push(make_plot, list(time = 2.5)) %...>% plot()})
output$two <- renderPlot({q$push(make_plot, list(time = 2.5)) %...>% plot()})
output$three <- renderPlot({q$push(make_plot, list(time = 2.5)) %...>% plot()})
output$four <- renderPlot({q$push(make_plot, list(time = 2.5)) %...>% plot()})
}
shiny::shinyApp(ui, server)