Create Shared Finalization to Avoid Over Garbage Collection
Source:R/zzz-finalizers.R
shared_finalizer.Rd
Generates a function to be passed to
reg.finalizer
Usage
shared_finalizer(x, key, fin, onexit = FALSE, ...)
# Default S3 method
shared_finalizer(x, key, fin, onexit = FALSE, ...)
# S3 method for class 'R6'
shared_finalizer(x, key, fin, onexit = TRUE, ...)
# S3 method for class 'fastmap'
shared_finalizer(x, key, fin, onexit = FALSE, ...)
# S3 method for class 'fastmap2'
shared_finalizer(x, key, fin, onexit = FALSE, ...)
Arguments
- x
object to finalize
- key
characters that should be identical if finalization method is to be shared
- fin
Shared finalization: function to call on finalization; see
reg.finalizer
. See details.- onexit
logical: should the finalization be run if the object is still uncollected at the end of the R session? See
reg.finalizer
- ...
passed to other methods
Details
The main purpose of this function is to allow multiple objects that point to a same source (say a temporary file) to perform clean up when all the objects are garbage collected.
Base function reg.finalizer
provides finalization
to to garbage collect single R environment. However, when multiple
environments share the same file, finalizing one single environment
will result in removing the file so that all the other environment
lose the reference. (See example "Native reg.finalizer
fails example")
The argument of fin
varies according to different types of
x
. For environments, fin
contains and only contains
one parameter, which is the environment itself. This is the same
as reg.finalizer
. For R6
classes, fin
is
ignored if class has "shared_finalize"
method defined.
For fastmap
or fastmap2
instances, fin
accepts no argument.
Examples
# ------------ Environment example ------------
file_exists <- TRUE
clear_files <- function(e){
print('Clean some shared files')
# do something to remove files
file_exists <<- FALSE
}
# e1, e2 both require file existence
e1 <- new.env()
e1$valid <- function(){ file_exists }
e2 <- new.env()
e2$valid <- function(){ file_exists }
e1$valid(); e2$valid()
#> [1] TRUE
#> [1] TRUE
# we don't want to remove files when either e1,e2 gets
# garbage collected, however, we want to run `clear_files`
# when system garbage collecting *both* e1 and e2
# Make sure `key`s are identical
shared_finalizer(e1, 'cleanXXXfiles', clear_files)
shared_finalizer(e2, 'cleanXXXfiles', clear_files)
# Now remove e1, files are not cleaned, and e2 is still valid
rm(e1); invisible(gc(verbose = FALSE))
e2$valid() # TRUE
#> [1] TRUE
file_exists # TRUE
#> [1] TRUE
# remove both e1 and e2, and file gets removed
rm(e2); invisible(gc(verbose = FALSE))
#> [1] "Clean some shared files"
file_exists # FALSE
#> [1] FALSE
# ------------ R6 example ------------
cls <- R6::R6Class(
classname = '...demo...',
cloneable = TRUE,
public = list(
file_path = character(0),
shared_finalize = function(){
cat('Finalize shared resource - ', self$file_path, '\n')
},
finalize = function(){
cat('Finalize private resource\n')
},
initialize = function(file_path){
self$file_path = file_path
shared_finalizer(self, key = self$file_path)
}
)
)
e1 <- cls$new('file1')
rm(e1); invisible(gc(verbose = FALSE))
#> Finalize private resource
#> Finalize shared resource - file1
e1 <- cls$new('file2')
# A copy of e1
e2 <- e1$clone()
# unfortunately, we have to manually register
shared_finalizer(e2, key = e2$file_path)
# Remove e1, gc only free private resource
rm(e1); invisible(gc(verbose = FALSE))
#> Finalize private resource
# remove e1 and e2, run shared finalize
rm(e2); invisible(gc(verbose = FALSE))
#> Finalize shared resource - file2
#> Finalize private resource
# ------------ fastmap/fastmap2 example -----------
# No formals needed for fastmap/fastmap2
fin <- function(){
cat('Finalizer is called\n')
}
# single reference case
e1 <- dipsaus::fastmap2()
shared_finalizer(e1, 'fin-fastmap2', fin = fin)
invisible(gc(verbose = FALSE)) # Not triggered
rm(e1); invisible(gc(verbose = FALSE)) # triggered
#> Finalizer is called
# multiple reference case
e1 <- dipsaus::fastmap2()
e2 <- dipsaus::fastmap2()
shared_finalizer(e1, 'fin-fastmap2', fin = fin)
shared_finalizer(e2, 'fin-fastmap2', fin = fin)
rm(e1); invisible(gc(verbose = FALSE)) # Not triggered
rm(e2); invisible(gc(verbose = FALSE)) # triggered
#> Finalizer is called
# ------------ Native reg.finalizer fails example ------------
# This example shows a failure case using base::reg.finalizer
file_exists <- TRUE
clear_files <- function(e){
print('Clean some shared files')
# do something to remove files
file_exists <<- FALSE
}
# e1, e2 both require file existence
e1 <- new.env()
e1$valid <- function(){ file_exists }
e2 <- new.env()
e2$valid <- function(){ file_exists }
reg.finalizer(e1, clear_files)
#> NULL
reg.finalizer(e2, clear_files)
#> NULL
gc()
#> used (Mb) gc trigger (Mb) max used (Mb)
#> Ncells 1357227 72.5 2304624 123.1 2304624 123.1
#> Vcells 2510377 19.2 12261858 93.6 23948760 182.8
file_exists
#> [1] TRUE
# removing e1 will invalidate e2
rm(e1); gc()
#> [1] "Clean some shared files"
#> used (Mb) gc trigger (Mb) max used (Mb)
#> Ncells 1357245 72.5 2304624 123.1 2304624 123.1
#> Vcells 2510443 19.2 12261858 93.6 23948760 182.8
e2$valid() # FALSE
#> [1] FALSE
# Clean-ups
rm(e2); gc()
#> [1] "Clean some shared files"
#> used (Mb) gc trigger (Mb) max used (Mb)
#> Ncells 1357259 72.5 2304624 123.1 2304624 123.1
#> Vcells 2510447 19.2 12261858 93.6 23948760 182.8