s-u / Rserve

Fast, flexible and powerful server providing access to R from many languages and systems
http://RForge.net/Rserve
Other
282 stars 65 forks source link

Rserve.eval and custom error conditions #154

Closed dselivanov closed 2 years ago

dselivanov commented 4 years ago

Hi Simon,

I'm trying to figure out how to use Rserve.eval to safely evaluate expression and get stack trace in case of fail. I face couple of challenges:

Consider following example:

error_with_condition = function(object = NULL) {
  cond = errorCondition("this is an error with condition", 
                        object = object, 
                        class = "CustomError")
  stop(cond)
}
caught_err = tryCatch(error_with_condition(1), error = identity)
str(caught_err)
#List of 3
# $ message     : chr "this is an error with condition"
# $ call        : NULL
# $ custom_field: chr "hello"
# - attr(*, "class")= chr [1:3] "CustomError" "error" "condition"

caught_rserve_eval = Rserve::Rserve.eval(quote(error_with_condition("hello")), last.value = TRUE)
#  Error: this is an error with condition

str(caught_rserve_eval)
#List of 4
#$ error     : chr "Error: this is an error with condition\n"
# $ traceback :Dotted pair list of 2
#  ..$ : chr "stop(cond)"
#  ..$ : chr "error_with_condition(\"hello\")"
# $ expression: int NA
# $ context   : NULL
# - attr(*, "class")= chr "Rserve-eval-error"
s-u commented 4 years ago

I don't think R actually allows any of the above. The problem is that the C-level interface is very limited when it comes to error handling and it doesn't support condition objects. The error comes directly from R_curErrorBuf() so Rserve is just a pass-through from R. I'll see if I can dig out more or if we could add more support in R.

dselivanov commented 3 years ago

@s-u do you think there is a chance to get such improvement in R 4.1? Should I create a ticket on R-bugzilla?

s-u commented 2 years ago

Ok, I have been digging through the whole mess of condition handling in R and I think I have at least a work-around for catching the conditions. Essentially it involves installing a calling handler for the "error" condition which stashes the condition, but then continues to the default handler to trigger the error so we get the traceback.

s-u commented 2 years ago
error_with_condition = function(object = NULL) {
  cond = errorCondition("this is an error with condition", 
                        object = object, 
                        class = "CustomError")
  stop(cond)
}

caught_rserve_eval = Rserve::Rserve.eval(quote(error_with_condition("hello")), last.value = TRUE)
# Error: this is an error with condition

str(caught_rserve_eval)
# Class 'Rserve-eval-error'  hidden list of 5
#  $ error     : chr "Error: this is an error with condition\n"
#  $ traceback :Dotted pair list of 2
#   ..$ : chr "stop(cond)"
#   ..$ : chr "error_with_condition(\"hello\")"
#  $ expression: int NA
#  $ context   : NULL
#  $ condition :List of 3
#   ..$ message: chr "this is an error with condition"
#   ..$ call   : NULL
#   ..$ object : chr "hello"
#   ..- attr(*, "class")= chr [1:3] "CustomError" "error" "condition"
s-u commented 2 years ago

The mechanism is fairly generic, so it would be possible to allow injection of arbitrary handlers, so I am reserving the right to change the API for the handler argument - it could be a list just like in tryCatch, but for now only one handler is added so if that sounds like a good idea that may be useful, please feel free to file a feature request.

s-u commented 2 years ago

Never mind the last comment - I have now changed the API in 57d1c80 such that handlers is a named list of handlers, so arbitrary calling handlers can be registered with the default being handlers = list(error = .save.condition) which saves the condition for retrieval upon return from Rserve.eval.