mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 08:25:20 +08:00
Add assertions that interrupts aren't checked when VM lock taken. (#18832)
This commit is contained in:
1 parent
fb829aff63
commit
a00df45480
6 files changed
+164
-87
Notes:
git
2026-09-15 04:23:43 +00:00
Merged-By: ioquatix <samuel@codeotaku.com>
No files matched your search
@@ -214,7 +214,7 @@ static inline int
|
||||
vm_check_ints_blocking(rb_execution_context_t *ec)
|
||||
{
|
||||
#ifdef RUBY_ASSERT_CRITICAL_SECTION
|
||||
VM_ASSERT(ruby_assert_critical_section_entered == 0);
|
||||
VM_ASSERT(ec->assert_critical_section_entered == 0);
|
||||
#endif
|
||||
|
||||
rb_thread_t *th = rb_ec_thread_ptr(ec);
|
||||
@@ -1704,7 +1704,7 @@ blocking_region_begin(rb_thread_t *th, struct rb_blocking_region_buffer *region,
|
||||
rb_unblock_function_t *ubf, void *arg, int flags)
|
||||
{
|
||||
#ifdef RUBY_ASSERT_CRITICAL_SECTION
|
||||
VM_ASSERT(ruby_assert_critical_section_entered == 0);
|
||||
VM_ASSERT(th->ec->assert_critical_section_entered == 0);
|
||||
#endif
|
||||
VM_ASSERT(th == GET_THREAD());
|
||||
|
||||
|
||||
+102
-64
@@ -599,25 +599,69 @@ rb_free_generic_fields_tbl_(void)
|
||||
st_free_table(generic_fields_tbl_);
|
||||
}
|
||||
|
||||
static void
|
||||
rb_gvar_undef_compactor(void *var)
|
||||
{
|
||||
}
|
||||
|
||||
NORETURN(static void global_entry_isolation_error(ID id));
|
||||
|
||||
static void
|
||||
global_entry_isolation_error(ID id)
|
||||
{
|
||||
rb_raise(rb_eRactorIsolationError, "can not access global variable %s from non-main Ractor", rb_id2name(id));
|
||||
}
|
||||
|
||||
/* Sets *isolation_error when the caller must raise; the caller has to do that
|
||||
* once it no longer holds the VM lock. */
|
||||
static struct rb_global_entry*
|
||||
rb_find_global_entry(ID id)
|
||||
global_entry_lookup(ID id, bool create_entry, bool *isolation_error)
|
||||
{
|
||||
struct rb_global_entry *entry;
|
||||
VALUE data;
|
||||
|
||||
RB_VM_LOCKING() {
|
||||
if (!rb_id_table_lookup(rb_global_tbl, id, &data)) {
|
||||
entry = NULL;
|
||||
}
|
||||
else {
|
||||
if (rb_id_table_lookup(rb_global_tbl, id, &data)) {
|
||||
entry = (struct rb_global_entry *)data;
|
||||
RUBY_ASSERT(entry != NULL);
|
||||
}
|
||||
else {
|
||||
entry = NULL;
|
||||
}
|
||||
|
||||
*isolation_error = UNLIKELY(!rb_ractor_main_p()) && (!entry || !entry->ractor_local);
|
||||
|
||||
if (!entry && create_entry && !*isolation_error) {
|
||||
struct rb_global_variable *var = ALLOC(struct rb_global_variable);
|
||||
entry = ALLOC(struct rb_global_entry);
|
||||
entry->id = id;
|
||||
entry->var = var;
|
||||
entry->ractor_local = false;
|
||||
var->counter = 1;
|
||||
var->data = 0;
|
||||
var->getter = rb_gvar_undef_getter;
|
||||
var->setter = rb_gvar_undef_setter;
|
||||
var->marker = rb_gvar_undef_marker;
|
||||
var->compactor = rb_gvar_undef_compactor;
|
||||
|
||||
var->block_trace = 0;
|
||||
var->trace = 0;
|
||||
var->box_ready = false;
|
||||
var->box_dynamic = false;
|
||||
rb_id_table_insert(rb_global_tbl, id, (VALUE)entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(!rb_ractor_main_p()) && (!entry || !entry->ractor_local)) {
|
||||
rb_raise(rb_eRactorIsolationError, "can not access global variable %s from non-main Ractor", rb_id2name(id));
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
static struct rb_global_entry*
|
||||
rb_find_global_entry(ID id)
|
||||
{
|
||||
bool isolation_error;
|
||||
struct rb_global_entry *entry = global_entry_lookup(id, false, &isolation_error);
|
||||
|
||||
if (isolation_error) global_entry_isolation_error(id);
|
||||
|
||||
return entry;
|
||||
}
|
||||
@@ -643,38 +687,14 @@ rb_gvar_box_dynamic(const char *name)
|
||||
entry->var->box_dynamic = true;
|
||||
}
|
||||
|
||||
static void
|
||||
rb_gvar_undef_compactor(void *var)
|
||||
{
|
||||
}
|
||||
|
||||
static struct rb_global_entry*
|
||||
rb_global_entry(ID 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);
|
||||
var = ALLOC(struct rb_global_variable);
|
||||
entry->id = id;
|
||||
entry->var = var;
|
||||
entry->ractor_local = false;
|
||||
var->counter = 1;
|
||||
var->data = 0;
|
||||
var->getter = rb_gvar_undef_getter;
|
||||
var->setter = rb_gvar_undef_setter;
|
||||
var->marker = rb_gvar_undef_marker;
|
||||
var->compactor = rb_gvar_undef_compactor;
|
||||
bool isolation_error;
|
||||
struct rb_global_entry *entry = global_entry_lookup(id, true, &isolation_error);
|
||||
|
||||
if (isolation_error) global_entry_isolation_error(id);
|
||||
|
||||
var->block_trace = 0;
|
||||
var->trace = 0;
|
||||
var->box_ready = false;
|
||||
var->box_dynamic = false;
|
||||
rb_id_table_insert(rb_global_tbl, id, (VALUE)entry);
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -1036,11 +1056,12 @@ rb_gvar_set(ID id, VALUE val)
|
||||
struct rb_global_entry *entry = NULL;
|
||||
const rb_box_t *box = rb_current_box();
|
||||
bool use_box_tbl = false;
|
||||
bool isolation_error = false;
|
||||
|
||||
RB_VM_LOCKING() {
|
||||
entry = rb_global_entry(id);
|
||||
entry = global_entry_lookup(id, true, &isolation_error);
|
||||
|
||||
if (gvar_use_box_tbl(box, entry)) {
|
||||
if (!isolation_error && gvar_use_box_tbl(box, entry)) {
|
||||
use_box_tbl = true;
|
||||
rb_hash_aset(box->gvar_tbl, rb_id2sym(entry->id), val);
|
||||
retval = val;
|
||||
@@ -1048,6 +1069,8 @@ rb_gvar_set(ID id, VALUE val)
|
||||
}
|
||||
}
|
||||
|
||||
if (isolation_error) global_entry_isolation_error(id);
|
||||
|
||||
if (!use_box_tbl) {
|
||||
retval = rb_gvar_set_entry(entry, val);
|
||||
}
|
||||
@@ -1066,33 +1089,41 @@ rb_gvar_get(ID id)
|
||||
VALUE retval, gvars, key;
|
||||
const rb_box_t *box = rb_current_box();
|
||||
bool use_box_tbl = false;
|
||||
bool isolation_error = false;
|
||||
struct rb_global_entry *entry = NULL;
|
||||
struct rb_global_variable *var = NULL;
|
||||
// TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist)
|
||||
RB_VM_LOCKING() {
|
||||
entry = rb_global_entry(id);
|
||||
var = entry->var;
|
||||
|
||||
if (gvar_use_box_tbl(box, entry)) {
|
||||
use_box_tbl = true;
|
||||
gvars = box->gvar_tbl;
|
||||
key = rb_id2sym(entry->id);
|
||||
if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached
|
||||
retval = rb_hash_aref(gvars, key);
|
||||
}
|
||||
else {
|
||||
RB_VM_UNLOCK();
|
||||
{
|
||||
retval = (*var->getter)(entry->id, var->data);
|
||||
if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) {
|
||||
retval = rb_funcall(retval, rb_intern("clone"), 0);
|
||||
RB_VM_LOCKING() {
|
||||
// TODO: use lock-free rb_id_table when it's available for use (doesn't yet exist)
|
||||
entry = global_entry_lookup(id, true, &isolation_error);
|
||||
|
||||
if (!isolation_error) {
|
||||
var = entry->var;
|
||||
|
||||
if (gvar_use_box_tbl(box, entry)) {
|
||||
use_box_tbl = true;
|
||||
gvars = box->gvar_tbl;
|
||||
key = rb_id2sym(entry->id);
|
||||
if (RTEST(rb_hash_has_key(gvars, key))) { // this gvar is already cached
|
||||
retval = rb_hash_aref(gvars, key);
|
||||
}
|
||||
else {
|
||||
RB_VM_UNLOCK();
|
||||
{
|
||||
retval = (*var->getter)(entry->id, var->data);
|
||||
if (rb_obj_respond_to(retval, rb_intern("clone"), 1)) {
|
||||
retval = rb_funcall(retval, rb_intern("clone"), 0);
|
||||
}
|
||||
}
|
||||
RB_VM_LOCK();
|
||||
rb_hash_aset(gvars, key, retval);
|
||||
}
|
||||
RB_VM_LOCK();
|
||||
rb_hash_aset(gvars, key, retval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isolation_error) global_entry_isolation_error(id);
|
||||
|
||||
if (!use_box_tbl) {
|
||||
retval = (*var->getter)(entry->id, var->data);
|
||||
}
|
||||
@@ -1180,13 +1211,17 @@ rb_alias_variable(ID name1, ID name2)
|
||||
struct rb_global_entry *entry1 = NULL, *entry2;
|
||||
VALUE data1;
|
||||
struct rb_id_table *gtbl = rb_global_tbl;
|
||||
bool tracer_error = false;
|
||||
|
||||
if (!rb_ractor_main_p()) {
|
||||
rb_raise(rb_eRactorIsolationError, "can not access global variables from non-main Ractors");
|
||||
}
|
||||
|
||||
RB_VM_LOCKING() {
|
||||
entry2 = rb_global_entry(name2);
|
||||
bool isolation_error;
|
||||
entry2 = global_entry_lookup(name2, true, &isolation_error);
|
||||
VM_ASSERT(!isolation_error); /* main Ractor, checked above */
|
||||
|
||||
if (!rb_id_table_lookup(gtbl, name1, &data1)) {
|
||||
entry1 = ZALLOC(struct rb_global_entry);
|
||||
entry1->id = name1;
|
||||
@@ -1195,19 +1230,22 @@ rb_alias_variable(ID name1, ID name2)
|
||||
else if ((entry1 = (struct rb_global_entry *)data1)->var != entry2->var) {
|
||||
struct rb_global_variable *var = entry1->var;
|
||||
if (var->block_trace) {
|
||||
RB_VM_UNLOCK();
|
||||
rb_raise(rb_eRuntimeError, "can't alias in tracer");
|
||||
tracer_error = true;
|
||||
}
|
||||
var->counter--;
|
||||
if (var->counter == 0) {
|
||||
free_global_variable(var);
|
||||
else {
|
||||
var->counter--;
|
||||
if (var->counter == 0) {
|
||||
free_global_variable(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entry1->var != entry2->var) {
|
||||
if (!tracer_error && entry1->var != entry2->var) {
|
||||
entry2->var->counter++;
|
||||
entry1->var = entry2->var;
|
||||
}
|
||||
}
|
||||
|
||||
if (tracer_error) rb_raise(rb_eRuntimeError, "can't alias in tracer");
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
#include "probes.h"
|
||||
#include "probes_helper.h"
|
||||
|
||||
#ifdef RUBY_ASSERT_CRITICAL_SECTION
|
||||
int ruby_assert_critical_section_entered = 0;
|
||||
#endif
|
||||
|
||||
static void *native_main_thread_stack_top;
|
||||
|
||||
bool ruby_vm_during_cleanup = false;
|
||||
|
||||
@@ -94,9 +94,8 @@ RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
|
||||
If `rb_vm_check_ints()` is called between the `RUBY_ASSERT_CRITICAL_SECTION_ENTER()` and
|
||||
`RUBY_ASSERT_CRITICAL_SECTION_LEAVE()`, a failed assertion will result.
|
||||
*/
|
||||
extern int ruby_assert_critical_section_entered;
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_ENTER() do{ruby_assert_critical_section_entered += 1;}while(false)
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_LEAVE() do{VM_ASSERT(ruby_assert_critical_section_entered > 0);ruby_assert_critical_section_entered -= 1;}while(false)
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_ENTER() do{GET_EC()->assert_critical_section_entered += 1;}while(false)
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_LEAVE() do{rb_execution_context_t *ec__ = GET_EC();VM_ASSERT(ec__->assert_critical_section_entered > 0);ec__->assert_critical_section_entered -= 1;}while(false)
|
||||
#else
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_ENTER()
|
||||
#define RUBY_ASSERT_CRITICAL_SECTION_LEAVE()
|
||||
@@ -1147,6 +1146,10 @@ struct rb_execution_context_struct {
|
||||
void *asan_fake_stack_handle;
|
||||
#endif
|
||||
} machine;
|
||||
|
||||
#ifdef RUBY_ASSERT_CRITICAL_SECTION
|
||||
int assert_critical_section_entered;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef rb_execution_context_t
|
||||
@@ -2363,7 +2366,7 @@ static inline void
|
||||
rb_vm_check_ints(rb_execution_context_t *ec)
|
||||
{
|
||||
#ifdef RUBY_ASSERT_CRITICAL_SECTION
|
||||
VM_ASSERT(ruby_assert_critical_section_entered == 0);
|
||||
VM_ASSERT(ec->assert_critical_section_entered == 0);
|
||||
#endif
|
||||
|
||||
VM_ASSERT(ec == rb_current_ec_noinline());
|
||||
|
||||
+51
-13
@@ -1435,6 +1435,31 @@ rb_zsuper_to_super(int argc, VALUE *argv, VALUE self)
|
||||
}
|
||||
|
||||
static inline rb_method_entry_t* search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined);
|
||||
static void
|
||||
method_entry_modify_check(VALUE klass, rb_method_type_t type)
|
||||
{
|
||||
ASSERT_vm_unlocking();
|
||||
if (type != VM_METHOD_TYPE_REFINED) {
|
||||
rb_class_modify_check(NIL_P(klass) ? rb_cObject : klass);
|
||||
}
|
||||
}
|
||||
|
||||
/* rb_method_entry_make() runs under the VM lock, where it must not warn:
|
||||
* rb_warn() dispatches Warning.warn and writes to $stderr, either of which can
|
||||
* check for interrupts. It formats the message instead, and the caller emits
|
||||
* it once the lock is released. */
|
||||
struct method_entry_warnings {
|
||||
VALUE redefined; /* $VERBOSE only */
|
||||
VALUE problem;
|
||||
};
|
||||
|
||||
static void
|
||||
method_entry_warnings_emit(const struct method_entry_warnings *warnings)
|
||||
{
|
||||
if (warnings->redefined) rb_warning("%"PRIsVALUE, warnings->redefined);
|
||||
if (warnings->problem) rb_warn("%"PRIsVALUE, warnings->problem);
|
||||
}
|
||||
|
||||
/*
|
||||
* klass->method_table[mid] = method_entry(defined_class, visi, def)
|
||||
*
|
||||
@@ -1443,7 +1468,8 @@ static inline rb_method_entry_t* search_method0(VALUE klass, ID id, VALUE *defin
|
||||
*/
|
||||
static rb_method_entry_t *
|
||||
rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibility_t visi,
|
||||
rb_method_type_t type, rb_method_definition_t *def, ID original_id, void *opts)
|
||||
rb_method_type_t type, rb_method_definition_t *def, ID original_id, void *opts,
|
||||
struct method_entry_warnings *warnings)
|
||||
{
|
||||
rb_method_entry_t *me;
|
||||
struct rb_id_table *mtbl;
|
||||
@@ -1470,10 +1496,6 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil
|
||||
}
|
||||
}
|
||||
|
||||
if (type != VM_METHOD_TYPE_REFINED) {
|
||||
rb_class_modify_check(klass);
|
||||
}
|
||||
|
||||
if (RB_TYPE_P(klass, T_MODULE) && FL_TEST(klass, RMODULE_IS_REFINEMENT)) {
|
||||
VALUE refined_class = rb_refinement_module_get_refined_class(klass);
|
||||
if (type == VM_METHOD_TYPE_ZSUPER) {
|
||||
@@ -1524,7 +1546,7 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil
|
||||
break;
|
||||
}
|
||||
if (iseq) {
|
||||
rb_warning(
|
||||
warnings->redefined = rb_sprintf(
|
||||
"method redefined; discarding old %"PRIsVALUE"\n%"PRIsVALUE":%d: warning: previous definition of %"PRIsVALUE" was here",
|
||||
rb_id2str(mid),
|
||||
rb_iseq_path(iseq),
|
||||
@@ -1533,7 +1555,7 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil
|
||||
);
|
||||
}
|
||||
else {
|
||||
rb_warning("method redefined; discarding old %"PRIsVALUE, rb_id2str(mid));
|
||||
warnings->redefined = rb_sprintf("method redefined; discarding old %"PRIsVALUE, rb_id2str(mid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1561,13 +1583,13 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil
|
||||
case idRespond_to_missing:
|
||||
case idMethodMissing:
|
||||
case idRespond_to:
|
||||
rb_warn("redefining Object#%s may cause infinite loop", rb_id2name(mid));
|
||||
warnings->problem = rb_sprintf("redefining Object#%s may cause infinite loop", rb_id2name(mid));
|
||||
}
|
||||
}
|
||||
/* check mid */
|
||||
if (mid == object_id || mid == id__id__ || mid == id__send__) {
|
||||
if (type != VM_METHOD_TYPE_CFUNC && search_method(klass, mid, 0)) {
|
||||
rb_warn("redefining '%s' may cause serious problems", rb_id2name(mid));
|
||||
warnings->problem = rb_sprintf("redefining '%s' may cause serious problems", rb_id2name(mid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,10 +1755,16 @@ void
|
||||
rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_visibility_t visi)
|
||||
{
|
||||
const rb_method_entry_t *me;
|
||||
struct method_entry_warnings warnings = {0};
|
||||
|
||||
method_entry_modify_check(klass, type);
|
||||
|
||||
RB_VM_LOCKING() {
|
||||
me = rb_method_entry_make(klass, mid, klass, visi, type, NULL, mid, opts);
|
||||
me = rb_method_entry_make(klass, mid, klass, visi, type, NULL, mid, opts, &warnings);
|
||||
}
|
||||
|
||||
method_entry_warnings_emit(&warnings);
|
||||
|
||||
if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) {
|
||||
method_added(klass, mid, me);
|
||||
}
|
||||
@@ -1761,15 +1789,21 @@ method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *me,
|
||||
rb_method_visibility_t visi, VALUE defined_class)
|
||||
{
|
||||
rb_method_entry_t *newme;
|
||||
struct method_entry_warnings warnings = {0};
|
||||
|
||||
method_entry_modify_check(klass, me->def->type);
|
||||
|
||||
RB_VM_LOCKING() {
|
||||
newme = rb_method_entry_make(klass, mid, defined_class, visi,
|
||||
me->def->type, me->def, 0, NULL);
|
||||
me->def->type, me->def, 0, NULL, &warnings);
|
||||
if (newme == me) {
|
||||
me->def->no_redef_warning = TRUE;
|
||||
METHOD_ENTRY_FLAGS_SET(newme, visi, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
method_entry_warnings_emit(&warnings);
|
||||
|
||||
method_added(klass, mid, newme);
|
||||
return newme;
|
||||
}
|
||||
@@ -2910,7 +2944,7 @@ rb_alias(VALUE klass, ID alias_name, ID original_name)
|
||||
rb_raise(rb_eTypeError, "no class to make alias");
|
||||
}
|
||||
|
||||
rb_class_modify_check(klass);
|
||||
rb_class_modify_check(target_klass);
|
||||
|
||||
again:
|
||||
orig_me = search_method(klass, original_name, &defined_class);
|
||||
@@ -2945,10 +2979,14 @@ rb_alias(VALUE klass, ID alias_name, ID original_name)
|
||||
if (visi == METHOD_VISI_UNDEF) visi = METHOD_ENTRY_VISI(orig_me);
|
||||
|
||||
if (orig_me->defined_class == 0) {
|
||||
struct method_entry_warnings warnings = {0};
|
||||
const rb_method_entry_t *alias_me =
|
||||
// TODO: needs vm lock?
|
||||
rb_method_entry_make(target_klass, alias_name, target_klass, visi,
|
||||
VM_METHOD_TYPE_ALIAS, NULL, orig_me->called_id,
|
||||
(void *)rb_method_entry_clone(orig_me));
|
||||
(void *)rb_method_entry_clone(orig_me), &warnings);
|
||||
|
||||
method_entry_warnings_emit(&warnings);
|
||||
method_added(target_klass, alias_name, alias_me);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -111,6 +111,7 @@ vm_lock_enter(rb_ractor_t *cr, rb_vm_t *vm, bool locked, bool no_barrier, unsign
|
||||
|
||||
vm->ractor.sync.lock_rec++;
|
||||
*lev = vm->ractor.sync.lock_rec;
|
||||
RUBY_ASSERT_CRITICAL_SECTION_ENTER();
|
||||
|
||||
RUBY_DEBUG_LOG2(file, line, "rec:%u owner:%u", vm->ractor.sync.lock_rec,
|
||||
(unsigned int)rb_ractor_id(vm->ractor.sync.lock_owner));
|
||||
@@ -144,6 +145,7 @@ vm_lock_leave(rb_vm_t *vm, bool no_barrier, unsigned int *lev APPEND_LOCATION_AR
|
||||
RUBY_DTRACE_GVL_RELEASE();
|
||||
}
|
||||
|
||||
RUBY_ASSERT_CRITICAL_SECTION_LEAVE();
|
||||
vm->ractor.sync.lock_rec--;
|
||||
*lev = vm->ractor.sync.lock_rec;
|
||||
|
||||
|
||||
Reference in new issue
Block a user