rplumber

Is there a way to supply a clean R session for every call to a plumber API in R?


I have access to a plumber API which handles some automatic pipelining workflows for me. However, every job runs on the same R session and I'm a bit concerned regarding different calls interfering with each other.

Off to top of my head the wish is that every call starts on a fresh R session. When regularly programming in R I would just restart the session and have a clean setup but with the plumber server this seems to be "not so easy". Some reading also revealed that "cleaning up" your R session without a restart isn't a straight forward thing.

Of course I'm also open to alternative suggestions. Any suggestions and insights are welcome. In the end I would simply like the API calls to return the same thing if the inputs are the same.


Solution

  • As suggested in the comments by Konrad Rudolph, callr does the job very well.

    Doing 1.-3. (see code chunk) several times for different packages will show that every run is performed in a fresh R session (see process_id) and hence that the search paths are independent.

    # put this code into the file plumber.R and run:
    # plumber::plumb("plumber.R")$run(port=9999)
    # 1. click "Try it out"
    # 2. enter a package name that you want to try
    # 3. click "Execute"
    
    #* @get /search_path
    #* @param lib library to load
    function(lib) {
      # callr runs the anonymous function in a fresh session 
      rp <- callr::r_bg({
        function(lib) {
          library(package = lib, character.only = TRUE)
          return(list(search_path = search(),
                      process_id = Sys.getpid(),
                      the_time = Sys.time()))
        }
      },
      args = list(lib = lib))
      
      # wait for a result with 30 seconds timeout
      rp$wait(30000)
      if(rp$is_alive() == TRUE) {
        rp$kill()
        stop("time exceeded")
      }
      # return the result
      rp$get_result()
    }
    

    A bit offtopic but maybe also interesting in this context: Allowing parallel requests can be enabled via the future package.

    # submit two requests for packages within less than 10 seconds of
    # each other and compare the time stamps to see that they were processed
    # in parallel and in different sessions
    
    future::plan(future::multisession)
    
    #* @get /search_path
    #* @param lib library to load
    function(lib) {
      future::future({
        # callr runs the anonymous function in a fresh session 
        rp <- callr::r_bg({
          function(lib) {
            Sys.sleep(10)
            library(package = lib, character.only = TRUE)
            return(list(search_path = search(),
                        process_id = Sys.getpid(),
                        the_time = Sys.time()))
          }
        },
        args = list(lib = lib))
        
        # wait for a result with 30 seconds timeout
        rp$wait(30000)
        if(rp$is_alive() == TRUE) {
          rp$kill()
          stop("time exceeded")
        }
        # return the result
        rp$get_result()
      }, seed = TRUE)
    }