diff --git a/gc.c b/gc.c index 1fe3dbf0ae..ab0539cd33 100644 --- a/gc.c +++ b/gc.c @@ -1709,63 +1709,68 @@ rb_gc_copy_finalizer(VALUE dest, VALUE obj) /* * call-seq: - * ObjectSpace.define_finalizer(obj, aProc=proc()) + * ObjectSpace.define_finalizer(obj) {|id| ... } -> array + * ObjectSpace.define_finalizer(obj, finalizer) -> array * - * Adds aProc as a finalizer, to be called after obj - * was destroyed. The object ID of the obj will be passed - * as an argument to aProc. If aProc is a lambda or - * method, make sure it can be called with a single argument. + * Adds a new finalizer for +obj+ that is called when +obj+ is destroyed + * by the garbage collector or when Ruby shuts down (which ever comes first). * - * The return value is an array [0, aProc]. + * With a block given, uses the block as the callback. Without a block given, + * uses a callable object +finalizer+ as the callback. The callback is called + * when +obj+ is destroyed with a single argument +id+ which is the object + * ID of +obj+ (see Object#object_id). * - * The two recommended patterns are to either create the finaliser proc - * in a non-instance method where it can safely capture the needed state, - * or to use a custom callable object that stores the needed state - * explicitly as instance variables. + * The return value is an array [0, callback], where +callback+ + * is a Proc created from the block if one was given or +finalizer+ otherwise. + * + * Note that defining a finalizer in an instance method of the object may prevent + * the object from being garbage collected since if the block or +finalizer+ refers + * to +obj+ then +obj+ will never be reclaimed by the garbage collector. For example, + * the following script demonstrates the issue: * * class Foo - * def initialize(data_needed_for_finalization) - * ObjectSpace.define_finalizer(self, self.class.create_finalizer(data_needed_for_finalization)) - * end - * - * def self.create_finalizer(data_needed_for_finalization) - * proc { - * puts "finalizing #{data_needed_for_finalization}" - * } + * def define_final + * ObjectSpace.define_finalizer(self) do |id| + * puts "Running finalizer for #{id}!" + * end * end * end * - * class Bar - * class Remover - * def initialize(data_needed_for_finalization) - * @data_needed_for_finalization = data_needed_for_finalization - * end + * obj = Foo.new + * obj.define_final * + * There are two patterns to solve this issue: + * + * - Create the finalizer in a non-instance method so it can safely capture + * the needed state: + * + * class Foo + * def define_final + * ObjectSpace.define_finalizer(self, self.class.create_finalizer) + * end + * + * def self.create_finalizer + * proc do |id| + * puts "Running finalizer for #{id}!" + * end + * end + * end + * + * - Use a callable object: + * + * class Foo + * class Finalizer * def call(id) - * puts "finalizing #{@data_needed_for_finalization}" + * puts "Running finalizer for #{id}!" * end * end * - * def initialize(data_needed_for_finalization) - * ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization)) + * def define_final + * ObjectSpace.define_finalizer(self, Finalizer.new) * end * end * - * Note that if your finalizer references the object to be - * finalized it will never be run on GC, although it will still be - * run at exit. You will get a warning if you capture the object - * to be finalized as the receiver of the finalizer. - * - * class CapturesSelf - * def initialize(name) - * ObjectSpace.define_finalizer(self, proc { - * # this finalizer will only be run on exit - * puts "finalizing #{name}" - * }) - * end - * end - * - * Also note that finalization can be unpredictable and is never guaranteed + * Note that finalization can be unpredictable and is never guaranteed * to be run except on exit. */