rrenvrprofile

Persist R function defined in Rprofile even if user clears environment


I have an rprofile that is used for multiple users spinning up sessions in a shared jupyterhub. In it I define a function that sets some parameters for closing out idle sessions (basically it periodically checks when the user activity was and if it was last a threshold, it kills the session). But, if a user clears their workspace/environment, it deletes that function and no longer has it running. Can I get around this somehow? I’ve tried:

defining it in a separate env in the rprofile including it in a finalizer in the r profile (but that only runs on shutdown, not clearing environment/workspace) using gdata::keep in the r profile telling people not to clear their environment (jk but kinda not, obviously impractical) Any suggestions? For reference if it helps, this is the code I need to persist/keep running:

# track last activity
  update_activity <- function(expr, value, ok, visible) {
    assign(".last_activity", Sys.time(), envir = .GlobalEnv)
    #cat(sprintf("[watchdog] Updated at %s\n", Sys.time()))
    return(TRUE)
  }
  addTaskCallback(update_activity, name = "update_activity")
# check how long since last activity
  check_idle <- function(timeout_minutes = 1) { # minutes
    now <- Sys.time()
    idle_time <- as.numeric(difftime(now, .GlobalEnv$.last_activity, units = "mins"))
    #cat(sprintf("[watchdog] Idle for %.2f minutes\n", idle_time))
if (idle_time > timeout_minutes) {
      cat("Session has been idle too long — exiting.\n")
      .Last()
      q("no")
    } else {
      later::later(function() {
        check_idle(timeout_minutes)
      }, delay = 60) # note - seconds!
    }
}
  later::later(function() {
    check_idle(180) # minutes
  }, delay = 60) # seconds

Solution

  • Edit: changed from using rlang::env_unlock() (which is deprecated) to something more local.

    Somes notes about this:

    .no_idle <- new.env()
    .no_idle$.update_activity <- function(expr, value, ok, visible) {
      assign(".last_activity", Sys.time(), envir = as.environment(".no_idle"))
      cat(sprintf("[watchdog] Updated at %s\n", Sys.time()))
      return(TRUE)
    }
    .no_idle$.last_activity <- Sys.time()
    .no_idle$.check_idle <- function(timeout_minutes = 1) { # minutes
      env <- as.environment(".no_idle")
      now <- Sys.time()
      idle_time <- as.numeric(difftime(now, env$.last_activity, units = "mins"))
      cat(sprintf("[watchdog] Idle for %.2f minutes\n", idle_time))
      if (idle_time > timeout_minutes) {
        cat("Session has been idle too long — exiting.\n")
        q("no")
      } else {
        cat("Session is still being used.\n")
      }
      later::later(function() {
        env$.check_idle(timeout_minutes)
      }, delay = 15) # note - seconds!
    }
    

    It worked:

    attach(.no_idle)
    rm(.no_idle)
    search()
    #  [1] ".GlobalEnv"        ".no_idle"          "ESSR"              "package:stats"     "package:graphics"  "package:grDevices" "package:utils"    
    #  [8] "package:datasets"  "package:r2"        "package:methods"   "Autoloads"         "package:base"     
    addTaskCallback(as.environment(".no_idle")$.update_activity, name = "update_activity")
    # update_activity 
    #               1 
    # [watchdog] Updated at 2025-06-02 20:19:09.435791
    
    later::later(as.environment(".no_idle")$.check_idle(), delay = 15)
    # [watchdog] Idle for 0.00 minutes
    # Session is still being used.
    # [watchdog] Updated at 2025-06-02 20:19:09.505805
    
    ls(all.names = TRUE)
    # [1] ".Random.seed"
    # [watchdog] Updated at 2025-06-02 20:19:17.225449
    # [watchdog] Idle for 0.12 minutes
    # Session is still being used.
    # [watchdog] Idle for 0.37 minutes
    # Session is still being used.
    # [watchdog] Idle for 0.62 minutes
    # Session is still being used.
    
    1
    # [1] 1
    # [watchdog] Updated at 2025-06-02 20:20:12.52559
    # [watchdog] Idle for 0.14 minutes
    # Session is still being used.
    # [watchdog] Idle for 0.39 minutes
    # Session is still being used.
    # [watchdog] Idle for 0.64 minutes
    # Session is still being used.
    # [watchdog] Idle for 0.89 minutes
    # Session is still being used.
    # [watchdog] Idle for 1.14 minutes
    # Session has been idle too long — exiting.
    # Process R:2 finished at Mon Jun  2 20:21:24 2025