I am using Shiny with a local server installed.
My Shiny app runs a heavy local program using system
/system2
/processx::run
. I am running it synchronously (wait=T
). If the user closes the browser window of the Shiny, I would love the heavy program to end. If the user re-opens the browser window, I want the Shiny app to be ready again for executing the local program.
How can this be achieved?
When I use system
/system2
/processx::run
, it seems that the app waits for the heavy program to finish and does not stop it upon close.
Reprex:
library(shiny)
library(processx)
ui <- fluidPage(
actionButton("runBtn", label="Run a program that consumes many resources") ,
)
server <- function(input, output, session) {
observeEvent(input$runBtn,
run("sleep", "240"))
}
shinyApp(ui, server)
When I close the browser window with the reprex, and then try to re-open it, it takes time till the process ends. I want it to be available more or less immediately.
P.S. I am using Linux; a system-specific hack is fine.
@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)