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.
Below are some of my code snippets from this exploration.
Simple usage
The following simple code shows:
- The
on.exit()
call can appear anywhere in the function - The
on.exit()
expression is evaluated after the function budy is complete i.e. after the return statement.
f <- function(a, b) {
on.exit(print("on.exit() called"))
print("Function finished, returning value")
return(a + b)
}
f(1, 2)
## [1] "Function finished, returning value"
## [1] "on.exit() called"
## [1] 3
Multiple on.exit()
statements
The following code shows:
- The
on.exit()
call can appear multiple times in the function - by using
add = TRUE
, the expression is added to any prior expressions
f <- function(a, b) {
on.exit(print("on.exit() called 1"))
result <- a + b
on.exit(print("on.exit() called 2"), add = TRUE)
print("Function finished, returning value")
return(result)
}
f(1, 2)
## [1] "Function finished, returning value"
## [1] "on.exit() called 1"
## [1] "on.exit() called 2"
## [1] 3
Multiple on.exit()
statements
The following code shows:
- The
on.exit()
call can appear multiple times in the function - by using
add = TRUE
, the expression is added to any prior expressions - by using
after = FALSE
, the expression is added before any any prior expressions
f <- function(a, b) {
on.exit(print("on.exit() called 1"))
result <- a + b
on.exit(print("on.exit() called 2"), add = TRUE, after = FALSE)
print("Function finished, returning value")
return(result)
}
f(1, 2)
## [1] "Function finished, returning value"
## [1] "on.exit() called 2"
## [1] "on.exit() called 1"
## [1] 3
The evaluation environment for on.exit()
has access to function variables
The evaulation environment where the on.exit()
expressions are a run has access
to variables within the function.
f <- function(a, b) {
on.exit(cat("The numbers were ", a, " and ", b, "\n"))
return(a + b)
}
f(1, 2)
## The numbers were 1 and 2
## [1] 3
The variables aren’t explicitly captured when then on.exit()
expression is
registered, so the variable values will be those at the end of the function evaluation.
f <- function(a, b) {
on.exit(cat("The numbers were ", a, " and ", b, "\n"))
a <- a + 1
b <- b + 1
return(a + b)
}
f(1, 2)
## The numbers were 2 and 3
## [1] 5
Viewing the expressions registered via on.exit()
It is possible to view the expressions that have been registered with on.exit()
during
a function call by calling sys.on.exit()
f <- function(a, b) {
on.exit(print("on.exit() called 1"))
result <- a + b
on.exit(print("on.exit() called 2"), add = TRUE, after = FALSE)
print(sys.on.exit())
print("Function finished, returning value")
return(result)
}
f(1, 2)
## {
## print("on.exit() called 2")
## print("on.exit() called 1")
## }
## [1] "Function finished, returning value"
## [1] "on.exit() called 2"
## [1] "on.exit() called 1"
## [1] 3
It is even possible to return the on.exit()
expressions for later evaluation
in a different context.
f <- function(a, b) {
on.exit(print("on.exit() called 1"))
result <- a + b
on.exit(print("on.exit() called 2"), add = TRUE, after = FALSE)
return(sys.on.exit())
}
# capture the returned on.exit() expressions
exp <- f(1, 2)
## [1] "on.exit() called 2"
## [1] "on.exit() called 1"
exp
## {
## print("on.exit() called 2")
## print("on.exit() called 1")
## }
# Evaluate the returned on.exit() expressions
eval(exp)
## [1] "on.exit() called 2"
## [1] "on.exit() called 1"
Can you use on.exit()
within an on.exit()
? Attempt 1
In the following code, I attempt to ask on.exit()
to try and execute another
on.exit()
during the evaluation of the on.exit()
expressions.
This does not work. Only the first on.exit()
expression is evaluated.
f <- function() {
on.exit({print("hello 1")})
on.exit(on.exit(print('hello 2')), add = TRUE)
}
f()
## [1] "hello 1"
Can you use on.exit()
within an on.exit()
? Attempt 2
You can sort of make this work by setting up another function context in the
on.exit()
expression, but nesting on.exit()
calls this way doesn’t really
do anything that regular usage doesn’t already do.
f <- function() {
on.exit(print("hello 1"))
on.exit((function() {on.exit(print("hello 2"))})(), add = TRUE)
}
f()
## [1] "hello 1"
## [1] "hello 2"
Can you use on.exit()
within an on.exit()
? Attempt 3
It’s interesting to note that on.exit()
expressions are evaluated at the end
of code created via an eval(parse(...))
statement. i.e.
eval(parse(text="on.exit(print('hello'))"))
## [1] "hello"
So it is possible to get an on.exit()
to evaluate within the context of
a different on.exit()
- but I can’t see an application for this behaviour.
f <- function() {
on.exit(print("hello 1"))
on.exit(eval(parse(text="on.exit(print('hello 2'))")), add = TRUE)
}
f()
## [1] "hello 1"
## [1] "hello 2"
What happens if you call stop()
within an on.exit()
?
If an error occurs during the evaluation of the on.exit()
expressions, then
the return value is never assigned to the variable in the calling environment.
a <- NULL
f <- function() {
on.exit(stop("error during on.exit()"))
return(1)
}
a <- f()
## Error in f(): error during on.exit()
print(a)
## NULL
What happens if you call return()
within an on.exit()
?
If return()
is called within on.exit()
then this will actually override
any return value in the function itself.
f <- function() {
on.exit(return(2))
return(1)
}
f()
## [1] 2
Manipulating the return value with on.exit()
The on.exit()
evaluation environment has access to the return value of the
function using the returnValue()
function.
This can then be used to post-process any return value before it actual gets to the function calling environment.
In the following code, the return value of the function is doubled during the exit process.
f <- function() {
on.exit(return(returnValue() * 2))
return(1)
}
result <- f()
print(result)
## [1] 2
Using Recall()
within an on.exit()
In the following code, the return value is evaluated within the on.exit()
context, and if it does not meet the specified criteria, the function is
called again (via Recall()
) until it passes.
set.seed(1)
f <- function() {
on.exit({
if (returnValue() < 0.9) {
return(Recall())
}
return(x)
})
x <- runif(1)
cat("Returning: ", x, "\n")
return(x)
}
result <- f()
## Returning: 0.2655087
## Returning: 0.3721239
## Returning: 0.5728534
## Returning: 0.9082078
print(result)
## [1] 0.9082078
Shift the entire function body to be evaluated on.exit()
In this code, the entire function body and return statement for this simpl
addition function are within the on.exit()
statement.
f <- function(a, b) {
on.exit({
result <- a + b
return(result)
})
return(-999)
}
result <- f(1, 2)
print(result)
## [1] 3