Skip to content

Commit 264b2d7

Browse files
committed
Reapply "merge revision(s) 62430c1: [Backport #21342]"
This reverts commit c414b98.
1 parent c414b98 commit 264b2d7

File tree

3 files changed

+120
-16
lines changed

3 files changed

+120
-16
lines changed

test/ruby/test_thread.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,4 +1557,65 @@ def test_pending_interrupt?
15571557
assert_equal(true, t.pending_interrupt?(Exception))
15581558
assert_equal(false, t.pending_interrupt?(ArgumentError))
15591559
end
1560+
1561+
# [Bug #21342]
1562+
def test_unlock_locked_mutex_with_collected_fiber
1563+
bug21127 = '[ruby-core:120930] [Bug #21127]'
1564+
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
1565+
begin;
1566+
5.times do
1567+
m = Mutex.new
1568+
Thread.new do
1569+
m.synchronize do
1570+
end
1571+
end.join
1572+
Fiber.new do
1573+
GC.start
1574+
m.lock
1575+
end.resume
1576+
end
1577+
end;
1578+
end
1579+
1580+
def test_unlock_locked_mutex_with_collected_fiber2
1581+
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
1582+
begin;
1583+
MUTEXES = []
1584+
5.times do
1585+
m = Mutex.new
1586+
Fiber.new do
1587+
GC.start
1588+
m.lock
1589+
end.resume
1590+
MUTEXES << m
1591+
end
1592+
10.times do
1593+
MUTEXES.clear
1594+
GC.start
1595+
end
1596+
end;
1597+
end
1598+
1599+
def test_mutexes_locked_in_fiber_dont_have_aba_issue_with_new_fibers
1600+
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
1601+
begin;
1602+
mutexes = 1000.times.map do
1603+
Mutex.new
1604+
end
1605+
1606+
mutexes.map do |m|
1607+
Fiber.new do
1608+
m.lock
1609+
end.resume
1610+
end
1611+
1612+
GC.start
1613+
1614+
1000.times.map do
1615+
Fiber.new do
1616+
raise "FAILED!" if mutexes.any?(&:owned?)
1617+
end.resume
1618+
end
1619+
end;
1620+
end
15601621
end

thread.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ rb_threadptr_unlock_all_locking_mutexes(rb_thread_t *th)
445445
th->keeping_mutexes = mutex->next_mutex;
446446

447447
// rb_warn("mutex #<%p> was not unlocked by thread #<%p>", (void *)mutex, (void*)th);
448-
448+
VM_ASSERT(mutex->fiber);
449449
const char *error_message = rb_mutex_unlock_th(mutex, th, mutex->fiber);
450450
if (error_message) rb_bug("invalid keeping_mutexes: %s", error_message);
451451
}

thread_sync.c

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ static VALUE rb_eClosedQueueError;
88
/* Mutex */
99
typedef struct rb_mutex_struct {
1010
rb_fiber_t *fiber;
11+
VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free
1112
struct rb_mutex_struct *next_mutex;
1213
struct ccan_list_head waitq; /* protected by GVL */
1314
} rb_mutex_t;
@@ -106,8 +107,6 @@ static const char* rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_fib
106107
*
107108
*/
108109

109-
#define mutex_mark ((void(*)(void*))0)
110-
111110
static size_t
112111
rb_mutex_num_waiting(rb_mutex_t *mutex)
113112
{
@@ -123,13 +122,39 @@ rb_mutex_num_waiting(rb_mutex_t *mutex)
123122

124123
rb_thread_t* rb_fiber_threadptr(const rb_fiber_t *fiber);
125124

125+
static bool
126+
locked_p(rb_mutex_t *mutex)
127+
{
128+
return mutex->fiber != 0;
129+
}
130+
131+
static void
132+
mutex_mark(void *ptr)
133+
{
134+
rb_mutex_t *mutex = ptr;
135+
VALUE fiber;
136+
if (locked_p(mutex)) {
137+
fiber = rb_fiberptr_self(mutex->fiber); // rb_fiber_t* doesn't move along with fiber object
138+
if (fiber) rb_gc_mark_movable(fiber);
139+
rb_gc_mark_movable(mutex->thread);
140+
}
141+
}
142+
143+
static void
144+
mutex_compact(void *ptr)
145+
{
146+
rb_mutex_t *mutex = ptr;
147+
if (locked_p(mutex)) {
148+
mutex->thread = rb_gc_location(mutex->thread);
149+
}
150+
}
151+
126152
static void
127153
mutex_free(void *ptr)
128154
{
129155
rb_mutex_t *mutex = ptr;
130-
if (mutex->fiber) {
131-
/* rb_warn("free locked mutex"); */
132-
const char *err = rb_mutex_unlock_th(mutex, rb_fiber_threadptr(mutex->fiber), mutex->fiber);
156+
if (locked_p(mutex)) {
157+
const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), mutex->fiber);
133158
if (err) rb_bug("%s", err);
134159
}
135160
ruby_xfree(ptr);
@@ -143,7 +168,7 @@ mutex_memsize(const void *ptr)
143168

144169
static const rb_data_type_t mutex_data_type = {
145170
"mutex",
146-
{mutex_mark, mutex_free, mutex_memsize,},
171+
{mutex_mark, mutex_free, mutex_memsize, mutex_compact,},
147172
0, 0, RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
148173
};
149174

@@ -204,12 +229,13 @@ rb_mutex_locked_p(VALUE self)
204229
{
205230
rb_mutex_t *mutex = mutex_ptr(self);
206231

207-
return RBOOL(mutex->fiber);
232+
return RBOOL(locked_p(mutex));
208233
}
209234

210235
static void
211236
thread_mutex_insert(rb_thread_t *thread, rb_mutex_t *mutex)
212237
{
238+
RUBY_ASSERT(!mutex->next_mutex);
213239
if (thread->keeping_mutexes) {
214240
mutex->next_mutex = thread->keeping_mutexes;
215241
}
@@ -234,10 +260,24 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex)
234260
}
235261

