Abort a running task/process by pressing a key

I'm developping an app built on top of a CLI to call various dependent tools. Some of these commands are relatively long to run (up to several minutes) depending on the size of the data. I use shinycssloaders::showPageSpinner() to display a spinner while a task is running. This works great but it's currently not possible to abort a job after it's been launched.

How could I kill a running process (for example by pressing the esc key)? Is it possible to do it in R only or should I necessarily use JavaScript?

Here's a mini example of what I've:

library(shiny)

ui <- fluidPage(
  actionButton("btn", "Click me!")
)

server <- function(input, output, session) {
  observeEvent(input$btn, {
    isolate({
      tryCatch(
        shinycssloaders::showPageSpinner(
          processx::run("sleep", "10"),
          type = 1L
        ),
        error = function(e) {
          shinycssloaders::hidePageSpinner()
          stop(e$stderr)
        }
      )
    })
  })
}

shinyApp(ui, server, options = list(launch.browser = TRUE))

You could use ?processx::process instead of processx::run which provides us with a kill method.

Thanks for your input.

Yes there's the process class and the kill() method but I couldn't make them work with shinycssloaders::showPageSpinner().

Anyway, I realised that when the process is running, the app is technically freezing so I can't listen to a keydown event until the process ends. I already tried running the process in an ExtendedTask class without success. So what I'm trying to do won't work unless I manage to fix my ExtendedTask problem.

Unless you are calling process$wait the process runs in parallel and won't freeze your main task.

I'm not calling the wait() method and it's the same if I use run(). I also thought it would run in a parallel process and won't cause any freeze issue but it's definitely not the case. Unfortunately I can't share the code.

Here's an example to simulate the issue:

library(shiny)

ui <- fluidPage(
  textOutput("current_time"),
  actionButton("btn", "Click me!")
)

server <- function(input, output, session) {
  output$current_time <- renderText({
    invalidateLater(1000)
    format(Sys.time(), "%H:%M:%S %p")
  })
  observeEvent(input$btn, {
    isolate({
      tryCatch(
        shinycssloaders::showPageSpinner(
          processx::run("sleep", "10"),
          type = 1L
        ),
        error = function(e) {
          shinycssloaders::hidePageSpinner()
          stop(e$stderr)
        }
      )
    })
  })
}

shinyApp(ui, server, options = list(launch.browser = TRUE))

When you click the button, the time stops during the entire runtime of the task.

Your example is still using processx::run, however I suggested using processx::process.

Please check the following:

library(shiny)
library(processx)
library(shinycssloaders)

ui <- fluidPage(
  textOutput("current_time"),
  actionButton("btn", "Start process"),
  actionButton("kill", "Kill process")
)

server <- function(input, output, session) {
  processRV <- reactiveVal()
  
  observeEvent(input$btn, {
    processRV(process$new("sleep", "10"))
  })
  
  observeEvent(input$kill, {
    req(processRV())
    processRV()$kill()
  })
  
  # Kill button is getting greyed out (not making sense)
  # observe({
  #   invalidateLater(1000)
  #   req(processRV())
  #   if(isTRUE(processRV()$is_alive())){
  #     shinycssloaders::showPageSpinner(type = 1L)
  #   } else {
  #     shinycssloaders::hidePageSpinner()
  #   }
  # })
  
  output$current_time <- renderText({
    invalidateLater(1000)
    if(!is.null(processRV())){
      paste(format(Sys.time(), "%H:%M:%S %p"), "| Process alive:", processRV()$is_alive()) 
    } else {
      format(Sys.time(), "%H:%M:%S %p")
    }
  })
}

shinyApp(ui, server, options = list(launch.browser = TRUE))


1 Like

Thanks. That answers my question.

I may have to use $wait() in the end because the app executes a shell command that generates an HTML file that will later be displayed in an iframe on the main page. I need to wait for the process to end before being able to show the output. Will see if I can adapt your example to my use case.

Unrelated but in my previous example, I use tryCatch(). I can't manage to capture errors when using process$new(), despite using stderr = "". How would you adapt your example to be able to capture potential errors?

Instead of invoking $wait() you could check if your process finished via process$poll_io(0)["process"] == "ready" as done here:

Another option would be to scan the filesystem for the resulting html file:

Regarding tryCatch: I haven't tested it but I guess the issue is that you'll receive character vectors but tryCatch is triggered by R error objects. Maybe you can listen on process$read_error_lines() and pass it to stop() for further error handling.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.