Ractor: lock around global variable get/set

There's a global id_table `rb_global_tbl` that needs a lock (I used VM lock). In the future, we might use a lock-free rb_id_table if we create such a data structure.

Reproduction script that might crash or behave strangely:

```ruby
100.times do
  Ractor.new do
    1_000_000.times do
      $stderr
      $stdout
      $stdin
      $VERBOSE
      $stderr
      $stdout
      $stdin
      $VERBOSE
      $stderr
      $stdout
      $stdin
      $VERBOSE
    end
  end
end

$myglobal0 = nil;
$myglobal1 = nil;
  # ... vim macros to the rescue
$myglobal100000 = nil;
```
This commit is contained in:
Luke Gruber 2025-07-03 17:43:37 -04:00 committed by Jean Boussier
parent c3d91eb4d9
commit be58cd4d7d
2 changed files with 81 additions and 70 deletions

View file

@ -588,8 +588,8 @@ assert_equal 'true', %q{
r.value[:frozen]
}
# Access to global-variables are prohibited
assert_equal 'can not access global variables $gv from non-main Ractors', %q{
# Access to global-variables are prohibited (read)
assert_equal 'can not access global variable $gv from non-main Ractor', %q{
$gv = 1
r = Ractor.new do
$gv
@ -602,8 +602,8 @@ assert_equal 'can not access global variables $gv from non-main Ractors', %q{
end
}
# Access to global-variables are prohibited
assert_equal 'can not access global variables $gv from non-main Ractors', %q{
# Access to global-variables are prohibited (write)
assert_equal 'can not access global variable $gv from non-main Ractor', %q{
r = Ractor.new do
$gv = 1
end

View file

@ -584,6 +584,7 @@ rb_find_global_entry(ID id)
struct rb_global_entry *entry;
VALUE data;
RB_VM_LOCKING() {
if (!rb_id_table_lookup(rb_global_tbl, id, &data)) {
entry = NULL;
}
@ -591,9 +592,10 @@ rb_find_global_entry(ID id)
entry = (struct rb_global_entry *)data;
RUBY_ASSERT(entry != NULL);
}
}
if (UNLIKELY(!rb_ractor_main_p()) && (!entry || !entry->ractor_local)) {
rb_raise(rb_eRactorIsolationError, "can not access global variables %s from non-main Ractors", rb_id2name(id));
rb_raise(rb_eRactorIsolationError, "can not access global variable %s from non-main Ractor", rb_id2name(id));
}
return entry;
@ -621,7 +623,9 @@ rb_gvar_undef_compactor(void *var)
static struct rb_global_entry*
rb_global_entry(ID id)
{
struct rb_global_entry *entry = rb_find_global_entry(id);
struct rb_global_entry *entry;
RB_VM_LOCKING() {
entry = rb_find_global_entry(id);
if (!entry) {
struct rb_global_variable *var;
entry = ALLOC(struct rb_global_entry);
@ -641,6 +645,7 @@ rb_global_entry(ID id)
var->namespace_ready = false;
rb_id_table_insert(rb_global_tbl, id, (VALUE)entry);
}
}
return entry;
}
@ -1003,6 +1008,7 @@ rb_gvar_set(ID id, VALUE val)
struct rb_global_entry *entry;
const rb_namespace_t *ns = rb_current_namespace();
RB_VM_LOCKING() {
entry = rb_global_entry(id);
if (USE_NAMESPACE_GVAR_TBL(ns, entry)) {
@ -1013,6 +1019,7 @@ rb_gvar_set(ID id, VALUE val)
else {
retval = rb_gvar_set_entry(entry, val);
}
}
return retval;
}
@ -1026,9 +1033,11 @@ VALUE
rb_gvar_get(ID id)
{
VALUE retval, gvars, key;
const rb_namespace_t *ns = rb_current_namespace();
// TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist)
RB_VM_LOCKING() {
struct rb_global_entry *entry = rb_global_entry(id);
struct rb_global_variable *var = entry->var;
const rb_namespace_t *ns = rb_current_namespace();
if (USE_NAMESPACE_GVAR_TBL(ns, entry)) {
gvars = ns->gvar_tbl;
@ -1047,6 +1056,7 @@ rb_gvar_get(ID id)
else {
retval = (*var->getter)(entry->id, var->data);
}
}
return retval;
}
@ -1128,7 +1138,7 @@ rb_f_global_variables(void)
void
rb_alias_variable(ID name1, ID name2)
{
struct rb_global_entry *entry1, *entry2;
struct rb_global_entry *entry1 = NULL, *entry2;
VALUE data1;
struct rb_id_table *gtbl = rb_global_tbl;
@ -1136,6 +1146,7 @@ rb_alias_variable(ID name1, ID name2)
rb_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors");
}
RB_VM_LOCKING() {
entry2 = rb_global_entry(name2);
if (!rb_id_table_lookup(gtbl, name1, &data1)) {
entry1 = ALLOC(struct rb_global_entry);
@ -1152,11 +1163,11 @@ rb_alias_variable(ID name1, ID name2)
free_global_variable(var);
}
}
else {
return;
}
if (entry1) {
entry2->var->counter++;
entry1->var = entry2->var;
}
}
}
static void