Server Function: Error handling

Preformatted text@yannick

Hi Yannick and developer colleagues,

I would like to propose the following and discuss the idea.

**Problem to solve: ** Currently, server functions (especially assign functions) appear not to provide a suitable mechanism to indicate a successful or non-successful outcome of the server function execution. This situation has led to write some client functions that are challenging to maintain, due to an excessive amount of code. The latter is often written to verify some values have been assigned on the server.

Proposal: I would like to suggest that a status and error to be handled by datashield.assign and datashield.aggregate. A class name FunctionOutcome should be defined with three private variables; i.e., status, error and value. A public method sets the value of these three private variables. Some get methods give access to the private values.

Server functions should return instantiations of this class. Then datashield.assign function can remove the value in the object, before returning it to the analysis server. The datashield.aggregate function can leave the object as it is. Finally, Datashield.assign and datashield.aggregate should remain backward compliant to the current functions.

I have an example below. The function uses a tryCatch function to catch the warnings and errors thrown by the main helper function. This statement would provide some information why the server function has stopped its execution, without completely stopping the execution of the R program…

Example code: [Example code]

#’@title createMatrixRUnifDS creates a matrix of n rows and columns using a uniform distribution. Assign server function. #’@description This server function function randomly generates some numbers using a uniform distribution. The latter is then coerced into a matrix. #’@param no.rows a number of rows greater than 11. Set to 11 by default #’@param no.cols a number of columns greater than 13. Set to 13 by default #’@param min a numerical value that indicates the possible minimum value of the distribution. Set to largest double value by default #’@param max a numerical value that indicates the possible minimum value of the distribution. Set to largest double value by default #’@return a matrix of no.rows and no.columns. #’@details This function stops if the number of rows or the number of columns is too disclosive. For that reason, a minimum of rows has been set #'to 11. The minimum or columns is set to 13. When a suitable matrix size is passed, the minimum and maximum values are guaranteed to be in an appropriate order. #'For example, when the minimum value is greater than the maximum value, the error is corrected. A warning is then thrown. A uniform distribution is created using the R function importFrom(“stats”, “runif”). #‘The outcome is then coerced into matrix, using the parameters no.rows and no.columns. #’@author P.Ryser-Welch, Newcastle University, DataSHIELD team

FunctionOutcome <- R6Class(“FunctionOutcome”, public = list ( SUCCESS = 0, WARNING = 1, FAIL = 2, set = function(status,error, value) { private$status = status private$error = error private$value = value }, delete_value = function() private$value = NULL, get_status = function() return(private$status), get_error = function() return(private$error), get_value = function() return(value) ), private = list( status = -1, error = “”, value = NULL ) )

create.matrix.runif.DS.4 <- function(no.rows = 11, no.columns = 13, min = .Machine$double.xmin, max = .Machine$double.xmax) { outcome <- FunctionOutcome$new() tryCatch( {outcome$set(outcome$SUCCESS,"",.create.matrix(no.rows, no.columns, min, max))}, warning = function(warning) outcome$set(outcome$WARNING,warning,“NULL”), error = function(error) outcome$set(outcome$FAIL,error,“NULL”), finally = {return(outcome)} ) }

.create.matrix <- function(no.rows = 11, no.columns = 13, min = .Machine$double.xmin, max = .Machine$double.xmax) { result <- NULL if (!is.numeric(no.rows) || !is.numeric(no.columns) || !is.numeric(min) || !is.numeric(max)) { stop(“ERR:000”) }

if (no.rows < 11 || no.columns < 13) { no.rows <- 11 no.columns <- 13 warning(“WARNING:000”) }

if (min > max) #The value have been reversed { min <- max max <- min warning(“WARNING:002”) }

random.numbers <- runif(no.rows * no.columns, min = min, max = max) result <- matrix(random.numbers,no.rows,no.columns)

return(result) }

What is next? I would like some opinions of what you think about the suggestion. I would like to know what other people thing and whether it is possible to implement it.

