2

I have a problem with using threads in a C Extension to run ruby code async.

I have the following C code:

struct DATA {
  VALUE callback;
  pthread_t watchThread;
  void *ptr;
};

void *executer(void *ptr) {
  struct DATA *data = (struct DATA *) ptr;
  char oldVal[20] = "1";
  char newVal[20] = "1";

  pthread_cleanup_push(&threadGarbageCollector, data);

  while(1) {
        if(triggerReceived) {
              rb_funcall(data->callback, rb_intern("call"), 0);
        }
  }

  pthread_cleanup_pop(1);

  return NULL;
}

VALUE spawn_thread(VALUE self) {
  VALUE block;
  struct DATA *data;
  Data_Get_Struct(self, struct DATA, data);

  block = rb_block_proc();

  data->callback = block;
  pthread_create(&data->watchThread, NULL, &executer, data);

  return self;
}

I am using this because I want to provide ruby-code as a callback, which will be executed, once the Thread receives a signal.

In general this is working fine, if the callback is something like this ruby-code:

1 + 1

But, if the callbacks ruby-code looks like this:

puts "test"

than the main ruby process will stop responding, once the callback is getting executed. The thread is still running and able to react to signals and puts the "test" everytime, the thread receives a message.

Can somebody maybe tell me, how to fix this?

Thanks a lot

Racer
  • 534
  • 1
  • 6
  • 11
  • I don't think it is supported, there's a reason for MRI to have a GIL: I guess it interefers with GC at least. You could try to ask to Ruby mailing list, it seems to me a more appropriate place – mdesantis Sep 16 '14 at 22:21
  • Hey, as far as I understood the GIL, it is about only one thread being able to do IO per time. This would be totally fine for me. But somehow, after the IO in my thread was done, the main program does not continue working. It would be okay, if the main process would stop working for the time, the other thread is making IO, but afterwards, it should continue working. So maybe I have to release the GIL after I executed ruby code in the thread? – Racer Sep 17 '14 at 06:59
  • What you're trying to do is interesting, but it goes far from my understanding of Ruby internals, and I don't think you will find on SO someone so specialized in Ruby to answer you; I would suggest to try on the [Ruby mailing list](https://www.ruby-lang.org/en/community/mailing-lists/) Let me know if you do it, I would like to follow the discussion ;) – mdesantis Sep 17 '14 at 08:35

1 Answers1

2

From the Ruby C API docs:

As of Ruby 1.9, Ruby supports native 1:1 threading with one kernel thread per Ruby Thread object. Currently, there is a GVL (Global VM Lock) which prevents simultaneous execution of Ruby code which may be released by the rb_thread_call_without_gvl and rb_thread_call_without_gvl2 functions. These functions are tricky-to-use and documented in thread.c; do not use them before reading comments in thread.c.

TLDR; the Ruby VM is not currently (at the time of writing) thread safe. Check out this nice write-up on Ruby Threading for a better overall understanding of how to work within these confines.

You can use Ruby's native_thread_create(rb_thread_t *th) which will use pthread_create behind the scenes. There are some drawbacks that you can read about in the documentation above the method definition. You can then run your callback with Ruby's rb_thread_call_with_gvl method. Also, I haven't done it here, but it might be a good idea to create a wrapper method so you can use rb_protect to handle exceptions your callback may raise (otherwise they will be swallowed by the VM).

VALUE execute_callback(VALUE callback)
{
    return rb_funcall(callback, rb_intern("call"), 0);
}

// execute your callback when the thread receives signal
rb_thread_call_with_gvl(execute_callback, data->callback);
codenamev
  • 2,203
  • 18
  • 24