Project

General

Profile

Misc #13486

Using rb_thread_call_without_gvl{2}

Added by magaudet (Matthew Gaudet) over 3 years ago. Updated over 3 years ago.

Status:
Closed
Priority:
Normal
Assignee:
-
[ruby-core:80800]

Description

I'm currently working on adding asynchronous compilation to Ruby+OMR, and I'm trying to use the existing Ruby thread API. However, given that compilation shouldn't happen while holding the GVL, I've been playing with rb_thread_call_without_gvl{2}. I've encountered something I don't entirely understand however. It appears that if the unblocking function for a thread is actually invoked, the interpreter hangs on shutdown.

With some tracing code elided, it's a pretty simple bit of code:

static int compilation_thread_started = 0;
void unblock_compilation_thread(void* arg) {                                                          
   *(int*)arg  = 0; // interrupt compilation thread.                                                  
}                                                                                                     

void* vm_compile_thread(void *vm) {                                                                   
   while (compilation_thread_started) { // compile until interupted.
         rb_thread_wait_for(rb_time_interval(DBL2NUM(0.01))); // pretend to compile by sleeping.
   }                                                                                                  
   return NULL;                                                                                       
}                                                                                                     

VALUE releaseGVLandStartCompilationThread(rb_vm_t* vm)                                                
   {                                                                                                  
   compilation_thread_started = 1;                                                                    
   rb_thread_call_without_gvl2(vm_compile_thread,             /* func */ 
                               (void*)vm,                     /* func arg */                          
                               unblock_compilation_thread,    /* unblock func */
                               &compilation_thread_started);  /* unblock arg */

   return Qnil; 
   }

void                                                                                                  
kickoff_thread(rb_vm_t* vm)                                                                           
{
   typedef VALUE (*thread_function)(ANYARGS);                                                         
   rb_thread_create((thread_function)(releaseGVLandStartCompilationThread),vm);                       
}

I've attached a patch with a very simple reproducing test case that should apply to trunk as of today. If you run it, what you'll notice is that the unblock function runs, the thread code exits and then the interpreter hangs; in an interpreter, what I see is the spawned thread that released the GVL is waiting to re-aquire, but it appears to be held. The main thread on the other hand, is waiting for the final non-main thread to shut down before proceeding with shutdown.

I've marked this as Misc, because I'm not entirely sure this isn't user error, but I'd love some guidance on how to spawn a thread that's not holding the GVL, but also have it participate in cleanup actions like regular threads.


Files

gvl_thread_error.patch (3.29 KB) gvl_thread_error.patch Patch with testcase. magaudet (Matthew Gaudet), 04/19/2017 07:52 PM

Updated by ko1 (Koichi Sasada) over 3 years ago

I'm not sure what is the problem on your situation, but you can't call rb_thread_wait_for when you don't holding GVL.
Basically, you can't use any of rb_... APIs because they depend on GVL.

BTW, I'm not sure why you are using Ruby thread for a compilation thread.
You can ignore all of Ruby mechanism.

Thanks,
Koichi

Updated by magaudet (Matthew Gaudet) over 3 years ago

Ok. That makes sense.

Thanks for the tip. This can probably get closed (I can't seem to do it myself).

#3

Updated by ko1 (Koichi Sasada) over 3 years ago

  • Status changed from Open to Closed

Updated by magaudet (Matthew Gaudet) over 3 years ago

Thinking more on this, I'm wondering if maybe I would still like to be in a situation where the compilation thread is a ruby thread (and in fact, today, have a working version that runs this way -- I got rid of rb_thread_wait_for--, but I run into a test failure. I'll get into that shortly).

Here's why I think I'd like the compilation thread to be a ruby thread: I would like the process of compiling ruby code the be free to call rb_ family functions, and perhaps, one day, even execute small pieces of Ruby code during compilation. Today, the JIT already makes rb_ family calls, though they are relatively few: rb_iseq_original_iseq, rb_class2name and rb_id2name are the big ones today. My understanding is that the thread must be a ruby thread to call these functions. I call them today in the ruby thread I'm using for compilation using rb_thread_call_with_gvl.

While I can run my compilation thread as a Ruby thread, I run into a couple of issues, all of which originate from this thread having user-level visibility: Some tests expect 1 thread, and see 2 and fail, and the deadlock detection sees the compilation thread as a runnable thread so doesn't expect when the last user Thread calls #stop.

What I'd like to do is cloak the compilation thread(s) from a variety of user level thread introspection, and indicate to some VM services this thread is a bit special, but I'm not sure what would be an acceptable way to do this.

The first idea that jumps to mind is adding a bit to rb_thread_t in order to mark a thread as 'hidden', however I'm not sure how well that fits into the ruby-core's view.

Also available in: Atom PDF