How to Stop Executing of R Code Inside Shiny (Without Stopping the Shiny Process)

How does one terminate all processes upon Shiny app end?

@PorkChop's comment is pointing in the right direction. However, I'd recommend using processx::process rather than run as it provides us with methods to control the started process from within R. See ?process. (run by the way is also based on the process class.)

The main problem here is, that running the process synchronously (wait=TRUE) blocks the R session. Accordingly onStop won't fire until R is back in control.
Therefore you can't trigger anything once the browser window was closed because the shiny-session continues to run until the external program is finished and R can close the shiny-session.

On session end, the below code checks if the asynchronously started process is still alive and kills it if necessary (tested on windows only).

library(shiny)
library(processx)

ui <- fluidPage(
actionButton("runBtn", label="Run a program that consumes many resources"),
actionButton("stopSession", "Stop session")
)

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

myProcess <- NULL

observeEvent(input$stopSession, {
cat(sprintf("Closing session %s\n", session$token))
session$close()
})

observeEvent(input$runBtn,
{
if(Sys.info()[["sysname"]]=="Windows"){
writeLines(text = c("ping 127.0.0.1 -n 60 > nul"), con = "sleep.bat")
myProcess <<- process$new("cmd.exe", c("/c", "call", "sleep.bat"), supervise = TRUE, stdout = "")
} else {
myProcess <<- process$new("sleep", "60", supervise = TRUE, stdout = "")
}
# myProcess$wait() # wait for the process to finish
})

onStop(function(){
cat(sprintf("Session %s was closed\n", session$token))
if(!is.null(myProcess)){
if(myProcess$is_alive()){
myProcess$kill()
}
}

})
}

shinyApp(ui, server)

Regarding the different session callback functions see this related post.


As requested here the process is wrapped in a reactiveVal:

library(shiny)
library(processx)

ui <- fluidPage(
actionButton("runBtn", label="Run a program that consumes many resources"),
actionButton("stopSession", "Stop session")
)

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

myProcess <- reactiveVal(NULL)

observeEvent(input$stopSession, {
cat(sprintf("Closing session %s\n", session$token))
session$close()
})

observeEvent(input$runBtn,
{
if(Sys.info()[["sysname"]]=="Windows"){
writeLines(text = c("ping 127.0.0.1 -n 60 > nul"), con = "sleep.bat")
myProcess(process$new("cmd.exe", c("/c", "call", "sleep.bat"), supervise = TRUE, stdout = ""))
} else {
myProcess(process$new("sleep", "60", supervise = TRUE, stdout = ""))
}
# myProcess()$wait() # wait for the process to finish
})

onStop(function(){
cat(sprintf("Session %s was closed\n", session$token))
if(!is.null(isolate(myProcess()))){
if(isolate(myProcess()$is_alive())){
isolate(myProcess()$kill())
}
}

})
}

shinyApp(ui, server)

Stop/restart execution of a reactive object in Shiny?

One alternative is to use shinyjs to disable (as @StéphaneLaurent suggested) and re-enable.

library(shiny)
library(shinyjs) # NEW

ui <- fluidPage(
shinyjs::useShinyjs(), # NEW
titlePanel("actionButton test"),
sidebarLayout(
sidebarPanel(
numericInput(
"n",
"N:",
min = 0,
max = 100,
value = 50
),
br(),
actionButton("goButton", "Go!"),
p("Click the button to update the value displayed in the main panel.")
),
mainPanel(verbatimTextOutput("nText"))
))

server <- function(input, output) {
ntext <- eventReactive(input$goButton, {
shinyjs::disable("goButton") # NEW
cat("sleeping for 30 seconds\n")
Sys.sleep(30)
shinyjs::enable("goButton") # NEW
input$n
})
output$nText <- renderText({
ntext()
})
}

shinyApp(ui = ui, server = server)

Granted, the shiny interface will still "hang" in a sense, but at least the button will not accept multiple presses. (You might want to consider future and promises.)

Prevent shiny app from breaking a while loop

The "standard" way to stop a Shiny application is to simply stop the R process that's serving it. You need a way to close the app without breaking. The stopApp() function does that, returning control of the thread back to the main flow of your script.

This example is from the help page at ?runApp, and I've made two additions commented below.

library(shiny)

x <- 1
while (x <= 5) {
runApp(list(
ui = bootstrapPage(
numericInput('n', 'Number of obs', 100),
plotOutput('plot'),
actionButton("done", "close") # added a close button
),
server = function(input, output) {
output$plot <- renderPlot({ hist(runif(input$n)) })
observeEvent(input$done, stopApp()) # added an observer to the close button
}
))
x <- x+1
}

You can also put an argument from the logic of the Shiny app inside stopApp(), so that you can use the result in your other calculations. For this example, if we wanted to know what the user input for n, we could use stopApp(input$n), and the whole app would return that value, like below.

x <- 1
while (x <= 5) {
user_input <- runApp(<code from above>)
x <- x+1
}

The important thing is to make it obvious to your users that they need to use your "close" button within the app, instead of closing the browser window or using the "Stop" button in RStudio. Either of those actions will interrupt your main script.


Edited to add:

There are actually some functions builtin to {shiny} to listen for a user's session ending (i.e. closing the browser window), onStop() and onSessionEnded(). You should check out the help page for them, but replacing my observeEvent(...) line with onStop(stopApp) (or onSessionEnded(stopApp)) seems to work just fine. Then you can remove the Close button from the UI.

How to kill shiny app in ESS without killing R process

Well, I finally found out the reason. I had the option

(setq comint-prompt-read-only t)

set somewhere in my init files. Apparently, when this option is set it becomes impossible (well, beyond me anyway) to send a kill signal to the R process. I don't understand what is happening though. If I run a server directly from httpuv I can kill it even with the option set, but not when running an app through shiny.



Related Topics



Leave a reply



Submit