Ruby c extension, how to manage garbage collection between two objects

I have a C extension in which I have a main class (like class A) generated with the classic:

Data_Wrap_Struct
rb_define_alloc_func
rb_define_private_method(mymodule, "initialize" ...)

      

This class has an instance method that generates object B. These B objects can only be generated from A objects and have C data that depends on the data wrapped in an A instance.

I have object A collected by garbage collector before object B, this may result in a Seg error.

How can I tell GC not to collect instance A while some of its B objects are still there. I guess I need to use rb_gc_mark or something. Should I mark instance A every time object B is created?

Edit: more details

I am trying to write a Clang extension. With clang, you first create a CXIndex from which you can get the CXTranslationUnit from which you can get CXDiagnostic and / or CXCursor etc. here's a simple illustration:

Clangc::Index#new => Clangc::Index
Clangc::Index#create_translation_unit => Clangc::TranslationUnit
Clangc::TranslationUnit#diagnostic(index) => Clangc::Diagnostic

      

You can see the code here: https://github.com/cedlemo/ruby-clangc

Edit 2: Solution

Material for creating objects "b" with reference to object "a":

typedef struct B_t {
    void * data; 
    VALUE instance_of_a;
} B_t;

static void
c_b_struct_free(B_t *s)
{
  if(s)
  {

  if(s->data)
    a_function_to_free_the_data(s->data); 

   ruby_xfree(s);
  }
}  
static void
c_b_mark(void *s)
{
  B_t *b =(B_t *)s;
  rb_gc_mark(b->an_instance_of_a);
}

VALUE
c_b_struct_alloc( VALUE klass)
{

    B_t * ptr;
    ptr = (B_t *) ruby_xmalloc(sizeof(B_t)); 
    ptr->data = NULL;
    ptr->an_instance_of_a = Qnil;
    return Data_Wrap_Struct(klass, c_b_mark, c_b_struct_free, (void *) ptr);
}

      

Function c which is used to create object "b" from object "a":

VALUE c_A_get_b_object( VALUE self, VALUE arg)
{

  VALUE mModule = rb_const_get(rb_cObject, rb_intern("MainModule"));\
  VALUE cKlass = rb_const_get(mModule, rb_intern("B"));

  VALUE b_instance = rb_class_new_instance(0, NULL, cKlass);
  B_t *b;
  Data_Get_Struct(b_instance, B_t, b);
  /*
    transform ruby value arg to C value c_arg
  */
  b->data = function_to_fill_the_data(c_arg);
  b->instance_of_a = self;
  return b_instance;
}

      

In the Init_mainModule function:

void Init_mainModule(void) 
{
  VALUE mModule = rb_define_module("MainModule");
  /*some code ....*/
  VALUE cKlass = rb_define_class_under(mModule, "B", rb_cObject);
  rb_define_alloc_func(cKlass, c_b_struct_alloc);
}

      

The same usage of rb_gc_mark can be found in mysql2 / ext / mysql2 / client.c ( rb_mysql_client_mark function

) in the project https://github.com/brianmario/mysql2

+3


source to share


1 answer


In the label function for your class B, you have to label the Ruby object A by telling the garbage collector not to get garbage collected.

The label function can be specified as the second argument to Data_Wrap_Struct. You may need to modify your design in some way to expose a pointer to objects A.



Another option is to allow object A to be an instance variable of object B. You should probably do this so that the Ruby code can get object A from object B. This will have the side effect of preventing the garbage collector from collecting A to B, but you shouldn't rely on this side effect because your Ruby code might accidentally mess up an instance variable and then cause a segmentation fault.

Edit: Another option is to use reference counting for shared data C. Then, when the last Ruby object to use that shared data gets garbage collected, you discard the shared data. This would have to find a good cross-platform, thread-safe way to do reference counting so that it isn't trivial.

+1


source







All Articles