236262
static void
237-
mutex_locked(rb_thread_t *th, VALUE self)
263+
mutex_set_owner(VALUE self, rb_thread_t *th, rb_fiber_t *fiber)
264+
{
265+
rb_mutex_t *mutex = mutex_ptr(self);
266+
267+
mutex->thread = th->self;
268+
mutex->fiber = fiber;
269+
RB_OBJ_WRITTEN(self, Qundef, th->self);
270+
if (fiber) {
271+
RB_OBJ_WRITTEN(self, Qundef, rb_fiberptr_self(fiber));
272+
}
273+
}
274+
275+
static void
276+
mutex_locked(rb_thread_t *th, rb_fiber_t *fiber, VALUE self)
238277
{
239278
rb_mutex_t *mutex = mutex_ptr(self);
240279

280+
mutex_set_owner(self, th, fiber);
241281
thread_mutex_insert(th, mutex);
242282
}
243283

@@ -258,9 +298,8 @@ rb_mutex_trylock(VALUE self)
258298

259299
rb_fiber_t *fiber = GET_EC()->fiber_ptr;
260300
rb_thread_t *th = GET_THREAD();
261-
mutex->fiber = fiber;
262301

263-
mutex_locked(th, self);
302+
mutex_locked(th, fiber, self);
264303
return Qtrue;
265304
}
266305
else {
@@ -328,7 +367,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
328367
rb_ensure(call_rb_fiber_scheduler_block, self, delete_from_waitq, (VALUE)&sync_waiter);
329368

330369
if (!mutex->fiber) {
331-
mutex->fiber = fiber;
370+
mutex_set_owner(self, th, fiber);
332371
}
333372
}
334373
else {
@@ -358,6 +397,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
358397
rb_ractor_sleeper_threads_inc(th->ractor);
359398
rb_check_deadlock(th->ractor);
360399

400+
RUBY_ASSERT(!th->locking_mutex);
361401
th->locking_mutex = self;
362402

363403
ccan_list_add_tail(&mutex->waitq, &sync_waiter.node);
@@ -368,7 +408,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
368408

369409
// unlocked by another thread while sleeping
370410
if (!mutex->fiber) {
371-
mutex->fiber = fiber;
411+
mutex_set_owner(self, th, fiber);
372412
}
373413

374414
rb_ractor_sleeper_threads_dec(th->ractor);
@@ -382,10 +422,13 @@ do_mutex_lock(VALUE self, int interruptible_p)
382422
if (interruptible_p) {
383423
/* release mutex before checking for interrupts...as interrupt checking
384424
* code might call rb_raise() */
385-
if (mutex->fiber == fiber) mutex->fiber = 0;
425+
if (mutex->fiber == fiber) {
426+
mutex->thread = Qfalse;
427+
mutex->fiber = NULL;
428+
}
386429
RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */
387430
if (!mutex->fiber) {
388-
mutex->fiber = fiber;
431+
mutex_set_owner(self, th, fiber);
389432
}
390433
}
391434
else {
@@ -404,7 +447,7 @@ do_mutex_lock(VALUE self, int interruptible_p)
404447
}
405448

406449
if (saved_ints) th->ec->interrupt_flag = saved_ints;
407-
if (mutex->fiber == fiber) mutex_locked(th, self);
450+
if (mutex->fiber == fiber) mutex_locked(th, fiber, self);
408451
}
409452

410453
RUBY_DEBUG_LOG("%p locked", mutex);

0 commit comments

Comments
 (0)