For the benefit of my future self and other lovers of #rstats debugging:

Kevin Ushey just shared an incredible little trick with me that I am still reeling from in this issue thread.

You can use it to get a stack trace for code that is getting stuck in infinite loops or just generally taking a really long time. You can use that stack trace to see where in the code execution flow is getting bogged down.

I was there hacking in timing code and print statements (aka banging rocks together) when Kevin dropped this construct:

withCallingHandlers({
  ..YOUR SLOW CODE HERE..
}, interrupt = function(e) browser())

Here’s an example of it working:


[ins] r$> my_bad <- function() {
            while(TRUE) {
              lapply(letters, I)
            }
          }

          withCallingHandlers({
            my_bad()
            }, interrupt = function(e) browser())
Called from: (function(e) browser())(list())

[ins] Browse[1]> traceback()
7: unique.default(c("AsIs", oldClass(x)))
6: unique(c("AsIs", oldClass(x)))
5: structure(x, class = unique(c("AsIs", oldClass(x))))
4: FUN(X[[i]], ...)
3: lapply(letters, I) at #3
2: my_bad() at #2
1: withCallingHandlers({
       my_bad()
   }, interrupt = function(e) browser())
   

So when I interrupted the code running in the console with CTRL+C, I was kicked into browse mode, and from there I could call traceback()!

I am still trying to figure out how to wield this new power. It seems that depending on where you interrupt it, you may or may not have traceback available. But if the stack trace is available are the environment frames?!

Noodling around with the idea I came up with this, which seemed to work consistently:

withCallingHandlers({
            my_bad()
            }, interrupt = function(e) traceback())

Sweeet!

There’s also a more powerful version that Kevin shared down the thread that allows resuming. That trapped me in a bit of a loop of my own, but that’s what you get when you play with MAGIC.

Update

Luke Tierney (Gandalf level wizard), chimed in with some info that this trick can be pulled off with:

options(interrupt = browser)

Wow!

But then that lead me to try:

options(interrupt = recover)

Which is epic!

In case you don’t know about recover you REALLY should have a go with it. It’s pretty special. So special I made a video about it: https://youtu.be/M5n_2jmdJ_8 .