Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Releasing the Global VM Lock in a C extension without using another function

I don't understand why there's a need for another level of indirection when releasing or acquiring the GVL in Ruby C API.
Both rb_thread_call_without_gvl() and rb_thread_call_with_gvl() require a function that accepts only one argument which isn't always the case. I don't want to wrap my arguments in a struct just for the purpose of releasing the GVL. It complicates the code's readability and requires casting from and to void pointers.
After looking into Ruby's threading code I found the GVL_UNLOCK_BEGIN/GVL_UNLOCK_END macros that matches Python's Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS but I can't find documentation about them and when they are safe to use.
There's also the BLOCKING_REGION macro is used within rb_thread_call_without_gvl() but I'm not sure if it's safe to use it as a standalone without calling rb_thread_call_without_gvl() itself.

What is the correct way to safely release the GVL in the middle of the execution flow without having to call another function?

like image 352
the_drow Avatar asked Mar 27 '16 09:03

the_drow


2 Answers

In Ruby 2.x, there is only the rb_thread_call_without_gvl API. GVL_UNLOCK_BEGIN and GVL_UNLOCK_END are implementation details that are only defined in thread.c, and are therefore unavailable to Ruby extensions. Thus, the direct answer to your question is "there is no way to correctly and safely release the GVL without calling another function".

There was previously a "region-based" API, rb_thread_blocking_region_begin/rb_thread_blocking_region_end, but this API was deprecated in Ruby 1.9.3 and removed in Ruby 2.2 (see https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/CAPI_obsolete_definitions for the CAPI deprecation schedule).

Therefore, unfortunately, you are stuck with rb_thread_call_without_gvl.


That said, there's a few things you could do to ease the pain. In standard C, converting between most pointers and void * is implicit, so you don't have to add a cast. Furthermore, using designated initializer syntax can simplify the creation of the argument structure.

Thus, you can write

struct my_func_args {
    int arg1;
    char *arg2;
};

void *func_no_gvl(void *data) {
    struct my_func_args *args = data;
    /* do stuff with args->arg... */
    return NULL;
}

VALUE my_ruby_function(...) {
    ...
    struct my_func_args args = {
        // designated initializer syntax (C99) for cleaner code
        .arg1 = ...,
        .arg2 = ...,
    };

    // call without an unblock function
    void *res = rb_thread_call_without_gvl(func_no_gvl, &args, NULL, NULL);
    ...
}

Although this doesn't solve your original problem, it does at least make it more tolerable (I hope).

like image 136
nneonneo Avatar answered Nov 16 '22 02:11

nneonneo


What is the correct way to safely release the GVL in the middle of the execution flow without having to call another function?

You must use the supplied API or whatever method you use will eventually break. The API to the GVL is defined in thread.h

void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1);
void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1,
         rb_unblock_function_t *ubf, void *data2);
void *rb_thread_call_without_gvl2(void *(*func)(void *), void *data1,
          rb_unblock_function_t *ubf, void *data2);

What you find in the header is an agreement between you the consumer of their API's and the author of the API's. Think of it as a contract. Anything you find in a .c in particular static methods and MACROS are not for consumption outside the file unless it's found in the header. The static keyword prevents this from happening, it's one of the reason it exists and it's most important use in C. The other items you mentioned are in thread.c. You can poke around in thread.c but using anything from it is a violation of the API's contract ie it's not safe and never will be.

I'm not suggesting you do this but the only way for you to do what you want is to copy portions of their implementation into your own code and this would not pass a code review. The amount of code you would need to copy out would likely dwarf anything you would need to do to use their API's safely.

like image 42
Harry Avatar answered Nov 16 '22 04:11

Harry