Best wishes,

P.

I also agree with @patricia.ryser-welch 's comment.

I had to do something similar with dsOmics package. This is the situation. The user can estimate cell-type composition from epigenomic data. This procedure is implemented in a BioC package that requires a number of CpGs. When these are not available, the function gets an error message.

What I did, so far, was to return NA when the error is obtained (I used try function in the R Server). Then, when the object is NA in the R server, you see a message in the client indicating that this estimation has not been able to be performed.

I cannot provide feedback about your solution since the link is broken (at least to me) but I guess your proposal is fine

Juan

I agree that error handling on the server side needs improvement, and will help usability. I have been trying to think how to do this better with the mixed model functions, so this is good timing.

I also can’t access the link

Tom

Try again with the link…

Hi,

The link still does not work.

Yes, that was what I suggested a while ago, with status number similar to HTTP (2xx = success, 4xx = user error (bad parameters for instance), 5xx = server error). Would it be to DSI to handle the FunctionOutcome object and always raise a stop() when an error is detected? Or would there be cases where the error is just a warn that could be recovered by the client function?

Cheers
Yannick

I am. having some difficulties sharing this code. … But that is it…

P.

#’@title createMatrixRUnifDS creates a matrix of n rows and columns using a uniform distribution. Assign server function. #’@description This server function function randomly generates some numbers using a uniform distribution. The latter is then coerced into a matrix. #’@param no.rows a number of rows greater than 11. Set to 11 by default #’@param no.cols a number of columns greater than 13. Set to 13 by default #’@param min a numerical value that indicates the possible minimum value of the distribution. Set to largest double value by default #’@param max a numerical value that indicates the possible minimum value of the distribution. Set to largest double value by default #’@return a matrix of no.rows and no.columns. #’@details This function stops if the number of rows or the number of columns is too disclosive. For that reason, a minimum of rows has been set #'to 11. The minimum or columns is set to 13. When a suitable matrix size is passed, the minimum and maximum values are guaranteed to be in an appropriate order. #'For example, when the minimum value is greater than the maximum value, the error is corrected. A warning is then thrown. A uniform distribution is created using the R function importFrom(“stats”, “runif”). #‘The outcome is then coerced into matrix, using the parameters no.rows and no.columns. #’@author P.Ryser-Welch, Newcastle University, DataSHIELD team

FunctionOutcome <- R6Class(“FunctionOutcome”, public = list ( SUCCESS = 0, WARNING = 1, FAIL = 2, set = function(status,error, value) { private$status = status private$error = error private$value = value }, delete_value = function() private$value = NULL, get_status = function() return(private$status), get_error = function() return(private$error), get_value = function() return(value) ), private = list( status = -1, error = “”, value = NULL ) )

create.matrix.runif.DS.4 <- function(no.rows = 11, no.columns = 13, min = .Machine$double.xmin, max = .Machine$double.xmax) { outcome <- FunctionOutcome$new() tryCatch( {outcome$set(outcome$SUCCESS,"",.create.matrix(no.rows, no.columns, min, max))}, warning = function(warning) outcome$set(outcome$WARNING,warning,“NULL”), error = function(error) outcome$set(outcome$FAIL,error,“NULL”), finally = {return(outcome)} ) }

.create.matrix <- function(no.rows = 11, no.columns = 13, min = .Machine$double.xmin, max = .Machine$double.xmax) { result <- NULL if (!is.numeric(no.rows) || !is.numeric(no.columns) || !is.numeric(min) || !is.numeric(max)) { stop(“ERR:000”) }

if (no.rows < 11 || no.columns < 13) { no.rows <- 11 no.columns <- 13 warning(“WARNING:000”) }

if (min > max)  #The value have been reversed { min <- max max <- min warning(“WARNING:002”) }

random.numbers <- runif(no.rows * no.columns, min = min, max = max) result <- matrix(random.numbers,no.rows,no.columns)

return(result) }