Skip to content

Commit 25eced5

Browse files
eregonandrykonchin
authored andcommitted
Use Primitive.use_cext_lock? to find out if the C ext lock should be used
1 parent 2f3bd0c commit 25eced5

File tree

8 files changed

+121
-83
lines changed

8 files changed

+121
-83
lines changed

doc/user/thread-safe-extensions.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ permalink: /reference-manual/ruby/ThreadSafeExtensions/
88

99
Native extensions are by default considered thread-unsafe for maximum compatibility with CRuby and use the global extension lock (unless `--cexts-lock=false` is used).
1010

11-
Extensions can mark themselves as thread-safe either by using `rb_ext_ractor_safe()` or `rb_ext_thread_safe()` (TruffleRuby-specific).
11+
Extensions can mark themselves as thread-safe either by using `rb_ext_ractor_safe()` or `rb_ext_thread_safe()` (the latter is TruffleRuby-specific).
1212
Such extensions are then run by TruffleRuby without a global extension lock, i.e. in parallel.
1313

1414
Here is an example of an extension marking itself as Ractor-safe.
@@ -34,6 +34,27 @@ void Init_my_extension(void) {
3434
}
3535
```
3636

37+
It is possible to mark individual methods as Ractor/Thread-safe by using `rb_ext_ractor_safe/rb_ext_thread_safe(true/false)` around them (these functions actually set a Fiber-local flag):
38+
```c
39+
void Init_my_extension(void) {
40+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
41+
rb_ext_ractor_safe(true);
42+
#endif
43+
rb_define_method(myClass, "ractor_safe_method", foo_impl, 0); // The C function foo_impl can be called from multiple threads in parallel
44+
// more Ractor-safe methods
45+
46+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
47+
rb_ext_ractor_safe(false);
48+
#endif
49+
rb_define_method(myClass, "ractor_unsafe_method", bar_impl, 0); // The C function bar_impl needs a global extension lock for correctness
50+
// more Ractor-unsafe methods
51+
}
52+
```
53+
54+
Other Ruby C API functions taking a C function like `rb_proc_new()` do not use the global extension lock if:
55+
* Called inside the `Init_my_extension` and `rb_ext_ractor_safe(true)` / `rb_ext_thread_safe(true)` are used.
56+
* Called outside the `Init_my_extension` and the calling function does not hold the global extension lock.
57+
3758
The conditions for an extension to be thread-safe are the following.
3859
This is similar to [the conditions for Ractor-safe extensions](https://github.com/ruby/ruby/blob/master/doc/extension.rdoc#appendix-f-ractor-support-) but not all conditions are necessary.
3960
1. The extension should make it clear in its documentation which objects are safe to share between threads and which are not.

lib/truffle/truffle/cext.rb

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ module Truffle::CExt
2727
SULONG = Truffle::Boot.get_option('cexts-sulong')
2828
NFI = !SULONG
2929

30+
CEXT_LOCK = Truffle::Boot.get_option('cexts-lock')
31+
3032
SET_LIBTRUFFLERUBY = -> libtruffleruby do
3133
LIBTRUFFLERUBY = libtruffleruby
3234
end
@@ -243,7 +245,7 @@ def init_extension(library, library_path)
243245
# Resolve while inside the ExtensionCallStackEntry to ensure the preservedObjects are still all alive
244246
resolve_registered_addresses
245247
end
246-
}, [], nil, nil)
248+
}, [], nil, nil, CEXT_LOCK)
247249
end
248250

249251
def supported?
@@ -856,12 +858,14 @@ def rb_str_new_frozen(value)
856858
end
857859

858860
def rb_tracepoint_new(events, func, data)
861+
use_cext_lock = Primitive.use_cext_lock?
859862
TracePoint.new(*events_to_events_array(events)) do |tp|
860863
Primitive.call_with_cext_lock_and_frame(
861864
POINTER2_TO_VOID_WRAPPER,
862865
[func, Primitive.cext_wrap(tp), data],
863866
Primitive.caller_special_variables_if_available,
864-
nil)
867+
nil,
868+
use_cext_lock)
865869
end
866870
end
867871

@@ -1204,6 +1208,7 @@ def rb_path_to_class(path)
12041208
end
12051209

12061210
def rb_proc_new(function, value)
1211+
use_cext_lock = Primitive.use_cext_lock?
12071212
Proc.new do |*args, &block|
12081213
Primitive.call_with_cext_lock_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [
12091214
function,
@@ -1212,7 +1217,7 @@ def rb_proc_new(function, value)
12121217
args.size, # argc
12131218
Truffle::CExt.RARRAY_PTR(args), # argv
12141219
Primitive.cext_wrap(block), # blockarg
1215-
], Primitive.caller_special_variables_if_available, nil)
1220+
], Primitive.caller_special_variables_if_available, nil, use_cext_lock)
12161221
end
12171222
end
12181223

@@ -1475,12 +1480,14 @@ def rb_enumeratorize(obj, meth, args)
14751480

14761481
def rb_enumeratorize_with_size(obj, meth, args, size_fn)
14771482
return rb_enumeratorize(obj, meth, args) if Primitive.interop_null?(size_fn)
1483+
use_cext_lock = Primitive.use_cext_lock?
14781484
enum = obj.to_enum(meth, *args) do
14791485
Primitive.call_with_cext_lock_and_frame_and_unwrap(
14801486
POINTER3_TO_POINTER_WRAPPER,
14811487
[size_fn, Primitive.cext_wrap(obj), Primitive.cext_wrap(args), Primitive.cext_wrap(enum)],
14821488
Primitive.caller_special_variables_if_available,
1483-
nil)
1489+
nil,
1490+
use_cext_lock)
14841491
end
14851492
enum
14861493
end
@@ -1494,12 +1501,14 @@ def rb_newobj_of(ruby_class)
14941501
end
14951502

14961503
def rb_define_alloc_func(ruby_class, function)
1504+
use_cext_lock = Primitive.use_cext_lock?
14971505
ruby_class.singleton_class.define_method(:__allocate__) do
14981506
Primitive.call_with_cext_lock_and_frame_and_unwrap(
14991507
POINTER_TO_POINTER_WRAPPER,
15001508
[function, Primitive.cext_wrap(self)],
15011509
Primitive.caller_special_variables_if_available,
1502-
nil)
1510+
nil,
1511+
use_cext_lock)
15031512
end
15041513
class << ruby_class
15051514
private :__allocate__
@@ -1697,10 +1706,11 @@ def rb_nativethread_lock_destroy(lock)
16971706
end
16981707

16991708
def rb_set_end_proc(func, data)
1709+
use_cext_lock = Primitive.use_cext_lock?
17001710
at_exit do
17011711
Primitive.call_with_cext_lock_and_frame(
17021712
POINTER_TO_VOID_WRAPPER, [func, data],
1703-
Primitive.caller_special_variables_if_available, nil)
1713+
Primitive.caller_special_variables_if_available, nil, use_cext_lock)
17041714
end
17051715
end
17061716

@@ -1736,22 +1746,24 @@ def RTYPEDDATA(object)
17361746

17371747
private def data_sizer(sizer_function, rtypeddata)
17381748
raise unless sizer_function.respond_to?(:call)
1749+
use_cext_lock = Primitive.use_cext_lock?
17391750
proc {
17401751
Primitive.call_with_cext_lock_and_frame(
17411752
POINTER_TO_SIZE_T_WRAPPER, [sizer_function, rtypeddata],
1742-
Primitive.caller_special_variables_if_available, nil)
1753+
Primitive.caller_special_variables_if_available, nil, use_cext_lock)
17431754
}
17441755
end
17451756

17461757
def rb_data_object_wrap(ruby_class, data, mark, free)
17471758
ruby_class = Object unless ruby_class
17481759
object = ruby_class.__send__(:__layout_allocate__)
1760+
use_cext_lock = Primitive.use_cext_lock?
17491761

17501762
rdata = LIBTRUFFLERUBY.rb_tr_rdata_create(mark, free, data)
17511763
Primitive.object_hidden_var_set object, DATA_STRUCT, rdata
17521764
Primitive.object_hidden_var_set object, DATA_MARKER, data_marker(LIBTRUFFLERUBY[:rb_tr_rdata_run_marker], rdata)
17531765
# Could use a simpler finalizer if Truffle::Interop.null?(free)
1754-
Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rdata_run_finalizer], rdata
1766+
Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rdata_run_finalizer], rdata, use_cext_lock
17551767

17561768
Primitive.cext_mark_object_on_call_exit(object) unless Truffle::Interop.null?(mark)
17571769

@@ -1761,12 +1773,13 @@ def rb_data_object_wrap(ruby_class, data, mark, free)
17611773
def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size)
17621774
ruby_class = Object unless ruby_class
17631775
object = ruby_class.__send__(:__layout_allocate__)
1776+
use_cext_lock = Primitive.use_cext_lock?
17641777

17651778
rtypeddata = LIBTRUFFLERUBY.rb_tr_rtypeddata_create(data_type, data)
17661779
Primitive.object_hidden_var_set object, DATA_STRUCT, rtypeddata
17671780
Primitive.object_hidden_var_set object, DATA_MARKER, data_marker(LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_marker], rtypeddata)
17681781
# Could use a simpler finalizer if Truffle::Interop.null?(free)
1769-
Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_finalizer], rtypeddata
1782+
Primitive.objectspace_define_data_finalizer object, LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_finalizer], rtypeddata, use_cext_lock
17701783

17711784
unless Truffle::Interop.null?(size)
17721785
Primitive.object_hidden_var_set object, DATA_MEMSIZER, data_sizer(LIBTRUFFLERUBY[:rb_tr_rtypeddata_run_memsizer], rtypeddata)
@@ -1778,8 +1791,8 @@ def rb_data_typed_object_wrap(ruby_class, data, data_type, mark, free, size)
17781791
object
17791792
end
17801793

1781-
def run_data_finalizer(function, data)
1782-
Primitive.call_with_cext_lock_and_frame POINTER_TO_VOID_WRAPPER, [function, data], nil, nil
1794+
def run_data_finalizer(function, data, use_cext_lock)
1795+
Primitive.call_with_cext_lock_and_frame POINTER_TO_VOID_WRAPPER, [function, data], nil, nil, use_cext_lock
17831796
end
17841797

17851798
def run_marker(obj)
@@ -1835,6 +1848,8 @@ def send_splatted(object, method, args)
18351848
end
18361849

18371850
def rb_block_call(object, method, args, func, data)
1851+
use_cext_lock = Primitive.use_cext_lock?
1852+
18381853
object.__send__(method, *args) do |*block_args|
18391854
Primitive.cext_unwrap(Primitive.call_with_cext_lock(RB_BLOCK_CALL_FUNC_WRAPPER, [ # Probably need to save the frame here for blocks.
18401855
func,
@@ -1843,7 +1858,7 @@ def rb_block_call(object, method, args, func, data)
18431858
block_args.size, # argc
18441859
Truffle::CExt.RARRAY_PTR(block_args), # argv
18451860
nil, # blockarg
1846-
]))
1861+
], use_cext_lock))
18471862
end
18481863
end
18491864

@@ -1920,6 +1935,8 @@ def rb_exec_recursive(func, obj, arg)
19201935
Truffle::Graal.always_split instance_method(:rb_exec_recursive)
19211936

19221937
def rb_catch_obj(tag, func, data)
1938+
use_cext_lock = Primitive.use_cext_lock?
1939+
19231940
catch tag do |caught|
19241941
Primitive.cext_unwrap(Primitive.call_with_cext_lock(RB_BLOCK_CALL_FUNC_WRAPPER, [
19251942
func,
@@ -1928,7 +1945,7 @@ def rb_catch_obj(tag, func, data)
19281945
0, # argc
19291946
nil, # argv
19301947
nil, # blockarg
1931-
]))
1948+
], use_cext_lock))
19321949
end
19331950
end
19341951

@@ -2006,13 +2023,14 @@ def rb_time_interval_acceptable(time_val)
20062023
end
20072024

20082025
def rb_thread_create(fn, args)
2026+
use_cext_lock = Primitive.use_cext_lock?
20092027
Thread.new do
2010-
Primitive.call_with_cext_lock_and_frame(POINTER_TO_POINTER_WRAPPER, [fn, args], Primitive.caller_special_variables_if_available, nil)
2028+
Primitive.call_with_cext_lock_and_frame(POINTER_TO_POINTER_WRAPPER, [fn, args], Primitive.caller_special_variables_if_available, nil, use_cext_lock)
20112029
end
20122030
end
20132031

20142032
def rb_thread_call_with_gvl(function, data)
2015-
Primitive.call_with_cext_lock(POINTER_TO_POINTER_WRAPPER, [function, data])
2033+
Primitive.call_with_cext_lock(POINTER_TO_POINTER_WRAPPER, [function, data], true)
20162034
end
20172035

20182036
def rb_thread_call_without_gvl(function, data1, unblock, data2)
@@ -2035,6 +2053,7 @@ def rb_thread_call_without_gvl(function, data1, unblock, data2)
20352053
end
20362054

20372055
def rb_iterate(iteration, iterated_object, callback, callback_arg)
2056+
use_cext_lock = Primitive.use_cext_lock?
20382057
block = rb_block_proc
20392058
wrapped_callback = proc do |block_arg|
20402059
Primitive.call_with_cext_lock_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [
@@ -2044,13 +2063,13 @@ def rb_iterate(iteration, iterated_object, callback, callback_arg)
20442063
0, # argc
20452064
nil, # argv
20462065
nil, # blockarg
2047-
], Primitive.cext_special_variables_from_stack, block)
2066+
], Primitive.cext_special_variables_from_stack, block, use_cext_lock)
20482067
end
20492068
Primitive.cext_unwrap(
20502069
Primitive.call_with_cext_lock_and_frame(POINTER_TO_POINTER_WRAPPER, [
20512070
iteration,
20522071
Primitive.cext_wrap(iterated_object)
2053-
], Primitive.cext_special_variables_from_stack, wrapped_callback))
2072+
], Primitive.cext_special_variables_from_stack, wrapped_callback, use_cext_lock))
20542073
end
20552074

20562075
# From ruby.h
@@ -2147,21 +2166,24 @@ def rb_hash_aref(object, key)
21472166
def rb_define_hooked_variable(name, gvar, getter, setter)
21482167
name = "$#{name}" unless name.start_with?('$')
21492168
id = name.to_sym
2169+
use_cext_lock = Primitive.use_cext_lock?
21502170

21512171
getter_proc = -> {
21522172
Primitive.call_with_cext_lock_and_frame_and_unwrap(
21532173
POINTER2_TO_POINTER_WRAPPER,
21542174
[getter, Primitive.cext_wrap(id), gvar],
21552175
Primitive.caller_special_variables_if_available,
2156-
nil)
2176+
nil,
2177+
use_cext_lock)
21572178
}
21582179

21592180
setter_proc = -> _, value {
21602181
Primitive.call_with_cext_lock_and_frame(
21612182
POINTER3_TO_VOID_WRAPPER,
21622183
[setter, Primitive.cext_wrap(value), Primitive.cext_wrap(id), gvar],
21632184
Primitive.caller_special_variables_if_available,
2164-
nil)
2185+
nil,
2186+
use_cext_lock)
21652187
}
21662188

21672189
Truffle::KernelOperations.define_hooked_variable id, getter_proc, setter_proc
@@ -2285,6 +2307,7 @@ def rb_fiber_current
22852307
end
22862308

22872309
def rb_fiber_new(function, value)
2310+
use_cext_lock = Primitive.use_cext_lock?
22882311
Fiber.new do |*args|
22892312
Primitive.call_with_cext_lock_and_frame_and_unwrap(RB_BLOCK_CALL_FUNC_WRAPPER, [
22902313
function,
@@ -2293,7 +2316,7 @@ def rb_fiber_new(function, value)
22932316
0, # argc
22942317
nil, # argv
22952318
nil, # blockarg
2296-
], nil, nil)
2319+
], nil, nil, use_cext_lock)
22972320
end
22982321
end
22992322

lib/truffle/truffle/cext_ruby.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def rb_define_method(mod, name, function, argc)
2121
end
2222

2323
wrapper = RB_DEFINE_METHOD_WRAPPERS[argc]
24-
thread_safe = Primitive.cext_thread_safe?
24+
use_cext_lock = Primitive.use_cext_lock?
2525
method_body = Truffle::Graal.copy_captured_locals -> *args, &block do
2626
if argc == -1 # (int argc, VALUE *argv, VALUE obj)
2727
args = [function, args.size, Truffle::CExt.RARRAY_PTR(args), Primitive.cext_wrap(self)]
@@ -36,11 +36,7 @@ def rb_define_method(mod, name, function, argc)
3636

3737
# We must set block argument if given here so that the
3838
# `rb_block_*` functions will be able to find it by walking the stack.
39-
if thread_safe
40-
Primitive.call_with_frame_and_unwrap(wrapper, args, Primitive.caller_special_variables_if_available, block)
41-
else
42-
Primitive.call_with_cext_lock_and_frame_and_unwrap(wrapper, args, Primitive.caller_special_variables_if_available, block)
43-
end
39+
Primitive.call_with_cext_lock_and_frame_and_unwrap(wrapper, args, Primitive.caller_special_variables_if_available, block, use_cext_lock)
4440
end
4541

4642
# Even if the argc is -2, the arity number

0 commit comments

Comments
 (0)