Skip to content

Commit 84a2a3a

Browse files
eregonandrykonchin
authored andcommitted
[GR-27379] Run C extensions marked as rb_ext_ractor_safe()/rb_ext_thread_safe() without the C extension lock (#2136)
PullRequest: truffleruby/4471
2 parents c9b1136 + f79582f commit 84a2a3a

File tree

19 files changed

+319
-135
lines changed

19 files changed

+319
-135
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Compatibility:
2525
Performance:
2626

2727
* Use TRegex for binary Regexps with non-US-ASCII characters in the pattern like `/[\x80-\xff]/n` (#3858, @eregon).
28+
* Run C extensions marked as `rb_ext_ractor_safe()` or `rb_ext_thread_safe()` in parallel (without the C extension lock) (#2136, @eregon).
2829

2930
Changes:
3031

doc/user/benchmarking.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ See [this documentation](reporting-performance-problems.md) for more details abo
4444

4545
### Consider Disabling the Global C-Extension Lock
4646

47-
On TruffleRuby, C extensions by default use a global lock for maximum compatibility with CRuby.
48-
If you are benchmarking a multi-threaded Ruby program (e.g. Rails on a multi-threaded server), it is worth trying
47+
On TruffleRuby, C extensions by default use a global extension lock for maximum compatibility with CRuby.
48+
Extensions marked as thread-safe by using `rb_ext_ractor_safe()` or `rb_ext_thread_safe()` do not use the global extension lock and run in parallel.
49+
50+
If you are benchmarking a multi-threaded Ruby program (e.g. Rails on a multi-threaded server),
51+
and not all extensions are already marked as thread-safe, it is worth trying
4952
`TRUFFLERUBYOPT="--experimental-options --cexts-lock=false"`.
50-
[This issue](https://github.com/oracle/truffleruby/issues/2136) tracks a way to automatically not use the lock for extensions which do not need it.
5153

5254
## Recommendations
5355

doc/user/compatibility.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,15 @@ To help alleviate this problem, backtraces are automatically disabled in cases w
194194

195195
## C Extension Compatibility
196196

197+
### Global Extension Lock
198+
199+
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).
200+
201+
Extensions can mark themselves as thread-safe either by using `rb_ext_ractor_safe()` or `rb_ext_thread_safe()`.
202+
Such extensions are then run by TruffleRuby without a global extension lock, i.e. in parallel.
203+
204+
See [Thread-Safe Extensions](thread-safe-extensions.md) for how to mark extensions as Ractor-safe or thread-safe.
205+
197206
### Identifiers may be macros or functions
198207

199208
Identifiers which are normally macros may be functions, functions may be macros, and global variables may be macros.

doc/user/thread-safe-extensions.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
layout: docs-experimental
3+
toc_group: ruby
4+
link_title: Thread-Safe Extensions
5+
permalink: /reference-manual/ruby/ThreadSafeExtensions/
6+
---
7+
# Thread-Safe Extensions
8+
9+
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).
10+
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).
12+
Such extensions are then run by TruffleRuby without a global extension lock, i.e. in parallel.
13+
14+
Here is an example of an extension marking itself as Ractor-safe.
15+
Such an extension must then satisfy [the conditions for Ractor-safe extensions](https://github.com/ruby/ruby/blob/master/doc/extension.rdoc#appendix-f-ractor-support-).
16+
```c
17+
void Init_my_extension(void) {
18+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
19+
rb_ext_ractor_safe(true);
20+
#endif
21+
22+
rb_define_method(myClass, "foo", foo_impl, 0); // The C function foo_impl can be called from multiple threads in parallel
23+
}
24+
```
25+
26+
Here is an example of an extension marking itself as thread-safe:
27+
```c
28+
void Init_my_extension(void) {
29+
#ifdef HAVE_RB_EXT_THREAD_SAFE
30+
rb_ext_thread_safe(true);
31+
#endif
32+
33+
rb_define_method(myClass, "foo", foo_impl, 0); // The C function foo_impl can be called from multiple threads in parallel
34+
}
35+
```
36+
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+
58+
The conditions for an extension to be thread-safe are the following.
59+
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.
60+
1. The extension should make it clear in its documentation which objects are safe to share between threads and which are not.
61+
This already needs to be done on CRuby with the GVL, as threads run concurrently.
62+
It helps gem users to avoid sharing objects between threads incorrectly.
63+
2. The extension's own code must be thread-safe, e.g. not mutate state shared between threads without synchronization.
64+
For example accesses to a `struct` shared between threads should typically be synchronized if it's not immutable.
65+
3. If the extension calls native library functions which are not thread-safe it must ensure that function cannot be called from multiple threads at the same time, e.g. using a [lock](https://github.com/oracle/truffleruby/blob/fd8dc74a72d107f8e58feaf1be1cfbb2f31d2e85/lib/cext/include/ruby/thread_native.h).
66+
4. Ruby C API functions/macros (like `rb_*()`) are generally thread-safe on TruffleRuby, because most of them end up calling some Ruby method.
67+
68+
These are the differences in comparison to Ractor-safe:
69+
* It is allowed to share Ruby objects between multiple threads from an extension, because it is the same as sharing them with only Ruby code.
70+
* There is no need to mark objects as Ractor-shareable.
71+
72+
Another way to look at this is to reason about the guarantees that a global extension lock provides:
73+
* C functions or sections of C code which does __*not*__ use any Ruby C API functions/macros (like `rb_*()`) are executed sequentially, i.e. one after another.
74+
* Calls to any Ruby C API function/macro have the possibility to trigger thread switching, and so for another part of the extension code to execute, while the current function is "suspended".
75+
* Therefore functions given to `rb_define_method`, if they call Ruby C API functions/macros (very likely), do not really benefit from the global extension lock as thread switching can happen in the middle of them, and they already need to take care about other functions executing in between.

lib/cext/include/ruby/internal/intern/load.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,11 @@ void rb_ext_ractor_safe(bool flag);
248248
*/
249249
#define HAVE_RB_EXT_RACTOR_SAFE 1
250250

251+
#ifdef TRUFFLERUBY
252+
void rb_ext_thread_safe(bool flag);
253+
#define RB_EXT_THREAD_SAFE(f) rb_ext_thread_safe(f)
254+
#define HAVE_RB_EXT_THREAD_SAFE 1
255+
#endif
251256
/** @} */
252257

253258
RBIMPL_SYMBOL_EXPORT_END()

lib/cext/include/truffleruby/truffleruby-abi-version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
// $RUBY_VERSION must be the same as TruffleRuby.LANGUAGE_VERSION.
2121
// $ABI_NUMBER starts at 1 and is incremented for every ABI-incompatible change.
2222

23-
#define TRUFFLERUBY_ABI_VERSION "3.3.7.3"
23+
#define TRUFFLERUBY_ABI_VERSION "3.3.7.4"
2424

2525
#endif

0 commit comments

Comments
 (0)