Defer the optimized method redefinition warning when refining under the VM lock

Since add_refined_method_entry() is called from rb_method_entry_make()
under the VM lock, it has to collect the performance warning in the
caller's method_entry_warnings like the rest of rb_method_entry_make().
Otherwise refining an optimized method that the refined class defines
itself, such as

    refine(Integer) { def +(other) end }

with Warning[:performance] enabled and a second Ractor around, warns with
the lock held and trips the critical section assertion in
rb_vm_check_ints.

The struct moves up so that add_refined_method_entry() can see its
members.
This commit is contained in:
John Hawthorn 2026-11-04 16:07:07 +00:00
parent 358733a634
commit 9022974666
Notes: git 2026-11-05 15:34:59 +00:00

View File

@ -1384,7 +1384,22 @@ lookup_method_table(VALUE klass, ID id)
}
}
struct method_entry_warnings;
/* rb_method_entry_make() runs under the VM lock, where it must not warn:
* rb_warn() and rb_category_warn() dispatch Warning.warn and write to $stderr,
* either of which can check for interrupts. It formats the messages instead,
* and the caller emits them once the lock is released.
*
* The performance warning cannot even be formatted under the lock, because
* rb_class2name() names an anonymous class with a "%"PRIsVALUE" that calls
* #to_s, so the method entries to warn about are collected as they are found.
* There can be more than one: the redefinition check walks the whole subclass
* tree. */
struct method_entry_warnings {
VALUE redefined; /* $VERBOSE only */
VALUE problem;
VALUE performance; /* method entries, RB_WARN_CATEGORY_PERFORMANCE */
};
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,
struct method_entry_warnings *warnings);
@ -1397,7 +1412,7 @@ add_refined_method_entry(VALUE refined_class, ID mid, struct method_entry_warnin
rb_method_entry_t *me = lookup_method_table(refined_class, mid);
if (me) {
make_method_entry_refined(refined_class, me, NULL);
make_method_entry_refined(refined_class, me, warnings ? &warnings->performance : NULL);
rb_clear_method_cache(refined_class, mid);
}
else if (warnings) {
@ -1477,22 +1492,6 @@ method_entry_modify_check(VALUE klass, rb_method_type_t type)
}
}
/* rb_method_entry_make() runs under the VM lock, where it must not warn:
* rb_warn() and rb_category_warn() dispatch Warning.warn and write to $stderr,
* either of which can check for interrupts. It formats the messages instead,
* and the caller emits them once the lock is released.
*
* The performance warning cannot even be formatted under the lock, because
* rb_class2name() names an anonymous class with a "%"PRIsVALUE" that calls
* #to_s, so the method entries to warn about are collected as they are found.
* There can be more than one: the redefinition check walks the whole subclass
* tree. */
struct method_entry_warnings {
VALUE redefined; /* $VERBOSE only */
VALUE problem;
VALUE performance; /* method entries, RB_WARN_CATEGORY_PERFORMANCE */
};
static void
method_entry_warnings_emit(const struct method_entry_warnings *warnings)
{