New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async process blocks shiny app within "user session" #23
Comments
|
Thanks for the detailed and thoughtful issue report. I suspect you won't like this answer, but this behavior is by design. I go into some detail about how this works in this section of the docs: https://rstudio.github.io/promises/articles/shiny.html#the-flush-cycle The goal, at least for this release of Shiny, is not to allow this kind of intra-session responsiveness, but rather, inter-session; i.e., running an async operation won't make its owning session more responsive, but rather will allow other sessions to be more responsive. If you really must have this kind of behavior, there is a way to work around it. You can "hide" the async operation from the Shiny session (allowing the session to move on with its event loop) by not returning your promise chain from your observer/reactive code. Essentially the async operation becomes a "fire and forget". You need to hook up a promise handler to have some side effect; in the example below, I set a Some caveats to this approach:
library("shiny")
library("promises")
library("dplyr")
library("future")
plan(multiprocess)
# A function to simulate a long running process
read_csv_async = function(sleep, path){
log_path = "./mylog.log"
pid = Sys.getpid()
write(x = paste(format(Sys.time(), "%Y-%m-%d %H:%M:%OS"), "pid:", pid, "Async process started"), file = log_path, append = TRUE)
Sys.sleep(sleep)
df = read.csv(path)
write(x = paste(format(Sys.time(), "%Y-%m-%d %H:%M:%OS"), "pid:", pid, "Async process work completed\n"), file = log_path, append = TRUE)
df = read.csv(path)
df
}
ui <- fluidPage(
actionButton(inputId = "submit_and_retrieve", label = "Submit short async analysis"),
br(),
br(),
tableOutput("user_content"),
br(),
br(),
br(),
hr(),
sliderInput(inputId = "hist_slider_val",
label = "Histogram slider",
value = 25,
min = 1,
max = 100),
plotOutput("userHist")
)
server <- function(input, output){
parent_pid = Sys.getpid()
# When button is clicked
# load csv asynchronously and render table
data <- reactiveVal()
observeEvent(input$submit_and_retrieve, {
data(NULL)
future({ read_csv_async(10, "./data.csv") }) %...>%
data() %...!% # Assign to data
(function(e) {
data(NULL)
warning(e)
session$close()
})
# Hide the async operation from Shiny by not having the promise be
# the last expression.
NULL
})
output$user_content <- renderTable({
req(data()) %>% head(5)
})
# Render a new histogram
# every time the slider is moved
output$userHist = renderPlot({
hist(rnorm(input$hist_slider_val))
})
}
shinyApp(ui, server)If lots of users have a strong need for this kind of thing, we can look into ways to support non-blocking-even-for-the-current-session abstractions more officially, and safely, than this. Please (P.S.: There should be no need to |
|
Hello @jcheng5, Many thanks for your quick and crystal clear response! I read most of the doc but I conceal I did skim-read the flush-cycle section thinking it was explaining some details I may not need. I agree there are 2 separate use cases: 1 - Submit and forget:
2 - Submit and retrieve:
So far my strategy for "Submit and forget" is to invoke a R script in a separate process with a system call, example: system("Rscript /path/to/script.R arg1 arg2 ...", wait = FALSE)This does exactly what I am looking for since the async process will terminate on its own when it has completed processing. Thanks again! |
|
You can also see the same question on Stackoverflow at https://stackoverflow.com/questions/50165443/async-process-blocking-r-shiny-app Feel free to keep an eye on the up-votes there as well. Thanks! |
|
@raphaelvannson I'd add just one more thing to your very useful reply. Instead of calling (callr doesn't yet integrate with promises automatically but I suspect we'll do that sooner rather than later--it should be very straightforward.) |
|
Hello @jcheng5, Thanks a lot for the suggestion - I came across |
|
Great discussion. Thanks for starting it @raphaelvannson! I was hoping to use promises to execute cross-validation (i.e., run the CV in a separate process and return the result when done). However, I was hoping the user would then be able to do 'other things' while the CV was running. Seems like |
|
Hi, @jcheng5 I gave a lightning talk about async Shiny at eRum 2018. After my talk, all the questions were about allowing intra-session responsiveness, so it's definitely a feature useR-s are looking for. Thanks for the great work! |
|
OK, thanks for the feedback @dgyurko! |
|
Hi, Here is a working example (hopefully helping others searching for a similar solution), which in my eyes seems to be a little bumpy (I’m far away from being a shiny expert..). Maybe someone has ideas to realize the same behavior but in a more elegant way? |
|
I developed a solution to this in my package, The solution involves an observer checking for the existence of a per-session unique file (generated at the beginning of the session). When the future is called, rather than returning the object itself, it ends in a saveRDS call with the per-session filename generated at the beginning of the session. The future object is only used to determine if the future has resolved--it carries no data. The observer checks for the existence of the unique file and that the future has been resolved: when those conditions are met, it loads the value into a reactiveVal. The reactive value is the one that goes to the outputs. I avoid race conditions by disabling input buttons with I'm not a Shiny expert, but this solution seems to work pretty well when I'm testing with multiple sessions locally. |
|
@tylermorganwall thanks for your input! It’s been a while but now I’m coming back to this. I tried to apply your suggestions to my earlier example – and would be glad to get some feedback if I got you right or did something wrong: The approach indeed isn’t blocking the whole app, but it seems to slow down the execution of the “fast” observer (which is not the case using the callr-approach) while the promise isn’t resolved – also among multiple local sessions (have a look at the refreshing-rate of the random number – 5 seconds fast – 5 seconds slow). |
|
Furthermore, here is a solution avoiding the need to save a file (saveRDS), unfortunately with the same behavior: |
|
@vnijs it seems you weren't advertising your investigation sufficiently (Or I didn' read as careful as I should...). Adding library("future.callr") |
|
Hello, Just dropping by to support the need for a "non-blocking-even-for-the-current-session". My use case : I have a page with several graphs, one taking several seconds to compute. What I'm doing is using the method described in the second comment to this issue, so that users can see graphs minimal reprexBlocking when performing long computationlibrary(shiny)
ui <- fluidPage(
column(6, plotOutput("one")),
column(6, plotOutput("two")),
column(6, plotOutput("three")),
column(6, plotOutput("four"))
)
server <- function(input, output, session) {
output$one <- renderPlot({
# Simulating long computation
Sys.sleep(5)
plot(iris)
})
output$two <- renderPlot({
plot(airquality)
})
output$three <- renderPlot({
plot(mtcars)
})
output$four <- renderPlot({
plot(cars)
})
}
shinyApp(ui, server)Non blockinglibrary(shiny)
library(promises)
library(future)
plan(multisession)
ui <- fluidPage(
column(6, plotOutput("one")),
column(6, plotOutput("two")),
column(6, plotOutput("three")),
column(6, plotOutput("four"))
)
server <- function(input, output, session) {
plotiris <- reactiveVal()
plotiris(NULL)
future({
Sys.sleep(5)
iris
}) %...>%
plotiris() %...!%
(function(e){
plotiris(NULL)
warning(e)
})
output$one <- renderPlot({
req(plotiris())
plot(plotiris())
})
output$two <- renderPlot({
plot(airquality)
})
output$three <- renderPlot({
plot(mtcars)
})
output$four <- renderPlot({
plot(cars)
})
}
shinyApp(ui, server)
|
|
I'd like to +1 for a need of intra session async. Many thanks! |
|
Also will add a plug for a need of intra session async. |
|
Excellent sum up of the topic. This post should definitely be part of the documentation. Just for the record, as a python/React programmer that is just moving into the Shiny/R world, I must say I am quite impressed how far R has gone since the last time I used it, 10 years ago. |
|
This is really problematic in our application where we want to close the modal call runjs to update the front-end code and later update the backend. It's not possible, because removeModal is executed after promise is resolved. The same behavior is with Promises are useless if they are no async in single session, in your application we use docker+swarm and there is always single user per R-process, so other uses of promises as per docs are of no use for us. |
|
Hey @jcubic, You might want to have a look at https://engineering-shiny.org/optimizing-shiny-code.html#asynchronous-in-shiny which documents how to uses promises for inner-session asynchronousity :) |
|
@jcubic Can you give a more precise description of what exactly you're trying to do? Or even better, provide a minimal reprex? I developed a technique last week for one of my own apps that may help, but it would take some time to extract/write up, so I want to be sure I understand your scenario. |
|
I have quite complex code but you can sum it up into something like this: I can try to create proper reprex but this is what I actually have in my code. wrapping expensiveCalculation in future or later don't make any difference because removeModal fire (the modal is closed) after the expensive calculation. What I need is to hide the modal, call runjs instantly and run expensive calculation in background. To give you perspective, we have table of data and user can exclude part of the data that he think was bad for some reason he can exclude measurement, row or section that have more then one row. It was working fine until we start working on new data input, that have lot of data, and user can exclude in one go big chunk of data (exclude section). Exclude happen on backend in R and on front-end when data rows in table should look differently. It's easier to update the data in front-end using jQuery then to generate new table in R. |
|
It doesn't change if you just put |
|
Wait, I think your analysis might not be correct. library(shiny)
library(future)
library(promises)
plan(multisession)
expensiveCalcuation <- function() {
future({
Sys.sleep(5)
})
}
ui <- fluidPage(
actionButton("show", "Show modal")
)
server <- function(input, output, session) {
observeEvent(input$show, {
showModal(modalDialog(
"This is a modal",
footer = actionButton("ok", "OK")
))
observeEvent(input$ok, once = TRUE, {
removeModal()
expensiveCalcuation() %...>% {
message("Expensive calculation complete")
}
})
})
}
shinyApp(ui, server)If you show the dialog and press "OK", the dialog dismisses immediately, and only after 5 seconds does "Expensive calculation complete" get logged. Am I missing something? |
|
Will check tomorrow at work, maybe something else is happening, one thing though is that I use |
|
A insufficient number of workers may also cause the blocking. |
|
My example closes the modal immediately even if you do this: observeEvent(input$ok, once = TRUE, {
removeModal()
Sys.sleep(5)
})Is it possible some other reactive logic is happening before your |
|
I have no idea what is happening, prints are executed as they should if I have |
|
What I've found is that there was message in web socket: but the modal was not removed instantly in our application, do you have any why this might happen? Just before this message there was: Bu it seems that the same happen with simple example. |
|
@jcubic Let's take the discussion off of this thread, since it's no longer related to promises. You can email me at joe@rstudio.com. |
|
@jcheng5 |
|
Hi @pablo-rodr-bio2, I'm not sure why you'd do the same for an That said, if you feel like you have a good reason to perform an async task in an |
|
Thanks for the answer! |
|
Sorry for the delay, here is an example with my use case:
If I run this, the 4 messages are printed at once when the |
I just wanted to leave a note here, that by now https://rstudio.github.io/promises/articles/future_promise.html |
Hello,
I am having trouble making a simple shiny app with a non-blocking async process.
I am not a beginner in R or multi-process programming, read the documentation thoroughly yet I cannot get this to work how it should so I am posting a question here in the hopes you can help me figure out what I am doing wrong.
Environment
Mac OS 10.12
$ R --version R version 3.4.3 (2017-11-30) -- "Kite-Eating Tree"One side question on the shiny package version:
https://rstudio.github.io/promises/articles/intro.html says it should be >=1.1, but even installing with devtools, the version remains 1.0.5... . Is this an issue or is there a typo in the doc?
Example of issue
I have implemented this simple shiny app inspired from the example at the URL mentioned above and the vignettes mentioned below.
The shiny app has 2 "sections":
read_csv_asyncwhich sleeps for a few seconds, reads a csv file into a data frame. The df is then rendered below the button.The issue is that the second functionality (histogram plot update) is blocked while the async processing is occurring.
global.R
ui.R
server.R
data.csv
Question
I can't get the non-blocking async processing to work in shiny: the histogram update is always blocked while the async process is running.
I have tried other strategies involving
observeEvent()or even simpler examples with the same resutls.Can you provide a simple example of a shiny app including a non-blocking example of an async processing or let me know what I am doing wrong here?
I have thoroughly read the vignettes listed below:
https://cran.r-project.org/web/packages/promises/vignettes/intro.html
https://cran.r-project.org/web/packages/promises/vignettes/overview.html
https://cran.r-project.org/web/packages/promises/vignettes/futures.html
https://cran.r-project.org/web/packages/promises/vignettes/shiny.html
Thanks!
The text was updated successfully, but these errors were encountered: