Stop Shiny App after specific amount of time without activity

92 Views Asked by At

I have my Shiny App which works fine, but I would like to add a functionality that counts the application inactivity time and closes the application after the set time is exceeded.

It is easy to close an application using the stopApp() function, but how to check the application inactivity time?

I have no idea how to do this, does anyone have any ideas? I will be grateful for your help :)


EDIT:

The solutions proposed by Mwavu and Judy below works fine, but not perfectly as I would like, there is one case that this solution does not handle - a long process in the application, the result of which the user is waiting for. In this case, if the process lasts longer than the inactivity limit, the application will not close during the process because the session is blocked, but will do so immediately after it is released. I would like this process to be treated as "application activity" and only after its completion, instead of killing the application, the inactivity time should be counted from scratch.

1

There are 1 best solutions below

2
Judy On

The Shiny invalidateLater() function will help you accomplish this. It reacts or re-executes when the specified number of milliseconds has passed. Since you are also interested not in just the passage of time, but the time since the last user interaction with the Shiny app, you'll need another reactive variable that updates when a user takes an action. Here's a small example:

library(shiny)

# Define UI for application that draws a histogram
ui <- fluidPage(
  
  # Application title
  titlePanel("Checking Application Idle Time"),
  
  # Sidebar with a slider input for number of bins 
  sidebarLayout(
    sidebarPanel(
      sliderInput("bins",
                  "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)
    ),
    
    # Show a plot of the generated distribution
    mainPanel(
      textOutput("update"),
      checkboxInput("check", "A user action, click here"),
      actionButton("startProcess", "Start long process"),
      plotOutput("distPlot")
    )
  )
)

# Define server logic required to draw a histogram
server <- function(input, output, session) {
  
  # choose number of milliseconds for how often to check idle time
  checkInterval <-  10000 # this is 10 seconds but in reality use much longer
  
  # choose number of idle minutes that should trigger the app to close
  maxIdleMins <- 2
  
  # create reactive variable to track the number of minutes app has been idle
  idle <- reactiveVal(0) 
  
  # create a reactive variable to flag when the processor is busy
  # with a computationally-intensive function
  processorBusy <- reactiveVal(FALSE)
  
  # initialize the lastActivity reactive variable value to the current time
  lastActivity <- reactiveVal(isolate(Sys.time()))
  
  observe({   
    # This reactive expression will re-evaluate after checkInterval milliseconds 
    # has passed due to invalidateLater function.
    # It will also re-evaluate if any user application has changed the value of 
    # lastActivity(). When the processor is busy with a long computation,
    # invalidateLater will not execute and idle will not increase. 
    # This protects the app from timing out while a calculation is being made.
    
    if (!processorBusy()){
      invalidateLater(checkInterval, session)
      idle(difftime(isolate(Sys.time()), lastActivity(), units = "mins")) # number of minutes since last activity
    }
    
    # id idle longer than maxIdleMins minutes, close application
    if (idle() > maxIdleMins) {
      stopApp()
    }
  })
  
  observeEvent(input$check,{
    lastActivity(isolate(Sys.time()))
    
  })
  
  longprocess <- function(){
    # mimic a long computation that will take more than maxIdleMins minutes
    Sys.sleep(200) # 200 seconds
    
    # before returning from this function, update the value of lastActivity
    lastActivity(isolate(Sys.time()))
    return("done")
  }
  
  observeEvent(input$startProcess,{
    lastActivity(isolate(Sys.time()))
    
    # set processorBusy to TRUE to disable invalidateLater and idle
    processorBusy(TRUE)
    
    # call to a long computational function
    result <- longprocess()
    
    lastActivity(isolate(Sys.time()))
    
    processorBusy(FALSE) # now resume idle checks
  })
  
  output$update <- renderText({
    paste0("Minutes since last user activity:", round(idle(), digits = 1))
  })
  
  output$distPlot <- renderPlot({
    # generate bins based on input$bins from ui.R
    x    <- faithful[, 2]
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    
    lastActivity(isolate(Sys.time()))
    
    # draw the histogram with the specified number of bins
    hist(x, breaks = bins, col = 'darkgray', border = 'white',
         xlab = 'Waiting time to next eruption (in mins)',
         main = 'Histogram of waiting times')
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

Note that every time you click the checkbox or the plot re-renders due to user interaction with the slider, the time since user activity resets to 0. Once the app has been idle the specified length of time, it closes.

Edit: If your application includes functions with long computation times, you don't want the application to timeout due to that delay. You must either:

  1. Set processorBusy(TRUE) before long computation and back to processorBusy(FALSE) after computation complete
  2. Set the value of maxIdleMins to be LONGER than the maximum time you expect your computation to take