Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upFinalizers, closes #92 #93
Conversation
|
Btw. this PR also help accessing private members from the finalizer, which is quite hacky currently, as I can see you need to extract the private env manually: |
| @@ -0,0 +1,100 @@ | |||
| context("destructor") | |||
wch
Aug 22, 2016
Member
Can you rename all of the instances of "destructor" to "finalizer"?
Can you rename all of the instances of "destructor" to "finalizer"?
gaborcsardi
Aug 22, 2016
Author
Contributor
Sure, no problem.
Sure, no problem.
gaborcsardi
Aug 22, 2016
Author
Contributor
Done.
Done.
| # last returned value is saved in .Last.value | ||
| 1+1 | ||
| # Instantiate an object: | ||
| O <- A$new() |
wch
Aug 22, 2016
Member
Can you rename O to a, or obj? The name O is surprising and sort of distracts from the important parts.
Can you rename O to a, or obj? The name O is surprising and sort of distracts from the important parts.
gaborcsardi
Aug 22, 2016
Author
Contributor
Done.
Done.
| gc() | ||
| # Remove the single existing reference to it, and force garbage collection | ||
| # (normally garbage collection will happen automatically from time | ||
| # time to time |
wch
Aug 22, 2016
Member
This says "time time to time" - please remove extra "time" and add closing paren.
This says "time time to time" - please remove extra "time" and add closing paren.
gaborcsardi
Aug 22, 2016
Author
Contributor
Done.
Done.
| ``` | ||
|
|
||
| In the example above, we used `onexit=TRUE`, so that the finalizer will also be called when R exits. This is useful in some cases, like database connections, but it isn't necessary for others, like open files, since they will be closed anyway when the R process exits. | ||
| Destructors are implemented using the `reg.finalizer()` function, and they set `onexit=TRUE`, so that the destructor will also be called when R exits. This is useful in some cases, like database connections. |
wch
Aug 22, 2016
Member
s/Destructor/Finalizer/
s/Destructor/Finalizer/
gaborcsardi
Aug 22, 2016
Author
Contributor
No worries, I replaced it everywhere.
No worries, I replaced it everywhere.
Classes can define a public finalize method, and this will be called automatically when objects are garbage collected.
|
PTAL. |
|
I just had a thought: What about inheritance? Say that class A has a finalizer, and class B inherits from A. What should happen then? |
|
YEah, that's a good one. |
|
I think the "usual" thing is sensible: the finalizer is still called, and you can override it in the child. |
|
If A and B both have finalizers, I can imagine cases where B's finalizer should override A's, but I can also imagine some where both finalizers should run. In the latter case, could the finalizer invoke And some tests of this would be nice :) (including tests with two levels of inheritance) |
Yes, I suspect that it already works like this,
I'll write them in a minute. |
|
@wch Btw. what did you have in mind for two levels? There are a lot of combinations to test with two levels.... |
|
I've been thinking about it some more, and I think I've convinced myself that B's finalizer should have to explicitly call Here's an example of that illustrates the other side: why it can be awkward for B to have to invoke A's finalizer explicitly. Suppose A is a class that represents a database connection with a On the other hand, if B's initializer wants to invoke A's initializer, the it needs to call |
|
For two levels: suppose C inherits from B, which inherits from A. Then if C's I don't think it makes sense for C's |
|
TBH, I am not sure what you prefer now. :) Sorry. Right now
I think this is all quite good. It is safe and flexible. In your DB example, B does not have to define a finalizer just to call A's finalizer. OTH if it defines one to do sg extra, it can still call the parent's finalizer via I'm almost done with the test cases, 5 more minutes.... |
Got it, so all three have finalizers and we destroy an object from the grandchild. |
|
Oh right, I was confused -- I forgot that B would just inherit A's finalizer if it didn't define one. I agree that what you outlined sounds good, and I'm glad it just works that way! |
|
Never mind, it was good to think this through. And also, to write the test cases, which are done btw. As for two levels of inheritance, you could test the case when B does not define a finalizer, only A and C, etc. so there are a lot of cases. Hmmm, this reminds me, what if we call |
OK, this fails, as expected. On the other hand it also fails for I think it is good defensive programming to just call if (!is.null(super$finalize)) super$finalize()is a simple workaround, so probably not a big deal. |
|
Wait a sec, I am fixing some test labels. |
|
OK, should be all good now. Maybe we could generate the non-portable test cases from the portable ones, because it is easy to make copy-and-paste errors. :) |
|
Looks good! |
|
Just a quick comment sharing my experiences with "automagic" finalizers: Make sure to test that the finalizers are re-entrant. There's a risk that finalizers depend on code that may not be available when R runs it. For instance, an object may still be around, but all package code may have been unloaded when it's finalizer runs. This becomes especially complicated when they run when R itself exits, cf. from
Then there's the following comment from
Problems often shows up as non-reproducible sporadic errors that are quite hard to troubleshoot, because how and when finalizers are run is hard to predict. I went through the above a while ago with R.oo - required lots of trial-and-error development and very defensive coding. Just my $.02 |
|
@HenrikBengtsson Thanks, this is very useful! Maybe it could be even in the docs. |
|
@HenrikBengtsson Reading now the R.oo code, wow! |
|
Don't look to close; that piece of code is ad hoc and patchy - it's an instance where "if it works - don't change it" truly applies. |
|
@HenrikBengtsson What I am thinking about is that R6 objects are pretty self-contained, and usually do not need anything from the R6 package. What might happen is that an R6 class needs its (package) environment to work. E.g. my I'll experiment with this. |
|
@HenrikBengtsson Very interesting, thanks for sharing your experiences. These do sound like really tricky problems to diagnose. FWIW, when I run this code in an R session: library(R6)
print(loadedNamespaces())
e <- new.env()
reg.finalizer(e, onexit = TRUE, function(e) {
cat("==== Finalizer ====\n")
print(loadedNamespaces())
})
.Last <- function() {
cat("==== .Last ====\n")
print(loadedNamespaces())
}
q()It doesn't unload any of the packages before
|
Classes can define a public finalize method, and this will be
called automatically when objects are garbage collected.
The destructor is called with no arguments, but of course it can refer to
selfandprivate, and if the class is not portable, then it can also just refer to members.