Skip to content

Conversation

tavianator
Copy link

@tavianator tavianator commented Aug 18, 2025

@cfis
Copy link
Collaborator

cfis commented Aug 20, 2025

Hi @tavianator - thanks for working on this. I did have something different in mind though.

When using define_method or define_function, I was thinking a user could pass in an additional parameter that tells Rice to release the GIL. Maybe:

define_method("foo", Arg("length"), Arg("byes"), Return().takeOwnership, Callable.releaseGIL())

So introduce a third allowed class, Callable (or maybe some better name).

Internally Rice would release the GIL before calling the c++ function/method. Any number of method parameters should be supported, so it would work similarly to RubyFunction and protect. Except it would be implemented inside of NativeMethod and NativeFunction.

Another approach could be what pyBind`` does - it has a more generic mechanism that allows users to specify a guard Class that is passed to define_method . But I think that would end up duplicating a bunch of what is already in NativeFunction/NativeMethod (because you need to store function arguments in a tuple and a function pointer) and I'm not seeing another use case (locking?).

As for calling back to Ruby and having to get the GIL, I would probably say that is the implementor's problem. As for specifying a UBF function, I would probably not support that and just use Ruby's default one.

@tavianator
Copy link
Author

Ah okay I'll give that a try. However I don't think that API would work for the use case that prompted this in the first place, since we have to check is_frozen() first before conditionally dropping the GVL.

@cfis
Copy link
Collaborator

cfis commented Aug 22, 2025

Good point. Then we probably do need a lower level class similar to std::lock_guard would be good - ie what Pybind11 seems to do. Or what Rice does in https://github.com/ruby-rice/rice/blob/master/rice/Address_Registration_Guard.ipp.

IE Rice::GvlGuard or some such. Might have to rework NativeFunction and NativeMethod then to avoid duplicated code.

@tavianator
Copy link
Author

I don't see how to build a lock_guard-style API out of the primitives that Ruby exposes like rb_nogvl() which takes a callback. There's no way to split the action of that function up between the constructor and destructor. Unless there's another API for dropping the GVL that I'm missing.

@cfis
Copy link
Collaborator

cfis commented Aug 23, 2025

Agreed.

The generic implementation of manually calling a C++ function without the GIL would be very similar to RubyFunction. The changes would be:

  • Change rb_protect to rb_thread_call_without_gvl and/or rb_thread_call_without_gvl2
  • Remove the error checking after the rb_protect call`
  • Change protect to 'call_without_gil`
  • Deal with both functions and methods

So 2 classes, NativeFunctionNoGIL and NativeMethodNoGil (previous to 4.7 NativeMethod and NativeFunction were combined together but 4.7 splits them up which I like a lot better).

@cfis
Copy link
Collaborator

cfis commented Sep 30, 2025

Well that turned out to be a lot harder than I thought it would. Anyway, this functionality is now implemented (see the linked commit). I still need to update the docs to describe how it works.

Thanks for the help on this @tavianator

@cfis cfis closed this Sep 30, 2025
@tavianator
Copy link
Author

No problem, thanks for taking it to the finish line!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants