@@ -133,6 +133,34 @@ mutex_free(void *ptr)
133133{
134134 rb_mutex_t * mutex = ptr ;
135135 if (mutex_locked_p (mutex )) {
136+ /* A *locked* mutex is being swept. Its owner (mutex->th) must be a
137+ * live thread that is keeping it in keeping_mutexes. Validate the
138+ * owner against the VM's authoritative live-thread set BEFORE
139+ * dereferencing it, so a dangling/garbage owner pointer (heap
140+ * corruption or a freed owner) is caught here with a backtrace
141+ * instead of segfaulting deeper inside thread_mutex_remove(). */
142+ rb_vm_t * vm = GET_VM ();
143+ rb_ractor_t * r ;
144+ rb_thread_t * i ;
145+ int owner_is_live = 0 ;
146+ ccan_list_for_each (& vm -> ractor .set , r , vmlr_node ) {
147+ ccan_list_for_each (& r -> threads .set , i , lt_node ) {
148+ if (i == mutex -> th ) { owner_is_live = 1 ; break ; }
149+ }
150+ if (owner_is_live ) break ;
151+ }
152+ if (!owner_is_live ) {
153+ /* Is the VM tearing down? The main thread is set THREAD_KILLED at
154+ * the very start of rb_ec_cleanup (eval.c) and never reverted, so
155+ * this is a monotonic "shutdown in progress" signal readable from
156+ * any thread without touching mutex->th. */
157+ int in_shutdown = (vm -> ractor .main_thread -> status == THREAD_KILLED );
158+ rb_bug ("mutex_free: locked mutex=%p has dangling owner th=%p "
159+ "(ec_serial=%llu) not in the live-thread set (%s)" ,
160+ (void * )mutex , (void * )mutex -> th ,
161+ (unsigned long long )mutex -> ec_serial ,
162+ in_shutdown ? "VM shutdown in progress" : "not shutdown" );
163+ }
136164 thread_mutex_remove (mutex -> th , mutex );
137165 }
138166 ruby_xfree (ptr );
@@ -479,6 +507,7 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial)
479507 struct sync_waiter * cur = 0 , * next ;
480508
481509 mutex -> ec_serial = 0 ;
510+ mutex -> th = 0 ;
482511 thread_mutex_remove (th , mutex );
483512
484513 ccan_list_for_each_safe (& mutex -> waitq , cur , next , node ) {
0 commit comments