-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
R6-encapsulated Shiny observer prevents the encapsulating object from being finalize()-ed #232
Comments
I believe the problem has to do with the creation of the observer: initialize = function(rx) {
private$obs <- shiny::observe(print(rx()))
}, When function() {
print(rx())
} The enclosing environment of that function is the environment from which So as long as the observer exists, it will keep the R6 object alive. That much probably isn't surprising. What may be surprising is that the "destroyed" observer exists until library(shiny)
o <- observe(message("this is the observer"))
reg.finalizer(o, function(e) message("observer finalized"))
o$destroy()
rm(o)
invisible(gc())
shiny:::flushReact()
invisible(gc())
#> observer finalized I'd have to think about this some more before I'd say it's a bug. There might be a good reason why it's like that. Can you file an issue on Shiny about this? |
@wch Yeah, happy to create an issue in the Shiny repo simply to document this behavior, at least. I did eventually come to that realization that the observer's newly-created function (via If one forgets to call In any case, thanks for taking a look, and if I can be of any help in working through this (including designing test cases), feel free to ping me. |
One workaround is to create the observer so that its code is evaluated in a different environment. Then the resulting function won't capture the execution environment of WithObs <- R6::R6Class("WithObs",
private = list(
obs = NULL,
finalize = function() {
message("WithObs: running finalize()")
private$obs$destroy()
}
),
public = list(
initialize = function(rx) {
private$obs <- shiny::observe({
print(rx())
},
env = globalenv()
)
}
)
)
rxval <- shiny::reactiveVal(0)
with_obs <- WithObs$new(rxval)
rm(with_obs)
invisible(gc())
#> WithObs: running finalize() Note that this change does not cause the Also note that with future versions of Shiny, the initialize = function(rx) {
myquo <- rlang::new_quosure(
quote({ print(rx()) }),
env = globalenv()
)
private$obs <- rlang::inject(shiny::observe(!!myquo))
} |
I'm not sure if this is an Issue (capital I), but I also can't find a good pattern here for when I embed a Shiny observer inside an R6 object. Take these two classes:
Now when creating a few objects, then dropping our references to those objects, I'd expect the finalizers of both to run. Note that the finalizer for WithObs destroys the observer.
with_obs
's finalizer doesn't run. If we now flush Shiny and then garbage-collect now we get:... still no finalizer.
In a brand new session, if we explicitly destroy the embedded observer, we can get the finalizer to run, but it requires a reactive flush, almost suggesting that the encapsulating object's finalizer is blocked until all other (external to the object) references to its fields have also been released?
I'm surprised by both (i) the non-execution of
with_obs
's finalizer on the firstgc()
above and (ii) the need to both explicitly destroy the encapsulated observer and flush the reactive system to finally get the encapsulating object tofinalize()
.FWIW, my use case that led me down this path is pretty much exactly what you see with the WithObs class: an encapsulated observer that I want to
destroy()
when the program loses its reference to the encapsulating object -- specifically to try to prevent memory leaks by not accumulating unused/unneeded observers during a program lifecycle.EDIT: forgot to mention, this is with R6 v2.5.0.
The text was updated successfully, but these errors were encountered: