8

In my Shiny app users can generate heavy powerpoint report. When it contains a lot of slides it could take > 30 minutes to be done. And therefore I'd like to process those tasks in independent processes/tasks which could work even when app is closed - e.g. user clicks button to generate report, closes app and when report is ready app informs user by email. Are there any good practices or proven solutions to do this?

My first thought was using future package with plan(multisession) set - but I'm not sure what happens when user closes the app - future session closes too or not?

Taz
  • 5,755
  • 6
  • 26
  • 63
  • Does `mcparallel` work for you? https://stat.ethz.ch/R-manual/R-devel/library/parallel/html/mcparallel.html – Roman Luštrik Sep 17 '17 at 16:40
  • @Roman Luštrik, yes, but does `mcparallel` let me create independent session which works even after I close app? – Taz Sep 17 '17 at 16:45
  • I don't have access to unix machine so I can't really test it out. – Roman Luštrik Sep 17 '17 at 16:47
  • @Taz Do you have this on a github? I'm trying to do something very similar (let users build a ppt using shiny) and this would probably give me a huge leg up on things. – Richard Lusch Mar 07 '18 at 20:17
  • @Richard Lusch what I use in client's app is much more developed version of this: https://github.com/Tazovsky/shiny-async-demo + `officer` R package. If you need any details, just email me ;) – Taz Mar 08 '18 at 14:22

2 Answers2

10

I was lucky enough to be at London EARL this week and I think one of the best presentations I saw there was about exactly this (by Joe Cheng). You would need the promises package for this to work and as it says on the documentation a special version of shiny devtools::install_github("rstudio/shiny@async") that supports asynchronous programming.

You can find a first documentation here on how this works by using dplyr and promises (future is also compatible).

As a small example (taken from the documentation), running an intensive calculation using the following:

read.csv.async("data.csv") %...>%
  filter(state == "NY") %...>%
  arrange(median_income) %...>%
  head(10) %...>%
  View()

would essentially return the console cursor back, allowing you to run any other command you want and would automatically open the View tab once this was finished. I might be able to dig out a shiny example in a bit, but keep in mind this is still under development and will be released before the end of the year (with a more comprehensive documentation I would imagine).

LyzandeR
  • 37,047
  • 12
  • 77
  • 87
  • 3
    Author of future here; I'm confirming what Joe's working on here will solve this. (The part where OP wants to close the app and get back to it later is trickier; I've got support for persistent serializable and reloadable futures on my todo list but that'll take some time) – HenrikB Sep 17 '17 at 17:17
1

So I made some example workaround using future package. Code executes in separate session (cluster) even when app is closed. I think the next step is just to figure out how app should check if process is still running or is finished. Any ideas?

library(future)
cl <- parallel::makeCluster(2L)
plan(cluster, workers = cl)

server <- function(input, output) {
  observeEvent(input$run, {

    iteration <- as.numeric(input$iteration)
    path <- input$path

    future::future({
      writeLog <- function(n, path) {
        file.remove(path)
        for (i in 1:n) {
          cat("#", i, "-",  as.character(Sys.time()), "\n", file = path, append = TRUE)
          Sys.sleep(1)
        }
      }
      writeLog(iteration, path)
    }, globals = c("iteration", "path"))
  })
}

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      tags$div("This app writes to file in cluster which means it is computed in parallel to this session. 
               It will execute even when app is closed.")
      , br()
      , shiny::textInput("path", "Path to log file", value = "/src/dev/export_performance/future.log")
      , shiny::textInput("iteration", "Iteration number", value = 60)    
    ),
    mainPanel(
      br()
      , actionButton("run", "Run future")
    )
  )
)

shinyApp(ui = ui, server = server)
Taz
  • 5,755
  • 6
  • 26
  • 63