Ruby Issue Tracking System: Issueshttps://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112015-02-19T21:53:23ZRuby Issue Tracking System
Redmine Ruby master - Bug #10871 (Closed): Sclass thread unsafe due to CREF sharinghttps://redmine.ruby-lang.org/issues/108712015-02-19T21:53:23Zevanphx (Evan Phoenix)evan@phx.io
<p>When entering an sclass, the context is tracked via the same cref mechanism used for class and module, specifically on the iseq->cref_stack. The bug is that the cref_stack is the wrong place to put the new cref because the scope is specific only to that sclass body. Mutating and using the iseq->cref_stack causes any code that reads the cref via this cref_stack to incorrectly pick up the sclass instance instead of the proper scope!</p>
<p>This is major thread safety bug because it means that all uses of <code>class << obj</code> are thread-unsafe and can cause random code to fail.</p>
<p>Here is a simple reproduction of the bug: <a href="https://gist.github.com/evanphx/6eef92f2c40662a4171b" class="external">https://gist.github.com/evanphx/6eef92f2c40662a4171b</a></p>
<p>I attempted to fix the bug by treating an sclass body the same as an eval, which already has special handling for cref's but I don't understand the code enough to make that change quickly.</p>
<p>I believe this is a major bug and hope that ruby-core can address it soon.</p>
<p>Thank you!</p> Ruby master - Bug #5754 (Closed): Double require bug in 1.9.3https://redmine.ruby-lang.org/issues/57542011-12-13T07:54:49Zevanphx (Evan Phoenix)evan@phx.io
<p>There appears a bug in the handling of concurrent requires of the same file. If a thread (t2) is waiting for another thread (t1) to finish requiring the file, but requiring the file raises an exception, t1 signals t2 and t2 tries to require the file itself. But due to how the loading table is managed, if t1 attempts to require the file again right away (or if another thread were to), it doesn't see an entry in the loading table even though t2 is currently loading the file. This causes t1 to begin requiring the file again or sometimes it raises a ThreadError instead. Here is the code I write for rubyspec to show the bug:</p>
<pre><code> start = false
fin = false
$scratch = []
$con1_ready = false
$con1_raise = true
path = "path/to/content/below.rb"
t1_res = nil
t2_res = nil
t1 = Thread.new do
lambda {
require(path)
}.should raise_error(RuntimeError)
# This hits the bug. Because MRI removes it's internal lock from a table
# when the exception is raised, this #require doesn't see that t2 is
# in the middle of requiring the file, so this #require runs when it should
# not.
#
# Sometimes this raises a ThreadError also, but I'm not sure why.
t1_res = require(path)
Thread.pass until fin
$scratch << :t1_post
end
t2 = Thread.new do
Thread.pass until $con1_ready
begin
t2_res = require(path)
$scratch << :t2_post
ensure
fin = true
end
end
t1.join
t2.join
raise "bug" unless t1_res == false
raise "bug" unless t2_res == true
$scratch == [:con_pre, :con_pre, :con_post, :t2_post, :t1_post]
</code></pre>
<p>Here is the file being required:</p>
<p>$scratch << :con_pre<br>
$con1_ready = true<br>
sleep 0.5<br>
if $con1_raise<br>
$con1_raise = false<br>
raise "con1"<br>
end<br>
sleep 0.5<br>
$scratch << :con_post</p>