summaryrefslogtreecommitdiff
path: root/gc.c
diff options
context:
space:
mode:
authorChris Seaton <chris.seaton@shopify.com>2020-09-16 19:59:36 +0100
committerAaron Patterson <aaron.patterson@gmail.com>2020-09-16 13:52:24 -0700
commit8e173d8b2709f47cc0709f699640dafe850c9a8f (patch)
tree8c5421a14acb0bce87a723ae1b4a8959dc7f2326 /gc.c
parentf75009c1222621836b2340bbb5f4d4274972ccb4 (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.c64
1 files changed, 61 insertions, 3 deletions
diff --git a/gc.c b/gc.c
index 6a8e838e34..38d146362d 100644
--- a/gc.c
+++ b/gc.c
@@ -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