Closed richfitz closed 7 years ago
Thanks for finding this bug. I think this is happening because the finalizer is capturing the parent environment. Here's a modified example that uses R's reg.finalizer
instead of an R6 finalize
method:
library(R6)
A <- R6::R6Class(
"A",
public = list(
value = NULL,
initialize = function(value) {
self$value <- value
}
)
)
B <- R6::R6Class(
"B",
public = list(
initialize = function(a) {
message(sprintf("I was passed %s in constructor", a$value))
}
)
)
a <- A$new(1)
reg.finalizer(a, function(e) message("Deleting object A"))
b <- B$new(a)
reg.finalizer(b, function(e) message("Deleting object B"))
rm(a)
gc()
# Deleting object A
rm(b)
gc()
# Deleting object B
Here's the part where the finalizer captures the parent environment. It happens because the finalize
method gets wrapped in another function: https://github.com/wch/R6/blob/2ce4882/R/new.R#L153-L157
The fix should be pretty straightforward.
Thanks - this is great. I can confirm things working as expected with the update, too :)
I'm not sure this is exactly a bug, but this is behaviour that surprised me. When passing an object through the constructor of an R6 object, the object passed as an argument will not be garbage collected until the newly created object is garbage collected.
Here is a minimal example:
Create a copy of
A
and pass that through toB
which just ignores the argumentDeleting
a
should (to my intuition anyway) leave no references toa
but the finalizer is not called...until
b
is deleted and garbage collected:I've poked about but I can't work out what environment a reference to
A
is being kept. This works the same way withcloneable
set toTRUE
orFALSE
, and the behaviour is the same for non-portable classesIn the end, nothing is terrible - things get deleted eventually. But it's a bit of a problem for a DB wrapper I am writing as it's overly conservative about when things can be deleted.