diff options
author | Chris Seaton <chris.seaton@shopify.com> | 2020-09-16 19:59:36 +0100 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2020-09-16 13:52:24 -0700 |
commit | 8e173d8b2709f47cc0709f699640dafe850c9a8f (patch) | |
tree | 8c5421a14acb0bce87a723ae1b4a8959dc7f2326 /gc.c | |
parent | f75009c1222621836b2340bbb5f4d4274972ccb4 (diff) |
Warn on a finalizer that captures the object to be finalized
Also improve specs and documentation for finalizers and more clearly
recommend a safe code pattern to use them.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/3444
Diffstat (limited to 'gc.c')
-rw-r--r-- | gc.c | 64 |
1 files changed, 61 insertions, 3 deletions
@@ -3384,6 +3384,57 @@ should_be_finalizable(VALUE obj) * as an argument to <i>aProc</i>. If <i>aProc</i> is a lambda or * method, make sure it can be called with a single argument. * + * The return value is an array <code>[0, aProc]</code>. + * + * 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. + * + * 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}" + * } + * end + * end + * + * class Bar + * class Remover + * def initialize(data_needed_for_finalization) + * @data_needed_for_finalization = data_needed_for_finalization + * end + * + * def call(id) + * puts "finalizing #{@data_needed_for_finalization}" + * end + * end + * + * def initialize(data_needed_for_finalization) + * ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization)) + * 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 + * to be run except on exit. */ static VALUE @@ -3400,6 +3451,10 @@ define_final(int argc, VALUE *argv, VALUE os) should_be_callable(block); } + if (rb_callable_receiver(block) == obj) { + rb_warn("finalizer references object to be finalized"); + } + return define_final0(obj, block); } @@ -12101,9 +12156,9 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self) * * ObjectSpace also provides support for object finalizers, procs that will be * called when a specific object is about to be destroyed by garbage - * collection. - * - * require 'objspace' + * collection. See the documentation for + * <code>ObjectSpace.define_finalizer</code> for important information on + * how to use this method correctly. * * a = "A" * b = "B" @@ -12111,6 +12166,9 @@ rb_gcdebug_remove_stress_to_class(int argc, VALUE *argv, VALUE self) * ObjectSpace.define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" }) * ObjectSpace.define_finalizer(b, proc {|id| puts "Finalizer two on #{id}" }) * + * a = nil + * b = nil + * * _produces:_ * * Finalizer two on 537763470 |