Skip to content

Commit

Permalink
* ext/weak: new weakref implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
nobu committed Sep 22, 2015
1 parent 0e84f98 commit 6642d1e
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ext/weak/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require "mkmf"
create_makefile("weak")
127 changes: 127 additions & 0 deletions ext/weak/weak.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "ruby.h"

struct weak_object {
VALUE obj;
VALUE queue;
};

#define WOBJ_FINALIZED ((struct weak_object *)-1)
#define WOBJ_VALID_P(wh) ((wh) && (wh) != WOBJ_FINALIZED)

static void
wobj_mark(void *p)
{
struct weak_object *wh = p;
if (!WOBJ_VALID_P(wh)) return;
if (!NIL_P(wh->queue)) rb_gc_mark(wh->queue);
}

static void
wobj_free(void *p)
{
struct weak_object *wh = p;
if (WOBJ_VALID_P(wh)) {
VALUE obj = wh->obj;
if (!NIL_P(obj)) rb_undefine_finalizer(obj);
}
}

static size_t
wobj_memsize(const void *p)
{
return p ? sizeof(struct weak_object) : 0;
}

static const rb_data_type_t weakobject_type = {
"weakobject",
{
wobj_mark,
wobj_free,
wobj_memsize,
},
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY,
};

static VALUE
wobj_allocate(VALUE klass)
{
return TypedData_Wrap_Struct(klass, &weakobject_type, NULL);
}

static VALUE
wobj_deref(VALUE wobj)
{
struct weak_object *wh = DATA_PTR(wobj);
return WOBJ_VALID_P(wh) ? wh->obj : Qnil;
}

static VALUE
wobj_alive_p(VALUE wobj)
{
struct weak_object *wh = DATA_PTR(wobj);
return WOBJ_VALID_P(wh) ? Qtrue : Qfalse;
}

static VALUE
wobj_initialize(int argc, VALUE *argv, VALUE wobj)
{
VALUE obj, queue;
struct weak_object *wh;

rb_scan_args(argc, argv, "11", &obj, &queue);

if (DATA_PTR(wobj)) {
rb_raise(rb_eArgError, "already initialized");
}
while (rb_typeddata_is_kind_of(obj, &weakobject_type)) {
obj = wobj_deref(obj);
}
DATA_PTR(wobj) = wh = ALLOC(struct weak_object);
rb_define_finalizer(obj, wobj);
wh->obj = obj;
wh->queue = queue;
return wobj;
}

static VALUE
wobj_finalize(int argc, VALUE *argv, VALUE wobj)
{
struct weak_object *wh = DATA_PTR(wobj);
if (WOBJ_VALID_P(wh)) {
VALUE queue = wh->queue;
DATA_PTR(wobj) = WOBJ_FINALIZED;
xfree(wh);
if (!NIL_P(queue)) {
rb_funcallv(queue, rb_intern("push"), 1, &wobj);
}
}
return Qnil;
}

static VALUE
wobj_inspect(VALUE wobj)
{
VALUE str = rb_any_to_s(wobj);
struct weak_object *wh = DATA_PTR(wobj);
VALUE obj;
if (WOBJ_VALID_P(wh) && !NIL_P(obj = wh->obj)) {
rb_str_set_len(str, RSTRING_LEN(str)-1);
rb_str_cat2(str, ": ");
rb_str_append(str, rb_inspect(wh->obj));
rb_str_cat2(str, ">");
}
return str;
}

void
Init_weak(void)
{
VALUE rb_cWeakObject = rb_define_class("Weak", rb_cObject);
rb_define_alloc_func(rb_cWeakObject, wobj_allocate);
rb_define_singleton_method(rb_cWeakObject, "ref", rb_class_new_instance, -1);
rb_define_private_method(rb_cWeakObject, "call", wobj_finalize, -1);
rb_define_method(rb_cWeakObject, "initialize", wobj_initialize, -1);
rb_define_method(rb_cWeakObject, "get", wobj_deref, 0);
rb_define_method(rb_cWeakObject, "alive?", wobj_alive_p, 0);
rb_define_method(rb_cWeakObject, "inspect", wobj_inspect, 0);
}
61 changes: 61 additions & 0 deletions test/weak/test_weak_ref.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require 'test/unit'
require 'weak'
require_relative '../ruby/envutil'

class TestWeakRef < Test::Unit::TestCase
def make_weakref(*args, level: 10)
if level > 0
make_weakref(*args, level: level-1)
else
args <<= Object.new if args.empty?
Weak.ref(*args)
end
ensure
args.clear
end

def test_ref
obj = Object.new
weak = Weak.ref(obj)
assert_same(obj, weak.get)
assert_predicate(weak, :alive?)
end

def test_recycled
weak = 1.times {break make_weakref}
GC.start
GC.start
assert_nil(weak.get)
assert_not_predicate(weak, :alive?)
end

def test_finalize
bug7304 = '[ruby-core:49044]'
assert_normal_exit %q{
require 'weak'
obj = Object.new
3.times do
Weak.ref(obj)
ObjectSpace.garbage_collect
end
}, bug7304
end

def test_queue
q = Object.new
def q.pushed; @pushed; end
def q.push(obj); @pushed = obj; end

ref = nil
1.times do
o = Object.new
ref = make_weakref(o, q)
assert_same(o, ref.get)
assert_nil(q.pushed)
end

5.times { GC.start }
assert_nil(ref.get)
assert_same(ref, q.pushed)
end
end

0 comments on commit 6642d1e

Please sign in to comment.