3

I have a C extension in which I have a main class (class A for example) created with the classical:

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

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

I the A object are collected by the garbage collector before a B object, this could result in a Seg Fault.

How can I tell the GC to not collect a A instance while some of his B objects are still remaining. I guess I have to use rb_gc_mark or something like that. Should I have to mark the A instance each time a B object is created ??

Edit : More specifics Informations

I am trying to write a Clang extension. With clang, you first create a CXIndex, from which you can get a CXTranslationUnit, from which you can get a CXDiagnostic and or a CXCursor and so on. here is a simple illustration:

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

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

Edit 2 : A solution

The stuff to build the "b" objects with a reference to the "a" object:

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);
}

The c function that is used to build a "b" object from an "a" object:

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);
}

Same usage of the 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

cedlemo
  • 3,205
  • 3
  • 32
  • 50
  • When you say "generate", do you mean that `B` objects look stand-alone to Ruby, but are linked underneath? Would your object model make sense if you could do `a_object.all_the_b_objects` and/or `b_object.parent_a_object` . . . with those relations in play the answer could be slightly different to if they were not. – Neil Slater May 23 '15 at 09:01
  • B object can not exist without A object. Wrapped data in B object depends on Wrapped data in A object. I wanted to make a very global question but I have added an edit to illustrate this question with what I am curently trying to do. – cedlemo May 23 '15 at 09:24

1 Answers1

1

In the mark function for your B class, you should mark the A Ruby object, telling the garbage collector not to garbage collect it.

The mark function can be specified as the second argument to Data_Wrap_Struct. You might need to modify your design somehow to expose a pointer to the A objects.

Another option is to let the A object be an instance variable of the B object. You should probably do this anyway so that Ruby code can obtain the A object from the B object. Doing this would have the side effect of making the garbage collector not collect the A before the B, but you should not be relying on this side effect because it would be possible for your Ruby code to accidentally mess up the instance variable and then cause a segmentation fault.

Edit: Another option is to use reference counting of the shared C data. Then when the last Ruby object that is using that shared data gets garbage collected, you would delete the shared data. This would involve finding a good, cross-platform, thread-safe way to do reference counting so it might not be trivial.

David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • " You might need to modify your design somehow to expose a pointer to the A objects" Iam sorry but I don't understand what you mean. – cedlemo May 23 '15 at 09:52
  • If I use rb_gc_register_mark_object it means that I have to keep track of the current instance VALUE/ ptr so you maybe want to say that I have to expose a pointer "of" the A objects? – cedlemo May 23 '15 at 10:25
  • Yes, you got it. A pointer to the Ruby A object must be exposed to the B objects so they can mark it. – David Grayson May 23 '15 at 16:52
  • Do I have to use rb_gc_register_mark_object in the mark function with Data_Wrap_Struct or can I use it after my object is allocated. Like my the last commit here : https://github.com/cedlemo/ruby-clangc . I haven't make any tests for now – cedlemo May 23 '15 at 17:12
  • No, you're doing it wrong in your [last commit](https://github.com/cedlemo/ruby-clangc/commit/41ca8be632d812584f8381ae4dc2a5b50065793f). The point of a mark and sweep garbage collector is that every object knows which objects it depends on, and the garbage collector can ask any object at any time to mark the objects it depends on. You need to use the second parameter to Data_Wrap_Struct. Search for "mark" here: https://github.com/ruby/ruby/blob/trunk/doc/extension.rdoc – David Grayson May 23 '15 at 17:18
  • As far as I can [tell](https://www.ruby-forum.com/topic/5364593), the `rb_gc_register_mark_object` function will prevent the garbage collector from ever collecting the object. – David Grayson May 23 '15 at 17:21
  • Ok I have done like you told me could you have a look at https://github.com/cedlemo/ruby-clangc. A confirmation from you could allow me to accept your answer and to write a real case solution as an edit in my Question. – cedlemo May 24 '15 at 12:43
  • Yeah, it looks good to me. But you shouldn't trust me as some kind of authority on Ruby's garbage collector, I'm just the one pointing you to the right documentation. – David Grayson May 24 '15 at 18:00
  • Well you know more things than me and any advices are good to take. I think I will try to confirm this and then write a solution in an edit. Anyway thanks you – cedlemo May 24 '15 at 19:12