Skip to content

Commit f1f2dfe

Browse files
authored
Release VM lock before running finalizers (#15050)
We shouldn't run any ruby code with the VM lock held.
1 parent e9e5a4a commit f1f2dfe

File tree

3 files changed

+36
-8
lines changed

3 files changed

+36
-8
lines changed

gc.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ rb_gc_run_obj_finalizer(VALUE objid, long count, VALUE (*callback)(long i, void
290290
saved.finished = 0;
291291
saved.final = Qundef;
292292

293+
ASSERT_vm_unlocking();
293294
rb_ractor_ignore_belonging(true);
294295
EC_PUSH_TAG(ec);
295296
enum ruby_tag_type state = EC_EXEC_TAG();

gc/default/default.c

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2763,24 +2763,27 @@ rb_gc_impl_define_finalizer(void *objspace_ptr, VALUE obj, VALUE block)
27632763

27642764
RBASIC(obj)->flags |= FL_FINALIZE;
27652765

2766-
int lev = RB_GC_VM_LOCK();
2766+
unsigned int lev = RB_GC_VM_LOCK();
27672767

27682768
if (st_lookup(finalizer_table, obj, &data)) {
27692769
table = (VALUE)data;
2770+
VALUE dup_table = rb_ary_dup(table);
27702771

2772+
RB_GC_VM_UNLOCK(lev);
27712773
/* avoid duplicate block, table is usually small */
27722774
{
27732775
long len = RARRAY_LEN(table);
27742776
long i;
27752777

27762778
for (i = 0; i < len; i++) {
2777-
VALUE recv = RARRAY_AREF(table, i);
2778-
if (rb_equal(recv, block)) {
2779-
RB_GC_VM_UNLOCK(lev);
2779+
VALUE recv = RARRAY_AREF(dup_table, i);
2780+
if (rb_equal(recv, block)) { // can't be called with VM lock held
27802781
return recv;
27812782
}
27822783
}
27832784
}
2785+
lev = RB_GC_VM_LOCK();
2786+
RB_GC_GUARD(dup_table);
27842787

27852788
rb_ary_push(table, block);
27862789
}
@@ -2841,8 +2844,8 @@ get_final(long i, void *data)
28412844
return RARRAY_AREF(table, i + 1);
28422845
}
28432846

2844-
static void
2845-
run_final(rb_objspace_t *objspace, VALUE zombie)
2847+
static unsigned int
2848+
run_final(rb_objspace_t *objspace, VALUE zombie, unsigned int lev)
28462849
{
28472850
if (RZOMBIE(zombie)->dfree) {
28482851
RZOMBIE(zombie)->dfree(RZOMBIE(zombie)->data);
@@ -2853,7 +2856,9 @@ run_final(rb_objspace_t *objspace, VALUE zombie)
28532856
FL_UNSET(zombie, FL_FINALIZE);
28542857
st_data_t table;
28552858
if (st_delete(finalizer_table, &key, &table)) {
2859+
RB_GC_VM_UNLOCK(lev);
28562860
rb_gc_run_obj_finalizer(RARRAY_AREF(table, 0), RARRAY_LEN(table) - 1, get_final, (void *)table);
2861+
lev = RB_GC_VM_LOCK();
28572862
}
28582863
else {
28592864
rb_bug("FL_FINALIZE flag is set, but finalizers are not found");
@@ -2862,6 +2867,7 @@ run_final(rb_objspace_t *objspace, VALUE zombie)
28622867
else {
28632868
GC_ASSERT(!st_lookup(finalizer_table, key, NULL));
28642869
}
2870+
return lev;
28652871
}
28662872

28672873
static void
@@ -2874,9 +2880,9 @@ finalize_list(rb_objspace_t *objspace, VALUE zombie)
28742880
next_zombie = RZOMBIE(zombie)->next;
28752881
page = GET_HEAP_PAGE(zombie);
28762882

2877-
int lev = RB_GC_VM_LOCK();
2883+
unsigned int lev = RB_GC_VM_LOCK();
28782884

2879-
run_final(objspace, zombie);
2885+
lev = run_final(objspace, zombie, lev);
28802886
{
28812887
GC_ASSERT(BUILTIN_TYPE(zombie) == T_ZOMBIE);
28822888
GC_ASSERT(page->heap->final_slots_count > 0);

test/ruby/test_gc.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,4 +914,25 @@ def test_old_to_young_reference
914914
assert_include ObjectSpace.dump(young_obj), '"old":true'
915915
end
916916
end
917+
918+
def test_finalizer_not_run_with_vm_lock
919+
assert_ractor(<<~'RUBY')
920+
Thread.new do
921+
loop do
922+
Encoding.list.each do |enc|
923+
enc.names
924+
end
925+
end
926+
end
927+
928+
o = Object.new
929+
ObjectSpace.define_finalizer(o, proc do
930+
sleep 0.5 # finalizer shouldn't be run with VM lock, otherwise this context switch will crash
931+
end)
932+
o = nil
933+
4.times do
934+
GC.start
935+
end
936+
RUBY
937+
end
917938
end

0 commit comments

Comments
 (0)