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

View file

@ -584,6 +584,7 @@ rb_find_global_entry(ID id)
struct rb_global_entry *entry; struct rb_global_entry *entry;
VALUE data; VALUE data;
RB_VM_LOCKING() {
if (!rb_id_table_lookup(rb_global_tbl, id, &data)) { if (!rb_id_table_lookup(rb_global_tbl, id, &data)) {
entry = NULL; entry = NULL;
} }
@ -591,9 +592,10 @@ rb_find_global_entry(ID id)
entry = (struct rb_global_entry *)data; entry = (struct rb_global_entry *)data;
RUBY_ASSERT(entry != NULL); RUBY_ASSERT(entry != NULL);
} }
}
if (UNLIKELY(!rb_ractor_main_p()) && (!entry || !entry->ractor_local)) { 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; return entry;
@ -621,7 +623,9 @@ rb_gvar_undef_compactor(void *var)
static struct rb_global_entry* static struct rb_global_entry*
rb_global_entry(ID id) 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) { if (!entry) {
struct rb_global_variable *var; struct rb_global_variable *var;
entry = ALLOC(struct rb_global_entry); entry = ALLOC(struct rb_global_entry);
@ -641,6 +645,7 @@ rb_global_entry(ID id)
var->namespace_ready = false; var->namespace_ready = false;
rb_id_table_insert(rb_global_tbl, id, (VALUE)entry); rb_id_table_insert(rb_global_tbl, id, (VALUE)entry);
} }
}
return entry; return entry;
} }
@ -1003,6 +1008,7 @@ rb_gvar_set(ID id, VALUE val)
struct rb_global_entry *entry; struct rb_global_entry *entry;
const rb_namespace_t *ns = rb_current_namespace(); const rb_namespace_t *ns = rb_current_namespace();
RB_VM_LOCKING() {
entry = rb_global_entry(id); entry = rb_global_entry(id);
if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { if (USE_NAMESPACE_GVAR_TBL(ns, entry)) {
@ -1013,6 +1019,7 @@ rb_gvar_set(ID id, VALUE val)
else { else {
retval = rb_gvar_set_entry(entry, val); retval = rb_gvar_set_entry(entry, val);
} }
}
return retval; return retval;
} }
@ -1026,9 +1033,11 @@ VALUE
rb_gvar_get(ID id) rb_gvar_get(ID id)
{ {
VALUE retval, gvars, key; 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_entry *entry = rb_global_entry(id);
struct rb_global_variable *var = entry->var; struct rb_global_variable *var = entry->var;
const rb_namespace_t *ns = rb_current_namespace();
if (USE_NAMESPACE_GVAR_TBL(ns, entry)) { if (USE_NAMESPACE_GVAR_TBL(ns, entry)) {
gvars = ns->gvar_tbl; gvars = ns->gvar_tbl;
@ -1047,6 +1056,7 @@ rb_gvar_get(ID id)
else { else {
retval = (*var->getter)(entry->id, var->data); retval = (*var->getter)(entry->id, var->data);
} }
}
return retval; return retval;
} }
@ -1128,7 +1138,7 @@ rb_f_global_variables(void)
void void
rb_alias_variable(ID name1, ID name2) rb_alias_variable(ID name1, ID name2)
{ {
struct rb_global_entry *entry1, *entry2; struct rb_global_entry *entry1 = NULL, *entry2;
VALUE data1; VALUE data1;
struct rb_id_table *gtbl = rb_global_tbl; 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_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors");
} }
RB_VM_LOCKING() {
entry2 = rb_global_entry(name2); entry2 = rb_global_entry(name2);
if (!rb_id_table_lookup(gtbl, name1, &data1)) { if (!rb_id_table_lookup(gtbl, name1, &data1)) {
entry1 = ALLOC(struct rb_global_entry); entry1 = ALLOC(struct rb_global_entry);
@ -1152,12 +1163,12 @@ rb_alias_variable(ID name1, ID name2)
free_global_variable(var); free_global_variable(var);
} }
} }
else { if (entry1) {
return;
}
entry2->var->counter++; entry2->var->counter++;
entry1->var = entry2->var; entry1->var = entry2->var;
} }
}
}
static void static void
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(ID id) IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(ID id)