This vignette demonstrates debugging a user-created function with the DebugFnW
call. For our example, we will use a simple function that takes an argument i
and returns the i
th index of a ten-element vector:
Let’s imagine that we are calling this function deep within another process; perhaps we are calling it repeatedly, on a long sequence of (possibly unknown to us) inputs.
## <simpleError in (1:10)[[i]]: attempt to select less than one element in get1index <real>>
Oops! We’ve crashed, and if this loop were deep in another process, we wouldn’t know why, or where. If we suspect that the function f
is the cause, then we can wrap f
using wrapr:DebugFn
.
DebugFnW(saveDest, fn)
wraps its function argument fn
, captures any arguments that cause it to fail, and saved those arguments and other state to a specified destination saveDest
.
The state data is written to:
saveDest
is null)saveDest
is character)globalenv()
variable (if saveDest
is a name, as produced by as.name()
or quote()
)saveDest
is a function).Here, we wrap f
and save error state into the global variable lastError
.
Now we run the same loop as above, with the wrapped function df
(note that the tryCatch
is not strictly needed, this is just for running this example in a vignette).
# capture error (Note: tryCatch not needed for user code!)
tryCatch(
for(x in inputs) {
df(x)
},
error = function(e) { print(e) })
## <simpleError in value[[3L]](cond): wrapr::DebugFnW: wrote error to globalenv() variable 'lastError'
## You can reproduce the error with:
## 'do.call(p$fn, p$args)' (replace 'p' with actual variable name)>
We can then examine the error. Note in particular that lastError$fn_name
records the name of the function that crashed, and lastError$args
records the arguments that the function was called with. Also in these examples we are wrapping our code with a tryCatch
block to capture exceptions; this is only to allow the knitr
sheet to continue and not needed to use the debugging wrappers effectively.
# examine error
str(lastError)
## List of 4
## $ fn :function (i)
## ..- attr(*, "srcref")= 'srcref' int [1:8] 5 6 5 32 6 32 5 5
## .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbc63c8aad0>
## $ args :List of 1
## ..$ : num 0
## $ namedargs: language df(x)
## $ fn_name : chr "f"
lastError$args
## [[1]]
## [1] 0
In many situations, just knowing the arguments is enough information (“Oops, we tried to index the vector from zero!”). In more complicated cases, we can set a debug point on the offending function, and then call it again with the failing arguments in order to track down the bug.
# redo call, perhaps debugging
tryCatch(
do.call(lastError$fn_name, lastError$args),
error = function(e) { print(e) })
## <simpleError in (1:10)[[i]]: attempt to select less than one element in get1index <real>>
# clean up
rm(list='lastError')
In many cases you may prefer to save the failing state into an external file rather than into the current runtime environment. Below we show example code for saving state to an RDS file.
saveDest <- paste0(tempfile('debug'),'.RDS')
# wrap function with saveDeest
df <- DebugFnW(saveDest,f)
# capture error (Note: tryCatch not needed for user code!)
tryCatch(
for(x in inputs) {
df(x)
},
error = function(e) { print(e) })
We can later read that file back into R, for debugging.
# load data
lastError <- readRDS(saveDest)
# examine error
str(lastError)
# redo call, perhaps debugging
tryCatch(
do.call(lastError$fn_name, lastError$args),
error = function(e) { print(e) })
# clean up
file.remove(saveDest)
For more practice, please view our video on wrapper debugging.
Note: wrapr
debug functionality rehashes some of the capabilities of dump.frames
(see help(dump.frames)
). Roughly dump.frames
catches the exception (so trying to step or continue re-throws, and arguments may have moved from their starting values) and wrapr
catches the call causing the exception in a state prior to starting the calculation (so arguments should be at their starting values). We have found some cases where wrapr
is a bit more convenient in how it interacts with the RStudio
visual debugger (please see this screencast for some comparison). Also, please see this article for use of tryCatch
and withRestarts
.