Search code examples
rshinyprogress-baripcfuture

R-Shiny App does not update progress bar correctly when using futures


I have a shiny app which should run calculations on multiple cores while providing feedback via progress bars. This works fine as long as I do not process the results of the futures further (see working example below). As soon as I use the results afterwards the progress bars do not update until all futures are done.

I use the packages future, promises and ipc for interprocess communication. I think the problem is that R wants to continue working with the futures as soon as the results are coming in. I tried to stop the algorithm with commands like resolved() or resolve() but without any progress.

library(shiny)
library(future)
library(promises)
library(ipc)

plan(list(multiprocess, sequential))

ui <- fluidPage(
    actionButton(inputId = "go", label = "Launch calculation")
)

server <- function(input, output, session) {

    observeEvent(input$go, {
        x <- list()
        N = availableCores()
        Tasks = rep(10, N) #Number of sequential tasks per core

        progress = list() #A list to maintain progress for each run

        resultsvec <- c()
        for(j in 1:N){

            progress[[j]] = AsyncProgress$new(message = paste("analysis, core ",j))

            x[[j]] <- future({
                for(l in 1:Tasks[j]){
                    progress[[j]]$inc(1/Tasks[j])
                    resultsvec <- append(resultsvec, l)
                    Sys.sleep(1)
                }
                resultsvec
                progress[[j]]$close()
            })
        }
        result <- lapply(x, value)
        #... do stuff with result
    })
}

shinyApp(ui = ui, server = server)

Here is the server function where the progress bars are updated correctly.

server <- function(input, output, session) {

    observeEvent(input$go, {
        x <- list()
        N = availableCores()
        Tasks = rep(10, N) #Number of sequential tasks per core
        progress = list() #A list to maintain progress for each run

        for(j in 1:N){

            progress[[j]] = AsyncProgress$new(message = paste("analysis, core ",j))

            x[[j]] <- future({
                for(l in 1:Tasks[j]){
                    progress[[j]]$inc(1/Tasks[j])
                    Sys.sleep(1)
                }
                progress[[j]]$close()
            })
        }
    })
}

Solution

  • I managed to solve the problem for my needs, although the solution does not use futures anymore. I switched over to the doSNOW package. But as far as I am aware there is no option in doSNOW or other parallel packages other than the future/promises that allow interprocess communication. So this is my workaround. I used one progress bar for the whole process as opposed to above.

    library(shiny)
    library(doSNOW)
    
    ui <- fluidPage(
        actionButton(inputId = "go", label = "Launch calculation")
    )
    
    server <- function(input, output, session) {
    
        observeEvent(input$go, {
    
            Tasks <- 40 #now total tasks to do
            runs <- 10 #splitting of progress bar. 10 means every 10% it gets updated. 20 every 5% etc.
    
            taskvec <- rep(Tasks %/% runs, runs)
    
            if (Tasks %% runs != 0){
                taskvec[1:(Tasks %% runs)] <- taskvec[1:(Tasks %% runs)] + 1
            }
    
            resultsvec <- c()
    
            cl <- makeCluster(2)
            registerDoSNOW(cl)
    
            withProgress(message = "Analysis", value = 0,{
                for (j in 1:runs) {
    
                    resultsvec_sub <- foreach(i = 1:taskvec[j], 
                                              .combine = append) %dopar% {
                                                  f <- i
                                                  Sys.sleep(1)
                                                  return(f)
                                              }
                    resultsvec <- append(resultsvec, resultsvec_sub)
                    incProgress(1/runs)
                }
            })
            stopCluster(cl)
            #do stuff with resultsvec..
        })
    }
    
    shinyApp(ui = ui, server = server)
    

    As you can see I split the Tasks before I assign them to the cores and update the progress bar when each split is done on all cores. This is of course more inefficient because when almost all of tasks within a split are done. Some cores may be idle until the other cores are done and the next split starts. One could improve the splitting process/distribution of tasks but it is working now.