https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112020-04-16T09:02:07ZRuby Issue Tracking SystemRuby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851282020-04-16T09:02:07ZEregon (Benoit Daloze)
<ul><li><strong>Tracker</strong> changed from <i>Bug</i> to <i>Feature</i></li><li><strong>Backport</strong> deleted (<del><i>2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN</i></del>)</li></ul> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851302020-04-16T09:02:19ZEregon (Benoit Daloze)
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-5 priority-4 priority-default closed" href="/issues/16786">Feature #16786</a>: Light-weight scheduler for improved concurrency.</i> added</li></ul> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851332020-04-16T09:18:26ZEregon (Benoit Daloze)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/85133/diff?detail_id=56872">diff</a>)</li></ul> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851342020-04-16T09:19:29ZEregon (Benoit Daloze)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/85134/diff?detail_id=56873">diff</a>)</li></ul> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851352020-04-16T09:24:21Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>Characterising this as trivial hides the impact of this both on the scheduler design and application code which expects per-thread mutex.</p>
<p>Once the scheduler lands into master, we will need to scope out something like <code>wait_mutex</code> and all the associated scheduling that needs to happen.</p>
<p>Can you check the Crystal implementation? How do they implement fair scheduling?</p>
<p>Finally, as this breaks assumptions about application code, I proposed <code>Mutex.new(blocking: false/true)</code>. I'm on the fence regarding this interface, but at least it should be clear that such a change has a track record of breaking application code. So if we decide to make the mutex per-fibre, we need to anticipate this problem, either through analysis of existing source code, issuing warnings, or something else.</p>
<p>Ultimately, I think keeping it simple would be great. But I'm hesitant to do so since it may break existing code. <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/286">@headius (Charles Nutter)</a> maybe you can comment on what user code was affected so we can see if there is some other way to mitigate it.</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851362020-04-16T09:32:41ZEregon (Benoit Daloze)
<ul></ul><blockquote>
<p>Application code which expects per-thread mutex</p>
</blockquote>
<p>I'm happy to see examples of that.<br>
I've never seen it in 5+ more years of bug reports on TruffleRuby, and JRuby AFAIK always had Mutex per Fiber and yet no real issue because of it.<br>
The only cases that would rely on this seems broken synchronization<br>
(e.g., taking the lock in one Fiber and unlocking in another Fiber, which I believe is always a bug, as lock/unlock needs to be used like <code>lock; begin; ...; ensure; unlock; end</code>, which is always on the same stack so on the same Fiber).</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851372020-04-16T09:37:00ZEregon (Benoit Daloze)
<ul></ul><p>ioquatix (Samuel Williams) wrote in <a href="#note-5">#note-5</a>:</p>
<blockquote>
<p>Can you check the Crystal implementation? How do they implement fair scheduling?</p>
</blockquote>
<p>It's linked in the description.<br>
Mutex is already not fair so I don't think it matters.</p>
<blockquote>
<p>Finally, as this breaks assumptions about user code, I proposed <code>Mutex.new(blocking: false/true)</code>.</p>
</blockquote>
<p>It doesn't.</p>
<blockquote>
<p>I'm on the fence regarding this interface, but at least it should be clear that such a change has a track record of breaking application code.</p>
</blockquote>
<p>Any example? Please don't claim such things if there is no example.</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851382020-04-16T09:57:28Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><blockquote>
<p>Any example? Please don't claim such things if there is no example.</p>
</blockquote>
<p>I'll have to defer to <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/286">@headius (Charles Nutter)</a> for examples, since he was the one that mentioned it. I can only assume he is telling the truth when he said some application code was broken.</p>
<p>The only reason to look at it is to check if it's a valid use case or not. If we can't find any valid use case, it confirms your assessment, which is a good thing.</p>
<blockquote>
<p>It doesn't.</p>
</blockquote>
<p>I did play around with this a bit, and I also wondered if we should add it to Ruby spec before making changes.</p>
<p><a href="https://github.com/ruby/ruby/pull/3032/files#diff-1a99f5621b805d95b67228708b652ff9R25" class="external">https://github.com/ruby/ruby/pull/3032/files#diff-1a99f5621b805d95b67228708b652ff9R25</a></p>
<p>That is the current semantic and if we introduce non-blocking fiber, it can cause deadlock, as shown in the test, if you disable the mutex -> blocking relationship.</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=851392020-04-16T10:26:28Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>My initial reply might have come across as overly critical and that was not my intention.</p>
<p>I want to say, I agree with this proposal, and I think it's a good idea.</p>
<p>I would like to see that we merge the scheduler proposal first, which preserves the current semantics of Mutex. Then, shortly afterwards and before Ruby 3 is released, we should implement this.</p>
<p>One more point:</p>
<blockquote>
<p>will hurt scalability of that proposal significantly.</p>
</blockquote>
<p>There is no evidence to suggest this. I do agree it <em>can</em> hurt the scalability of a system but it's entirely possible to build such a system without a Mutex.</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=852182020-04-21T03:10:11Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>In order to experiment with this, I'd like to propose the following hooks to the scheduler:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Scheduler</span>
<span class="c1"># A fiber has tried to lock a mutex, but it failed.</span>
<span class="k">def</span> <span class="nf">wait_mutex</span><span class="p">(</span><span class="n">mutex</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># A mutex which has previously called `wait_mutex` may now be available to lock.</span>
<span class="k">def</span> <span class="nf">notify_mutex</span><span class="p">(</span><span class="n">mutex</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a> do you think this is sufficient? I'm a little bit concerned about the reentrancy guarantees of <code>notify_mutex</code>. Should we define that method to be reentrant/thread safe? Ideally, we have a way to notify the scheduler to reschedule the fiber to try and lock the mutex again.</p>
<p>cc <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/1604">@jeremyevans0 (Jeremy Evans)</a></p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=855422020-05-12T22:21:08Zko1 (Koichi Sasada)
<ul></ul><p>I think it seems difficult to implement it.<br>
If an interpreter manages everything, it is easy (at least I can image how to implement it).</p>
<p>(1) API</p>
<p>I'm not sure we can implement Mutex scheduling with the hooks introduced at #10.<br>
If there is only one thread, maybe it is easy to implement.</p>
<p>Maybe <code>notify_mutex(mutex)</code> will be called with locked mutex by the interpreter. It means we need to introduce lock_nonblock or similar API for Mutex (maybe <code>_nonblock</code> is not good name because it is different from <code>IO#read_nonblock</code>). <code>Mutex#notice_when_locked</code>?</p>
<p>Also we need to wait this notification with wait for the ready of IO operations. how to write it? Use pipe trick or interrupt mechanism?</p>
<p>(2) Queue/SizedQueue/CV</p>
<p>there are several blocking operations because of the thread synchronization, how to treat them?<br>
Introduce unified hook method like <code>wait_synchronization(sync_object)</code> and <code>notify_synchronization(sync_object)</code>?<br>
Introduce <code>pop_nonblock</code> similar to <code>Mutex#sync_nonblock</code>?</p>
<p>(3) implemented by all schedulers?</p>
<p>maybe most of code are same between scheduler implementations. can we provide a framework to implement it?</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=870842020-08-17T03:17:13Zioquatix (Samuel Williams)samuel@oriontransfer.net
<ul></ul><p>I have played around with this and I see the most basic operation is a way to tell the scheduler that a fiber can make progress. We can use a generic approach - in a loop, you may call <code>Fiber.yield</code>. When that fiber is ready to proceed (e.g. mutex is unlocked), you need to notify scheduler, e.g. either of the following interfaces:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Scheduler</span>
<span class="c1"># Inform the scheduler that the fiber can be resumed. If urgent, the scheduler will wake up as soon as possible.</span>
<span class="c1"># @parameter fiber [Object] Must respond to `#resume`.</span>
<span class="k">def</span> <span class="nf">ready</span><span class="p">(</span><span class="n">fiber</span><span class="p">,</span> <span class="n">urgent</span> <span class="o">=</span> <span class="kp">false</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Execute the block in the run loop. If urgent, the scheduler will wake up as soon as possible.</span>
<span class="k">def</span> <span class="nf">invoke</span><span class="p">(</span><span class="n">urgent</span> <span class="o">=</span> <span class="kp">false</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Here is the proof of concept:</p>
<p><a href="https://github.com/socketry/async/pull/72" class="external">https://github.com/socketry/async/pull/72</a></p>
<p>See the hacks required to <code>async/mutex.rb</code> to make it work. This approach should work for <code>Queue</code>/<code>SizedQueue</code>/<code>ConditionVariable</code>.</p>
<p>The implementation of a blocking operation looks like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">until</span> <span class="p">[</span><span class="n">operation</span> <span class="n">succeeded</span><span class="p">]</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">waiting</span> <span class="o"><<</span> <span class="no">Fiber</span><span class="p">.</span><span class="nf">current</span>
<span class="no">Fiber</span><span class="p">.</span><span class="nf">yield</span>
<span class="k">end</span>
</code></pre>
<p>On the other side, where the resource becomes available:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">if</span> <span class="n">fiber</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">waiting</span><span class="p">.</span><span class="nf">pop</span>
<span class="k">if</span> <span class="n">fiber</span><span class="p">.</span><span class="nf">scheduler</span> <span class="c1"># helper</span>
<span class="n">fiber</span><span class="p">.</span><span class="nf">scheduler</span><span class="p">.</span><span class="nf">ready</span><span class="p">(</span><span class="n">fiber</span><span class="p">)</span>
<span class="k">else</span>
<span class="c1"># existing logic</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>I don't mind how we call the methods. Crystal had <code>reschedule</code>. I'm open to ideas that make sense. <code>ready</code> maybe not so great.</p>
<p>Also, maybe this is crazy idea, but in order to unify the interface, maybe we should introduce <code>Fiber#call</code> as an alias for <code>Fiber#resume</code> (or <code>Fiber#transfer</code>). That way, we could make it valid to substitute a proc for a fiber.</p> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=875542020-09-14T04:44:26ZEregon (Benoit Daloze)
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Closed</i></li></ul><p>Applied in changeset <a class="changeset" title="Make Mutex per-Fiber instead of per-Thread * Enables Mutex to be used as synchronization between..." href="https://bugs.ruby-lang.org/projects/ruby-master/repository/git/revisions/178c1b0922dc727897d81d7cfe9c97d5ffa97fd9">git|178c1b0922dc727897d81d7cfe9c97d5ffa97fd9</a>.</p>
<hr>
<p>Make Mutex per-Fiber instead of per-Thread</p>
<ul>
<li>Enables Mutex to be used as synchronization between multiple Fibers<br>
of the same Thread.</li>
<li>With a Fiber scheduler we can yield to another Fiber on contended<br>
Mutex#lock instead of blocking the entire thread.</li>
<li>This also makes the behavior of Mutex consistent across CRuby, JRuby and TruffleRuby.</li>
<li>[Feature <a class="issue tracker-2 status-5 priority-4 priority-default closed" title="Feature: Make Mutex held per Fiber instead of per Thread (Closed)" href="https://bugs.ruby-lang.org/issues/16792">#16792</a>]</li>
</ul> Ruby master - Feature #16792: Make Mutex held per Fiber instead of per Threadhttps://bugs.ruby-lang.org/issues/16792?journal_id=1002122022-11-22T11:22:13Zbyroot (Jean Boussier)byroot@ruby-lang.org
<ul><li><strong>Related to</strong> <i><a class="issue tracker-2 status-1 priority-4 priority-default" href="/issues/19141">Feature #19141</a>: Add thread-owned Monitor to protect thread-local resources</i> added</li></ul>