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.
## 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: 0x7fd4452ee520>
## $ args :List of 1
## ..$ : num 0
## $ namedargs: language df(x)
## $ fn_name : chr "f"
## [[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>>
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
.