On 'on.exit()' - Part 2

Introduction

on.exit() is a base R function which records expressions to evaluate when the current function exits.

I was wanting to use on.exit() in a function that was allocating some resources, and I explicitly wanted these resources freed in the reverse order.

Working out how to do what I wanted was easy (discussed below), but it lead me down a path of discovery as I tried to figure out just exactly where and when the on.exit() expressions were being executed.

Yesterday I posted some code snippets from this exploration.

This is a follow-up post with some more notes

Capture variable values at time of on.exit() creation

The following code shows that, by default, values in the on.exit() expression are evaluated as the function exits.

f <- function(a, b) {
  on.exit(cat("a =", a, " b =", b, "\n"))
  a <- a + 1
  b <- b + 1
  a + b
}

f(1, 2)
## a = 2  b = 3
## [1] 5

However sometimes you might like the on.exit() expression to capture the current values of the variables at the time the on.exit() expression was captured.

The docs suggest using substitute() to do this. I’m not sure if the following is exactly what the docs are suggesting, but this works:

f <- function(a, b) {
  eval(substitute(on.exit(cat("a =", a, " b =", b, "\n"))))
  a <- a + 1
  b <- b + 1
  a + b
}

f(1, 2)
## a = 1  b = 2
## [1] 5

Make a function callable only once by removing the function from the calling environment

Antoine Fabri in his {once} package uses on.exit() to remove some functions from the calling environment after they are run for the first (and only!) time.

The following code is a small recreation of this functionality i.e. the function can be called once, and is then removed from the calling environment.

f <- function() {
  on.exit(rm('f', envir = parent.frame()))
  print("In the function")
}

f()
## [1] "In the function"
f()
## Error in f(): could not find function "f"

on.exit() in nested functions - abnormal exit with stop()

Nested functions - each with their own on.exit() - will trigger the evaluation of the captured exit expressions when a stop() is called in the innermost function. The on.exit() expressions are evaluated from most deeply nested function out to the initial outer function call.

fa <- function() {on.exit(print("Exit fa()")); stop("Bang!")}
fb <- function() {on.exit(print("Exit fb()")); fa()}
fc <- function() {on.exit(print("Exit fc()")); fb()}

fc()
## Error in fa(): Bang!
## [1] "Exit fa()"
## [1] "Exit fb()"
## [1] "Exit fc()"

on.exit() in nested functions - abnormal exit with kill signals.

For those familiar with sending signals to processes, on.exit() code may or may not be run when signals are sent.

A common way to send a signal on Unix or MacOS is with the kill command.

My tests on MacOS with some common signals, show that R only executes the captured on.exit() expressions when sent a SIGINT (equivalent to hitting CTRL-C).

Many other signals caused R to terminate completely without the on.exit() registered expressions being evaluated.

Other signals had zero noticeable effect.