Synchronicity
Synchronicity

Reputation: 143

Quit a Plumber API once a condition is met

I am trying to run a Plumber API inline to receive an input, and once the proper input is received and a specified condition is met, the input is returned to the globalenv and the API closes itself such that the script can continue to run.

I've specified a condition within a @get endpoint that calls quit(), stop() etc, none of which successfully shut down the API.

I've attempted to run the API in parallel using future such that the parent script can close the Plumber API.

It appears that there isn't actually a method in the Plumber API class object to close the Plumber API, and the API can't be closed from within itself.

I've been through the extended documentation, SO, and the Github Issues in search of a solution. The only semi-relevant solution suggested is to use R.Utils::withTimeout to create a time-bounded timeout. However, this method is also unable to close the API.

A simple use case: Main Script:

library(plumber)
code_api <- plumber::plumb("code.R")
code_api$run(port = 8000)

code.R


#' @get /<code>
function(code) {
  print(code)
  if (nchar(code) == 3) {
    assign("code",code,envir = globalenv())
  quit()}
  return(code)
}
#' @get /exit
function(exit){
  stop()
}

The input is successfully returned to the global environment, but the API does not shut down afterward, nor after calling the /exit endpoint.

Any ideas on how to accomplish this?

Upvotes: 1

Views: 929

Answers (2)

dvasilen
dvasilen

Reputation: 21

Running a Plumber in the terminal might work in some cases but as I needed access to the R session (for insertText) I had to come up with the different approach. While not ideal the following solution worked:

# plumber.R
#* Insert 
#* @param msg The msg to insert to the cursor location 
#* @post /insert
function(msg="") {
  rstudioapi::insertText(paste0(msg))
  stop_plumber(Sys.getpid())
}

.state <- new.env(parent = emptyenv())  #create .state when package is first loaded

stop_plumber <- function(pid) {
  trml <- rstudioapi::terminalCreate(show = FALSE)
  Sys.sleep(2) # Wait for the terminal to initialize  
  # Wait a bit for the Plumber to flash the buffers and then send a SIGINT to the R session process,
  # to terminate the Plumber
  cmd <- sprintf("sleep 2 && kill -SIGINT %s\n", pid)
  rstudioapi::terminalSend(trml, cmd)
  .state[["trml"]] <- trml  # store terminal name
  invisible(trml)
  Sys.sleep(2) # Wait for the Plumber to terminate and then kill the terminal
  rstudioapi::terminalKill(.state[["trml"]])  # access terminal name
}

Upvotes: 1

Phil
Phil

Reputation: 1209

You could look at Iterative testing with plumber @Irène Steve's, Dec 23 2018 with:

  • trml <- rstudioapi::terminalCreate()
  • rstudioapi::terminalKill(trml)

excerpt of her article (2nd version of 3):

.state <- new.env(parent = emptyenv()) #create .state when package is first loaded

start_plumber <- function(path, port) {
    trml <- rstudioapi::terminalCreate(show = FALSE)
    rstudioapi::terminalSend(trml, "R\n") 
    Sys.sleep(2)
    cmd <- sprintf('plumber::plumb("%s")$run(port = %s)\n', path, port)
    rstudioapi::terminalSend(trml, cmd)

    .state[["trml"]] <- trml #store terminal name
    invisible(trml)
}

kill_plumber <- function() {
    rstudioapi::terminalKill(.state[["trml"]]) #access terminal name
}

Upvotes: 2

Related Questions