From 818f1c65ab6534acdbe539616843cfec3ac794fa Mon Sep 17 00:00:00 2001
From: normal <normal@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date: Wed, 5 Dec 2018 18:58:45 +0000
Subject: [PATCH] thread_sync.c (mutex_ptr): handle mutexes held by parent
 threads in children

Mutexes may be held by threads which only exist in the parent
process, so their waitqueues may be populated with references
to other dead threads.  We must reset them at fork.

I am a moron for introducing this bug :<

[ruby-core:90312] [Bug #15383]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66230 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
---
 test/ruby/test_thread.rb | 21 +++++++++++++++++++++
 thread_sync.c            | 11 +++++++++++
 2 files changed, 32 insertions(+)

diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb
index 2b120b4a17..78d6d82e23 100644
--- a/test/ruby/test_thread.rb
+++ b/test/ruby/test_thread.rb
@@ -1239,6 +1239,27 @@ q.pop
     end
   end if Process.respond_to?(:fork)
 
+  def test_fork_while_parent_locked
+    skip 'needs fork' unless Process.respond_to?(:fork)
+    m = Thread::Mutex.new
+    failures = 0
+    run = true
+    thrs = 50.times.map do
+      Thread.new do
+        while run
+          pid = fork { m.synchronize {} }
+          m.synchronize {}
+          _, st = Process.waitpid2(pid)
+          m.synchronize { failures += 1 } unless st.success?
+        end
+      end
+    end
+    sleep 0.5
+    run = false
+    thrs.each(&:join)
+    assert_equal 0, failures, '[ruby-core:90312] [Bug #15383]'
+  end
+
   def test_subclass_no_initialize
     t = Module.new do
       break eval("class C\u{30b9 30ec 30c3 30c9} < Thread; self; end")
diff --git a/thread_sync.c b/thread_sync.c
index b79db1fee3..e478a96b88 100644
--- a/thread_sync.c
+++ b/thread_sync.c
@@ -45,6 +45,7 @@ typedef struct rb_mutex_struct {
     rb_thread_t *th;
     struct rb_mutex_struct *next_mutex;
     struct list_head waitq; /* protected by GVL */
+    rb_serial_t fork_gen;
 } rb_mutex_t;
 
 #if defined(HAVE_WORKING_FORK)
@@ -121,8 +122,18 @@ static rb_mutex_t *
 mutex_ptr(VALUE obj)
 {
     rb_mutex_t *mutex;
+    rb_serial_t fork_gen = GET_VM()->fork_gen;
 
     TypedData_Get_Struct(obj, rb_mutex_t, &mutex_data_type, mutex);
+
+    if (mutex->fork_gen != fork_gen) {
+        /* forked children can't reach into parent thread stacks */
+        mutex->fork_gen = fork_gen;
+        list_head_init(&mutex->waitq);
+        mutex->next_mutex = 0;
+        mutex->th = 0;
+    }
+
     return mutex;
 }