In summary, I'm finding that when running httr::POST against a plumber api, within an R.utils::withTimeout, the post is throwing an error message which can't be suppressed. The error message is:
Error in .Call(R_curl_fetch_memory, enc2utf8(url), handle, nonblocking) : reached elapsed time limit
Things I've tried:
suppressmessages/warnings
tryCatch with no error response
Using sinks
I can't think of any other way of stopping this error. The code carries on running but it's resulting in users of the api getting spammed with error messages when checking for an available port to call. Any ideas welcome.
Reproducible example (note this uses rstudio jobs pkg and a separate plumber file, but i've tried without the jobs pkg and the issue persists so it's not connected to that.) :
plumber.R:
#* quick ping
#* #post /ping
function() {
list(msg = "you got here!")
}
#* slow 10s call
#* #post /slowfxn
function() {
Sys.sleep(10)
1
}
code which calls and produces errors:
#run the plumber file in a separate job:
r <- plumber::plumb("errortest/plumber.R")
job::job({r$run(swagger = F,port = 1111)})
#this function will throw the error if no response comes within 3s:
call_with_timeout = function() {
R.utils::withTimeout(
rawToChar(httr::POST("http://127.0.0.1:1111/echo")$content)
,timeout = 3,onTimeout = "silent")
}
#now call the fxn (again in a job) which takes 10 s to run:
job::job({httr::POST(url = "http://127.0.0.1:1111/slowfxn")})
#wait a second for that job to be initiated:
Sys.sleep(1)
#while that's running, try and call the quick fxn, with a 3 second timeout:
#try wrap it in a trycatch to suppress:
tryCatch( {
call_with_timeout()
},
error = function(x) {},
TimeoutException = function(x){},
warning = function(x) {}
)
suppressMessages(suppressWarnings(call_with_timeout()))
#not even a sink can stop it!
sink("delete.txt")
call_with_timeout()
sink(NULL)
Related
I have code that includes several initial checks of different parameter values. The code is part of a larger project involving several R scripts as well as calls from other environments. If a parameter value does not pass one of the checks, I want to
Generate a customizable result code
Skip the remaining code (which is not going to work anyhow if the parameters are wrong)
Create a log entry with the line where the error was thrown (which tells me which test was not passed by the parameters)
Print my customizable result code to the console (without a more detailed explanation / trace back from the error)
Otherwise, the remaining code should be run. If there are other errors (not thrown by me), I also need an error handling resulting in a customizable general result code (signalling that there was an error, but that it was not one thrown by me) and a more detailled log.
The result codes are part of the communication with a larger environment and just distinguishes between wrong parameter values (i.e., errors thrown by me) and other internal problems (that might occur later in the script).
I would like to use tryCatchLog because it allows me to log a detailed traceback including the script name (I am sourcing my own code) and the line number. I have not figured out, however, how to generate my own error code (currently I am doing this via the base function stop()) and pass this along using tryCatchLog (while also writing a log).
Example
In the following example, my parameter_check() throws an error via stop() with my result code "400". Using tryCatchLog I can catch the error and get a detailed error message including a traceback. However, I want to seperate my own error code (just "400"), which should be printed to the console, and a more detailed error message, which should go to a log file.
library(tryCatchLog)
parameter_check <- function(error) {
if (error){
stop("400")
print("This line should not appear")
}
}
print("Beginning")
tryCatchLog(parameter_check(error = TRUE),
error = function(e) {print(e)}
)
print("End")
Currently, the result is:
[1] "Beginn"
ERROR [2021-12-08 11:43:38] 400
Compact call stack:
1 tryCatchLog(parameter_check(0), error = function(e) {
2 #3: stop("400")
Full call stack:
1 tryCatchLog(parameter_check(0), error = function(e) {
print(e)
2 tryCatch(withCallingHandlers(expr, condition =
cond.handler), ..., finall
3 tryCatchList(expr, classes, parentenv, handlers)
4 tryCatchOne(expr, names, parentenv, handlers[[1]])
5 doTryCatch(return(expr), name, parentenv, handler)
6 withCallingHandlers(expr, condition = cond.handler)
7 parameter_check(0)
8 #3: stop("400")
9 .handleSimpleError(function (c)
{
if (inherits(c, "condition")
<simpleError in parameter_check(0): 400>
I would like to get my own result code ("400") so that I can print it to the console while logging the complete error message in a file. Is there a way of doing it without writing code parsing the error message, etc.?
Solution with tryCatch
Based on the hint by R Yoda and this answers this is a solution with tryCatch and calling handlers.
### Parameters
log_file_location <- "./logs/log.txt"
### Defining functions
parameter_check_1 <- function(error) {
if (error){
stop("400")
}
}
parameter_check_2 <- function(error) {
if (error){
stop("400")
}
}
write_to_log <- function(file_location, message) {
if (file.exists(file_location))
{write(message, file_location, append = TRUE)}
else
{write(message, file_location, append = FALSE)}
}
parameter_check <- function(){
print("Beginning of parameter check")
print("First check")
parameter_check_1(error = TRUE)
print("Second check")
parameter_check_2(error = FALSE)
print("End of parameter check")
}
main<- function() {
print("Beginning of main function")
log(-1) # throws warning
log("error") # throws error
print("End of main function")
}
### Setting parameters
result_code_no_error <- "200"
result_code_bad_request <- "400"
result_code_internal_error <- "500"
# initial value for result_code
result_code <- result_code_no_error
print("Beginning of program")
### Execute parameter check with tryCatch and calling handlers
# Error in parameter checking functions should result in result_code_bad_request
tryCatch(withCallingHandlers(parameter_check(),
error = function(condition){},
warning = function(condition){
write_to_log(log_file_location, condition$message)
invokeRestart("muffleWarning")
}
),
error = function(condition) {
write_to_log(log_file_location, condition$message)
result_code <<- result_code_bad_request
}
)
### Execute main section with tryCatch and calling handlers
# Error in main section should result in result_code_internal_error
# main section should only be excecuted if there is no error (internal or bad request) in the previous section
if (result_code == result_code_no_error) {
tryCatch(withCallingHandlers(main(),
error = function(condition){},
warning = function(condition){
write_to_log(log_file_location, condition$message)
invokeRestart("muffleWarning")
}
),
error = function(condition) {
write_to_log(log_file_location, condition$message)
result_code <<- result_code_internal_error
}
)
}
print("End of program")
print(result_code)
As explained in the vignette for tryCatchLog this has the disadvantage of not logging the precise location of the error. I am not passing on the error message from stop("400"), because all parameter checking functions are in one function call now, but this could be done using condition$message.
The solution is (totally independent of using tryCatchLog or standard R tryCatch):
...
error = function(e) {print(e$message)}
..
Background (how R errors work): They create an object of type (error) condition:
e <- simpleError("400") # same "condition" object as created by stop("400")
str(e)
# List of 2
# $ message: chr "400"
# $ call : NULL
# - attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
print(e$message)
[1] "400"
I want to write a error log for one of my functions (see below). However, when the function fails, the sink won't get closed properly. Is there a way to always close the sinks() upon exiting the function?
some_function <- function(){
con <- file("test.log")
sink(con, append=TRUE,type="output",split=TRUE)
sink(con, append=TRUE,type="message")
>> do some stuff that may fail.
sink()
sink(type = "message")
}
Use try.
try(lm(1), outFile="error.txt") ## failing stuff
File contains:
Error in formula.default(object, env = baseenv()) : invalid formula
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?
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
}
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
}
I am building a function to connect to a specific password-protected ODBC data source that will be used many members of a team - it may be used in multiple environments. In the event that the connection is rejected, I would like to display the warning messages but mask the password that's displayed. If I use suppressWarnings() nothing gets captured as far as I can tell, and if I don't, then the message is displayed in the standard output with the password. Here's the function so far:
connectToData <- function(uid, pswd, dsn='myDSN') {
# Function to connect to myDSN data
#
# Args:
# uid: The user's ID for connecting to the database
# pswd: The user's password for connecting to the database.
# dsn: The DSN for the (already existing) ODBC connection to the 5G
# data. It must be set up on an individual Windows user's machine,
# and they could use any name for it. The default is 'myDSN'
#
# Returns:
# The 'RODBC' class object returned by the RODBC:odbcConnect() function.
#
# TODO: 1) See if you can specify the connection using odbcDriverConnect()
# so as to not rely on user's ODBC connections
# 2) Capture warnings from odbcConnect() and print them while
# disguising password using gsub, as I've attempted to do below.
library('RODBC')
db.conn <- odbcConnect(dsn,
uid=uid,
pwd=pswd)
if(class(db.conn) != 'RODBC') { # Error handling for connections that don't make it
print(gsub(pswd,'******',warnings())) # This doesn't work like I want it to
stop("ODBC connection could not be opened. See warnings()")
} else {
return(db.conn)
}
}
When I run it with the right username/password, I get the right result but when I run it with a bad password, I get this:
> db.conn <- connectTo5G(uid='myID',pswd='badpassword', dsn='myDSN')
[1] "RODBC::odbcDriverConnect(\"DSN=myDSN;UID=myID;PWD=******\")"
[2] "RODBC::odbcDriverConnect(\"DSN=myDSN;UID=myID;PWD=******\")"
Error in connectTo5G(uid = "myID", pswd = "badpassword", dsn = "myDSN") :
ODBC connection could not be opened. See warnings()
In addition: Warning messages:
1: In RODBC::odbcDriverConnect("DSN=myDSN;UID=myID;PWD=badpassword") :
[RODBC] ERROR: state 28000, code 1017, message [Oracle][ODBC][Ora]ORA-01017: invalid username/password; logon denied
2: In RODBC::odbcDriverConnect("DSN=myDSN;UID=myID;PWD=badpassword") :
ODBC connection failed
The print(gsub(...)) appears to work on the most recent warnings from before the function was invoked, and it also only prints the function call that produced the warning, not the text of the warning.
What I would like to do is capture everything after "In addition: Warning messages:" so that I can use gsub() on it, but avoid printing it before the gsub() gets a chance to work on it. I think I need to use withCallingHandlers() but I've looked through the documentation and examples and I cannot figure it out.
Some extra background: This is an Oracle database that locks users out after three attempts to connect so I want to use stop() in case someone writes code that calls this function multiple times. Different users in my group work in both Windows and Linux (sometimes going back and forth) so any solution needs to be flexible.
Catching error messages
I do not fully understand what you want to accomplish with ODBC but in terms of converting the error message, you can use tryCatch as #joran suggested
pswd = 'badpassword'
# Just as a reproducable example, a function which fails and outputs badpassword
failing <- function(){
badpassword == 1
}
# This would be the error handling part
tryCatch(failing(),
error = function(e) gsub(pswd, '******', e))
[1] "Error in failing(): object '******' not found\n"
e in this case is the error message and you could think of other ways to manipulate what is put to your screen, so it would not be as easy to guess passwords based on what was replaced. Note for example that 'object' would have been replaced as well if the password had been 'object' for some reason. Or even parts of words, which get replaced as well. At the very least, it would make sense to include word boundaries in the gsub command:
pswd = 'ling'
failing <- function(){
ling == 1
}
tryCatch(failing(),
error = function(e) gsub(paste0("\\b", pswd, "\\b"), '******', e))
[1] "Error in failing(): object '******' not found\n"
For other improvements you should look closely at the specific error messages.
Warnings
trycatch can also manipulate warning:
pswd = 'ling'
failing <- function(){
warning("ling")
ling == 1
}
tryCatch(failing(),
warning = function(w) gsub(paste0("\\b", pswd, "\\b"), '******', w),
error = function(e) gsub(paste0("\\b", pswd, "\\b"), '******', e))
[1] "simpleWarning in failing(): ******\n"
This will not show the error then, however.
withCallingHandlers
If you really want to catch all output from errors and warnings, you do indeed need withCallingHandlers, which works mostly in the same way, except that it does not terminate the rest of the evaluation.
pswd = 'ling'
failing <- function(pswd){
warning(pswd)
warning("asd")
stop(pswd)
}
withCallingHandlers(failing(),
warning = function(w) {
w <- gsub(paste0("\\b", pswd, "\\b"), '******', w)
warning(w)},
error = function(e){
e <- gsub(paste0("\\b", pswd, "\\b"), '******', e)
stop(e)
})
I have an application built with Shiny (a tutorial, where ui.R and server.R are taken from here: http://shiny.rstudio.com/tutorial/lesson1/).
I have these two files in shiny-frontend folder, and if I runApp("shiny-frontend") locally in RStudio - everything works great and I see the tutorial in my browser.
Now I want the same app to be put into Bluemix via cloudfoundry. I'm using this: http://www.ibm.com/developerworks/library/ba-rtwitter-app/ as a tutorial, but struggling with an error.
I have a start.r file which I run as R -f ./start.r --gui-none --no-save. I'm using https://github.com/virtualstaticvoid/heroku-buildpack-r buildpack.
My start.r looks like this (taken from the bluemix tutorial with a very minor modifications):
library(shiny)
if (Sys.getenv('VCAP_APP_PORT') == "") {
print("Running Shiny")
runApp("shiny-frontend")
} else {
# In case we're on Cloudfoundry, run this:
print('running on CF')
# Starting Rook server during CF startup phase - after 60 seconds start the actual Shiny server
library(Rook)
myPort <- as.numeric(Sys.getenv('VCAP_APP_PORT'))
myInterface <- Sys.getenv('VCAP_APP_HOST')
status <- -1
# R 2.15.1 uses .Internal, but the next release of R will use a .Call.
# Either way it starts the web server.
if (as.integer(R.version[["svn rev"]]) > 59600) {
status <- .Call(tools:::startHTTPD, myInterface, myPort)
} else {
status <- .Internal(startHTTPD(myInterface, myPort))
}
if (status == 0) {
unlockBinding("httpdPort", environment(tools:::startDynamicHelp))
assign("httpdPort", myPort, environment(tools:::startDynamicHelp))
s <- Rhttpd$new()
s$listenAddr <- myInterface
s$listenPort <- myPort
s$print()
Sys.sleep(60)
s$stop()
}
# run shiny server
sink(stderr())
options(bitmapType='cairo')
getOption("bitmapType")
print("test")
write("prints to stderr", stderr())
write("prints to stdout", stdout())
write(port, stdout())
runApp('shiny-frontend',port=myPort,host="0.0.0.0",launch.browser=F)
}
And my init.r, looks like this:
install.packages("shiny", clean=T)
install.packages("Rook", clean=T)
Then when I run, everything is deployed correctly, but then when I try to go by the route, I see an error in the log:
* ERR Calls: <Anonymous> -> startDynamicHelp
* ERR Execution halted
* ERR Error in startDynamicHelp(FALSE) : could not find function "httpdPort"
I also noticed that assigned port is different every time, which is weird and the route in bluemix dashboard does not mention it. But I output the port to the log, and use that number.
Also the way I'm doing it seems a bit too complicated, so if anybody could suggest any easier way, I'd appreciate it
it took me a while to understand that this error is thrown by R because it can not find the function (not the value) httpdPort. Instead of binding httpdPort to a function you are binding it to a value. The line s$stop() is the one causing trouble. It calls startDynamicHelp that assumes that httpdPort is a function defined in the environment tools.
To fix this issue you can change the block if (status == 0){...} in your code to:
if (status == 0) {
getSettable <- function(default){
function(obj = NA){if(!is.na(obj)){default <<- obj};
default}
}
myHttpdPort <- getSettable(myPort)
unlockBinding("httpdPort", environment(tools:::startDynamicHelp))
assign("httpdPort", myHttpdPort, environment(tools:::startDynamicHelp))
s <- Rhttpd$new()
s$listenAddr <- myInterface
s$listenPort <- myPort
s$print()
Sys.sleep(60)
s$stop()
}