Ruby Issue Tracking System: Issueshttps://redmine.ruby-lang.org/https://redmine.ruby-lang.org/favicon.ico?17113305112024-02-26T00:34:27ZRuby Issue Tracking System
Redmine Ruby master - Feature #20298 (Open): Introduce `Time()` type-cast / constructor.https://redmine.ruby-lang.org/issues/202982024-02-26T00:34:27Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>Many Ruby primitive types have constructors, e.g. <code>Integer(...)</code>, <code>String(...)</code>, <code>Float(...)</code>, etc. These usually convert from some subset of types, e.g. <code>Float(1) -> 1.0</code> will convert an Integer to a Float, and <code>Float("1") -> 1.0</code> will parse a String to a Float, and similar for other type casts/constructors.</p>
<p>I'd like to propose we introduce something similar for <code>Time</code> (and possibly this extends to <code>Date</code>/<code>DateTime</code> in a follow up proposal).</p>
<p>Suggested implementation could look something like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">Time</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">case</span> <span class="n">value</span>
<span class="k">when</span> <span class="no">Time</span>
<span class="n">value</span>
<span class="k">when</span> <span class="no">Integer</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">when</span> <span class="no">String</span> <span class="c1"># The format is assumed to be the result of `Time#to_s`.</span>
<span class="no">Time</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">else</span>
<span class="n">value</span><span class="p">.</span><span class="nf">to_time</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Alternatively, we might like to be a little more specific with the <code>else</code> clause/error handling.</p>
<a name="Background"></a>
<h2 >Background<a href="#Background" class="wiki-anchor">¶</a></h2>
<p>In a project, I need to support multiple serialization formats/coders, including MessagePack, Marshal and JSON.</p>
<p>While Marshal and MessagePack are capable of serializing <code>Time</code> instances, <code>JSON</code> is not.</p>
<p>The code looks a bit like this:</p>
<pre><code>data = fetch_job_data
job = @coder.load(data)
scheduled_at = Time(job[:scheduled_at]) # Hypothetical type-cast as outlined above
</code></pre>
<a name="Additional-Observations"></a>
<h2 >Additional Observations<a href="#Additional-Observations" class="wiki-anchor">¶</a></h2>
<p>While some primitive data types accept themselves as arguments and construct by copy, e.g. <code>Array.new(Array.new)</code>, others do not, e.g. <code>Hash</code> and <code>Time</code>. Perhaps <code>Time.new(Time.new)</code> should behave similarly to <code>Array.new(Array.new)</code> - the outer instance is a copy of the inner.</p> Ruby master - Feature #20282 (Open): Enhancing Ruby's Coverage with Per-Test Coverage Reportshttps://redmine.ruby-lang.org/issues/202822024-02-19T22:51:10Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>As Ruby applications grow in complexity, the need for more sophisticated testing and coverage analysis tools becomes paramount. Current coverage tools in Ruby offer a good starting point but fall short in delivering the granularity and flexibility required by modern development practices. Specifically, there is a significant gap in "per-test coverage" reporting, which limits developers' ability to pinpoint exactly which tests exercise which lines of code. This proposal seeks to initiate a discussion around improving Ruby's coverage module to address this gap.</p>
<a name="Objectives"></a>
<h2 >Objectives<a href="#Objectives" class="wiki-anchor">¶</a></h2>
<p>The primary goal of this initiative is to introduce support for per-test coverage reports within Ruby, focusing on three key areas:</p>
<ol>
<li>
<p>Scoped Coverage Data Capture: Implementing the capability to capture coverage data within user-defined scopes, such as global, thread, or fiber scopes. This would allow for more granular control over the coverage analysis process.</p>
</li>
<li>
<p>Efficient Data Capture Controls: Developing mechanisms to efficiently control the capture of coverage data. This includes the ability to exclude specific files, include/ignore/merge eval'd code, to ensure that the coverage data is both relevant and manageable.</p>
</li>
<li>
<p>Compatibility and Consistency: Ensuring that the coverage data is exposed in a manner that is consistent with existing coverage tools and standards. This compatibility is crucial for integrating with a wide array of tooling and for facilitating a seamless developer experience.</p>
</li>
</ol>
<a name="Proposed-Solutions"></a>
<h2 >Proposed Solutions<a href="#Proposed-Solutions" class="wiki-anchor">¶</a></h2>
<p>The heart of this proposal lies in the introduction of a new subclassable component within the Coverage module, tentatively named <code>Coverage::Capture</code>. This component would allow users to define custom coverage capture behaviors tailored to their specific needs. Below is a hypothetical interface for such a mechanism:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Coverage::Capture</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">start</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">new</span><span class="p">.</span><span class="nf">tap</span><span class="p">(</span><span class="o">&</span><span class="ss">:start</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Start receiving coverage callbacks.</span>
<span class="k">def</span> <span class="nf">start</span>
<span class="k">end</span>
<span class="c1"># Stop receiving coverage callbacks.</span>
<span class="k">def</span> <span class="nf">stop</span>
<span class="k">end</span>
<span class="c1"># User-overridable statement coverage callback.</span>
<span class="k">def</span> <span class="nf">statement</span><span class="p">(</span><span class="n">iseq</span><span class="p">,</span> <span class="n">location</span><span class="p">)</span>
<span class="n">fetch</span><span class="p">(</span><span class="n">iseq</span><span class="p">)</span><span class="o">&</span><span class="p">.</span><span class="nf">statement_coverage</span><span class="p">.</span><span class="nf">increment</span><span class="p">(</span><span class="n">location</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Additional methods for branch/declaration coverage would follow a similar pattern.</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">MyCoverageCapture</span> <span class="o"><</span> <span class="no">Coverage</span><span class="o">::</span><span class="no">Capture</span>
<span class="c1"># Provides efficient data capture controls - can return nil if skipping coverage for this iseq, or can store coverage data per-thread, per-fiber, etc.</span>
<span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">iseq</span><span class="p">)</span>
<span class="vi">@coverage</span><span class="p">[</span><span class="n">iseq</span><span class="p">]</span> <span class="o">||=</span> <span class="no">Coverage</span><span class="p">.</span><span class="nf">default_coverage</span><span class="p">(</span><span class="n">iseq</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Usage example:</span>
<span class="n">my_coverage_capture</span> <span class="o">=</span> <span class="no">MyCoverageCapture</span><span class="p">.</span><span class="nf">start</span>
<span class="c1"># Execute test suite or specific tests</span>
<span class="n">my_coverage_capture</span><span class="p">.</span><span class="nf">stop</span>
<span class="c1"># Access detailed coverage data</span>
<span class="nb">puts</span> <span class="n">my_coverage_capture</span><span class="p">.</span><span class="nf">coverage</span><span class="p">.</span><span class="nf">statement_coverage</span>
</code></pre>
<p>In addition, we'd need a well defined interface for <code>Coverage.default_coverage</code>, which includes line, branch and declaration coverage statistics. I suggest we take inspiration from the proposed interface defined by the vscode text editor: <a href="https://github.com/microsoft/vscode/blob/b44593a612337289c079425a5b2cc7010216eef4/src/vscode-dts/vscode.proposed.testCoverage.d.ts" class="external">https://github.com/microsoft/vscode/blob/b44593a612337289c079425a5b2cc7010216eef4/src/vscode-dts/vscode.proposed.testCoverage.d.ts</a> - this interface was designed to be compatible with a wide range of coverage libraries, so represents the intersection of that functionality.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Hypothetical interface (mostly copied from vscode's proposed interface):</span>
<span class="k">module</span> <span class="nn">Coverage</span>
<span class="c1"># Contains coverage metadata for a file</span>
<span class="k">class</span> <span class="nc">Target</span>
<span class="nb">attr_reader</span> <span class="ss">:instruction_sequence</span>
<span class="nb">attr_accessor</span> <span class="ss">:statement_coverage</span><span class="p">,</span> <span class="ss">:branch_coverage</span><span class="p">,</span> <span class="ss">:declaration_coverage</span><span class="p">,</span> <span class="ss">:detailed_coverage</span>
<span class="c1"># @param statement_coverage [Hash(Location, StatementCoverage)] A hash table of statement coverage instances keyed on location.</span>
<span class="c1"># Similar structures for other coverage data.</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">instruction_sequence</span><span class="p">,</span> <span class="n">statement_coverage</span><span class="p">,</span> <span class="n">branch_coverage</span><span class="o">=</span><span class="kp">nil</span><span class="p">,</span> <span class="n">declaration_coverage</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="vi">@instruction_sequence</span> <span class="o">=</span> <span class="n">instruction_sequence</span>
<span class="vi">@statement_coverage</span> <span class="o">=</span> <span class="n">statement_coverage</span>
<span class="vi">@branch_coverage</span> <span class="o">=</span> <span class="n">branch_coverage</span>
<span class="vi">@declaration_coverage</span> <span class="o">=</span> <span class="n">declaration_coverage</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Coverage information for a single statement or line.</span>
<span class="k">class</span> <span class="nc">StatementCoverage</span>
<span class="c1"># The number of times this statement was executed, or a boolean indicating</span>
<span class="c1"># whether it was executed if the exact count is unknown. If zero or false,</span>
<span class="c1"># the statement will be marked as un-covered.</span>
<span class="nb">attr_accessor</span> <span class="ss">:executed</span>
<span class="c1"># Statement location (line number? or range? or position? AST?)</span>
<span class="nb">attr_accessor</span> <span class="ss">:location</span>
<span class="c1"># Coverage from branches of this line or statement. If it's not a</span>
<span class="c1"># conditional, this will be empty.</span>
<span class="nb">attr_accessor</span> <span class="ss">:branches</span>
<span class="c1"># Initializes a new instance of the StatementCoverage class.</span>
<span class="c1">#</span>
<span class="c1"># @parameter executed [Number, Boolean] The number of times this statement was executed, or a</span>
<span class="c1"># boolean indicating whether it was executed if the exact count is unknown. If zero or false,</span>
<span class="c1"># the statement will be marked as un-covered.</span>
<span class="c1">#</span>
<span class="c1"># @parameter location [Position, Range] The statement position.</span>
<span class="c1">#</span>
<span class="c1"># @parameter branches [Array(BranchCoverage)] Coverage from branches of this line.</span>
<span class="c1"># If it's not a conditional, this should be omitted.</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">executed</span><span class="p">,</span> <span class="n">location</span><span class="p">,</span> <span class="n">branches</span><span class="o">=</span><span class="p">[])</span>
<span class="vi">@executed</span> <span class="o">=</span> <span class="n">executed</span>
<span class="vi">@location</span> <span class="o">=</span> <span class="n">location</span>
<span class="vi">@branches</span> <span class="o">=</span> <span class="n">branches</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Coverage information for a branch</span>
<span class="k">class</span> <span class="nc">BranchCoverage</span>
<span class="c1"># The number of times this branch was executed, or a boolean indicating</span>
<span class="c1"># whether it was executed if the exact count is unknown. If zero or false,</span>
<span class="c1"># the branch will be marked as un-covered.</span>
<span class="nb">attr_accessor</span> <span class="ss">:executed</span>
<span class="c1"># Branch location.</span>
<span class="nb">attr_accessor</span> <span class="ss">:location</span>
<span class="c1"># Label for the branch, used in the context of "the ${label} branch was</span>
<span class="c1"># not taken," for example.</span>
<span class="nb">attr_accessor</span> <span class="ss">:label</span>
<span class="c1"># Initializes a new instance of the BranchCoverage class.</span>
<span class="c1">#</span>
<span class="c1"># @param executed [Number, Boolean] The number of times this branch was executed, or a</span>
<span class="c1"># boolean indicating whether it was executed if the exact count is unknown. If zero or false,</span>
<span class="c1"># the branch will be marked as un-covered.</span>
<span class="c1">#</span>
<span class="c1"># @param location [Position, Range] (optional) The branch position.</span>
<span class="c1">#</span>
<span class="c1"># @param label [String] (optional) Label for the branch, used in the context of</span>
<span class="c1"># "the ${label} branch was not taken," for example.</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">executed</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="kp">nil</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="vi">@executed</span> <span class="o">=</span> <span class="n">executed</span>
<span class="vi">@location</span> <span class="o">=</span> <span class="n">location</span>
<span class="vi">@label</span> <span class="o">=</span> <span class="n">label</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Coverage information for a declaration</span>
<span class="k">class</span> <span class="nc">DeclarationCoverage</span>
<span class="c1"># Name of the declaration. Depending on the reporter and language, this</span>
<span class="c1"># may be types such as functions, methods, or namespaces.</span>
<span class="nb">attr_accessor</span> <span class="ss">:name</span>
<span class="c1"># The number of times this declaration was executed, or a boolean</span>
<span class="c1"># indicating whether it was executed if the exact count is unknown. If</span>
<span class="c1"># zero or false, the declaration will be marked as un-covered.</span>
<span class="nb">attr_accessor</span> <span class="ss">:executed</span>
<span class="c1"># Declaration location.</span>
<span class="nb">attr_accessor</span> <span class="ss">:location</span>
<span class="c1"># Initializes a new instance of the DeclarationCoverage class.</span>
<span class="c1">#</span>
<span class="c1"># @param name [String] Name of the declaration.</span>
<span class="c1">#</span>
<span class="c1"># @param executed [Number, Boolean] The number of times this declaration was executed, or a</span>
<span class="c1"># boolean indicating whether it was executed if the exact count is unknown. If zero or false,</span>
<span class="c1"># the declaration will be marked as un-covered.</span>
<span class="c1">#</span>
<span class="c1"># @param location [Position, Range] The declaration position.</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">executed</span><span class="p">,</span> <span class="n">location</span><span class="p">)</span>
<span class="vi">@name</span> <span class="o">=</span> <span class="nb">name</span>
<span class="vi">@executed</span> <span class="o">=</span> <span class="n">executed</span>
<span class="vi">@location</span> <span class="o">=</span> <span class="n">location</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>By following this format, we will be compatible with a wide range of external tools.</p> Ruby master - Feature #20215 (Open): Introduce `IO#readable?`https://redmine.ruby-lang.org/issues/202152024-01-26T05:19:16Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>There are some cases where, as an optimisation, it's useful to know whether more data is potentially available.</p>
<p>We already have <code>IO#eof?</code> but the problem with using <code>IO#eof?</code> is that it can block indefinitely for sockets.</p>
<p>Therefore, code which uses <code>IO#eof?</code> to determine if there is potentially more data, may hang.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">make_request</span><span class="p">(</span><span class="n">path</span> <span class="o">=</span> <span class="s2">"/"</span><span class="p">)</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">connect_remote_host</span>
<span class="c1"># HTTP/1.0 request:</span>
<span class="n">client</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="s2">"GET </span><span class="si">#{</span><span class="n">path</span><span class="si">}</span><span class="s2"> HTTP/1.0</span><span class="se">\r\n\r\n</span><span class="s2">"</span><span class="p">)</span>
<span class="c1"># Read response</span>
<span class="n">client</span><span class="p">.</span><span class="nf">gets</span><span class="p">(</span><span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">)</span> <span class="c1"># => "HTTP/1.0 200 OK\r\n"</span>
<span class="c1"># Assuming connection close, there are two things the server can do:</span>
<span class="c1"># 1. peer.close</span>
<span class="c1"># 2. peer.write(...); peer.close</span>
<span class="k">if</span> <span class="n">client</span><span class="p">.</span><span class="nf">eof?</span> <span class="c1"># <--- Can hang here!</span>
<span class="nb">puts</span> <span class="s2">"Connection closed"</span>
<span class="c1"># Avoid yielding as we know there definitely won't be any data.</span>
<span class="k">else</span>
<span class="nb">puts</span> <span class="s2">"Connection open, data may be available..."</span>
<span class="c1"># There might be data available, so yield.</span>
<span class="k">yield</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">ensure</span>
<span class="n">client</span><span class="o">&</span><span class="p">.</span><span class="nf">close</span>
<span class="k">end</span>
<span class="n">make_request</span> <span class="k">do</span> <span class="o">|</span><span class="n">client</span><span class="o">|</span>
<span class="nb">puts</span> <span class="n">client</span><span class="p">.</span><span class="nf">read</span> <span class="c1"># <--- Prefer to wait here.</span>
<span class="k">end</span>
</code></pre>
<p>The proposed <code>IO#readable?</code> is similar to <code>IO#eof?</code> but rather than blocking, would simply return false. The expectation is the user will subsequently call <code>read</code> which may then wait.</p>
<p>The proposed implementation would look something like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">IO</span>
<span class="k">def</span> <span class="nf">readable?</span>
<span class="o">!</span><span class="nb">self</span><span class="p">.</span><span class="nf">closed?</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">BasicSocket</span>
<span class="c1"># Is it likely that the socket is still connected?</span>
<span class="c1"># May return false positive, but won't return false negative.</span>
<span class="k">def</span> <span class="nf">readable?</span>
<span class="k">return</span> <span class="kp">false</span> <span class="k">unless</span> <span class="k">super</span>
<span class="c1"># If we can wait for the socket to become readable, we know that the socket may still be open.</span>
<span class="n">result</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">recv_nonblock</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="no">MSG_PEEK</span><span class="p">,</span> <span class="ss">exception: </span><span class="kp">false</span><span class="p">)</span>
<span class="c1"># No data was available - newer Ruby can return nil instead of empty string:</span>
<span class="k">return</span> <span class="kp">false</span> <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="nf">nil?</span>
<span class="c1"># Either there was some data available, or we can wait to see if there is data avaialble.</span>
<span class="k">return</span> <span class="o">!</span><span class="n">result</span><span class="p">.</span><span class="nf">empty?</span> <span class="o">||</span> <span class="n">result</span> <span class="o">==</span> <span class="ss">:wait_readable</span>
<span class="k">rescue</span> <span class="no">Errno</span><span class="o">::</span><span class="no">ECONNRESET</span>
<span class="c1"># This might be thrown by recv_nonblock.</span>
<span class="k">return</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>For <code>IO</code> itself, when there is buffered data, <code>readable?</code> would also return true immediately, similar to <code>eof?</code>. This is not shown in the above implementation as I'm not sure if there is any Ruby method which exposes "there is buffered data".</p> Ruby master - Feature #20105 (Open): Introduce `IO::Stream` or something similar.https://redmine.ruby-lang.org/issues/201052024-01-01T07:43:50Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>Ruby's IO class has a general model for streaming IO, including some hidden classes like <code>IO::generic_readable</code> and <code>IO::generic_writable</code>.</p>
<p>As Ruby's core IO classes evolve, gems like <code>openssl</code> (see <code>OpenSSL::Buffering</code>) need to be updated to support changes to the interface.</p>
<p>As it stands, there are changes in <code>IO</code> which are not copied to <code>OpenSSL::Buffering</code>. I'd like to propose we introduce some shared interface for streams that is used by <code>IO</code>, <code>Socket</code>, and <code>OpenSSL</code> to start with. The general interface would be similar to <code>IO</code> and allow code like <code>OpenSSL</code> to avoid re-implementing the <code>IO</code> interface.</p>
<p>I don't have a strong idea for the interface yet, but it would probably look something like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">IO::Stream</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">io</span><span class="p">,</span> <span class="ss">buffered: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">read</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">buffer</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">write</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">buffer</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Include general operations from IO, like gets, puts, etc</span>
<span class="k">end</span>
</code></pre>
<p>I think ideally we'd try implement with pure Ruby and a first goal would be to replace <code>OpenSSL::Buffering</code>.</p> Ruby master - Feature #20102 (Open): Introduce `Fiber#resuming?`https://redmine.ruby-lang.org/issues/201022023-12-28T07:25:59Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>There are some tricky edge cases when using <code>Fibre#raise</code> and <code>Fiber#kill</code>, e.g.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">fiber</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">killer</span> <span class="o">=</span> <span class="no">Fiber</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="n">fiber</span><span class="p">.</span><span class="nf">raise</span><span class="p">(</span><span class="s2">"Stop"</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">fiber</span> <span class="o">=</span> <span class="no">Fiber</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="n">killer</span><span class="p">.</span><span class="nf">resume</span>
<span class="k">end</span>
<span class="n">fiber</span><span class="p">.</span><span class="nf">resume</span>
<span class="c1"># 4:in `raise': attempt to raise a resuming fiber (FiberError)</span>
<span class="c1"># 4:in `block in <main>'</span>
</code></pre>
<p>Async has to deal with this edge case explicitly by rescuing the exception:</p>
<p><a href="https://github.com/socketry/async/blob/ffd019d9c1d547926a28fe8f36bf7bfe91d8a168/lib/async/task.rb#L226-L233" class="external">https://github.com/socketry/async/blob/ffd019d9c1d547926a28fe8f36bf7bfe91d8a168/lib/async/task.rb#L226-L233</a></p>
<p>I'd like to avoid doing that and instead just ask "Can I kill/raise on this fiber right now?" which is determined by whether the fiber itself can be resumed or transferred to.</p>
<p>To address this, I'd like to introduce <code>Fiber#resuming?</code>:</p>
<pre><code class="c syntaxhl" data-language="c"><span class="cm">/*
* call-seq: fiber.resumed? -> true or false
*
* Whether the fiber is currently resumed.
*/</span>
<span class="n">VALUE</span>
<span class="nf">rb_fiber_resuming_p</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">fiber_value</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">rb_fiber_struct</span> <span class="o">*</span><span class="n">fiber</span> <span class="o">=</span> <span class="n">fiber_ptr</span><span class="p">(</span><span class="n">fiber_value</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">FIBER_TERMINATED_P</span><span class="p">(</span><span class="n">fiber</span><span class="p">))</span> <span class="k">return</span> <span class="n">RUBY_Qfalse</span><span class="p">;</span>
<span class="k">return</span> <span class="n">RBOOL</span><span class="p">(</span><span class="n">fiber</span><span class="o">-></span><span class="n">resuming_fiber</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
<p>See the PR: <a href="https://github.com/ruby/ruby/pull/9382" class="external">https://github.com/ruby/ruby/pull/9382</a></p> Ruby master - Feature #19742 (Open): Introduce `Module#anonymous?`https://redmine.ruby-lang.org/issues/197422023-06-21T10:47:33Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>As a follow-on <from <a href="https://bugs.ruby-lang.org/issues/19521%3E" class="external">https://bugs.ruby-lang.org/issues/19521></a>, I'd like propose we introduce <code>Module#anonymous?</code>.</p>
<p>In some situations, like logging/formatting, serialisation/deserialization, debugging or meta-programming, we might like to know if a class is a proper constant or not.</p>
<p>However, this brings about some other issues which might need to be discussed.</p>
<p>After assigning a constant, then removing it, the internal state of Ruby still believes that the class name is permanent, even thought it's no longer true.</p>
<p>e.g.</p>
<pre><code>m = Module.new
m.anonymous? # true
M = m
m.anonyomous # false
Object.send(:remove_const, :M)
M # uninitialized constant M (NameError)
m.anonymous? # false
</code></pre>
<p>Because RCLASS data structure is not updated after the constant is removed, internally the state still has a "permanent class name".</p>
<p>I want to use this proposal to discuss this issue and whether there is anything we should do about such behaviour (or even if it's desirable).</p>
<p>Proposed PR: <a href="https://github.com/ruby/ruby/pull/7966" class="external">https://github.com/ruby/ruby/pull/7966</a></p>
<p>cc <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/1241">@fxn (Xavier Noria)</a></p> Ruby master - Feature #19717 (Open): `ConditionVariable#signal` is not fair when the wakeup is co...https://redmine.ruby-lang.org/issues/197172023-06-07T10:04:38Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>For background, see this issue <a href="https://github.com/socketry/async/issues/99" class="external">https://github.com/socketry/async/issues/99</a>.</p>
<p>It looks like <code>ConditionVariable#signal</code> is not fair, if the calling thread immediately reacquires the resource.</p>
<p>I've given a detailed reproduction here as it's non-trivial: <a href="https://github.com/ioquatix/ruby-condition-variable-timeout" class="external">https://github.com/ioquatix/ruby-condition-variable-timeout</a>.</p>
<p>Because the spurious wakeup occurs, the thread is pushed to the back of the waitq, which means any other waiting thread will acquire the resource, and that thread will perpetually be at the back of the queue.</p>
<p>I believe the solution is to change <code>ConditionVarialbe#signal</code> should only remove the thread from the waitq if it's possible to acquire the lock. Otherwise it should be left in place, so that the order is retained, this should result in fair scheduling.</p> Ruby master - Feature #19642 (Open): Remove vectored read/write from `io.c`.https://redmine.ruby-lang.org/issues/196422023-05-15T06:33:11Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p><a href="https://github.com/socketry/async/issues/228#issuecomment-1546789910" class="external">https://github.com/socketry/async/issues/228#issuecomment-1546789910</a> is a comment from the kernel developer who tells us that <code>writev</code> is always worse than <code>write</code> system call.</p>
<p>A large amount of complexity in <code>io.c</code> comes from optional support from <code>writev</code>.</p>
<p>So, I'd like to remove support for <code>writev</code>.</p>
<p>I may try to measure the performance before/after. However it may not show much difference, except that the implementation in <code>io.c</code> can be much simpler.</p> Ruby master - Feature #19607 (Open): Introduce `Hash#symbolize_keys`.https://redmine.ruby-lang.org/issues/196072023-04-18T11:02:29Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>This is a very common operation.</p>
<p>It can currently be implemented using <code>Hash#transform_keys(&:to_sym)</code>.</p>
<p>It's currently provided by Rails as <code>Hash#symbolize_keys</code> and <code>Hash#symbolize_keys!</code>.</p>
<p>Proposed implementation is identical to Rails implementation: <a href="https://github.com/rails/rails/blob/539144d2d61770dab66c8643e744441e52538e09/activesupport/lib/active_support/core_ext/hash/keys.rb#L20-L37" class="external">https://github.com/rails/rails/blob/539144d2d61770dab66c8643e744441e52538e09/activesupport/lib/active_support/core_ext/hash/keys.rb#L20-L37</a></p>
<p>For completeness we could also consider adding <code>stringify_keys</code> but I think that's less frequently used.</p> Ruby master - Feature #19452 (Open): `Thread::Backtrace::Location` should have column information...https://redmine.ruby-lang.org/issues/194522023-02-20T07:06:22Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>I discussed this with <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/18">@mame (Yusuke Endoh)</a> and it would be pretty useful if we could also get the column information from exception backtrace location, even if it was slow.</p>
<p>A POC:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Thread::Backtrace::Location</span>
<span class="k">if</span> <span class="k">defined?</span><span class="p">(</span><span class="no">RubyVM</span><span class="o">::</span><span class="no">AbstractSyntaxTree</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">first_column</span>
<span class="no">RubyVM</span><span class="o">::</span><span class="no">AbstractSyntaxTree</span><span class="p">.</span><span class="nf">of</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="ss">keep_script_lines: </span><span class="kp">true</span><span class="p">).</span><span class="nf">first_column</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="k">def</span> <span class="nf">first_column</span>
<span class="k">raise</span> <span class="no">NotImplementedError</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>It would be good to have a standard interface, so we follow the same interface as <a href="https://bugs.ruby-lang.org/issues/19451" class="external">https://bugs.ruby-lang.org/issues/19451</a> and vice versa where it makes sense. I'll investigate it.</p> Ruby master - Feature #19451 (Open): Extract path and line number from SyntaxError?https://redmine.ruby-lang.org/issues/194512023-02-20T06:58:04Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>There doesn't seem to be any official way to extract the path and line number from a syntax error.</p>
<p>There are two ways I can see us dong this:</p>
<ul>
<li>Provide explicit <code>path</code> and <code>line_number</code> attributes.</li>
<li>Prepend them to <code>backtrace_locations</code>.</li>
</ul>
<p>The nice thing about the latter approach is that it will just work with existing tools which understand how to highlight code based on backtrace locations.</p>
<p>Maybe we should do both?</p> Ruby master - Misc #19421 (Open): Distribution documentationhttps://redmine.ruby-lang.org/issues/194212023-02-07T05:03:49Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>I use Ruby a lot, on a lot of different systems, and help people and companies use it, including developers who install it on their systems.</p>
<p>Over time, I found that installing Ruby isn't always easy. Part of this is due to package management. There are many systems, and Ruby has had some tricky migrations (e.g. OpenSSL is probably one of the most painful ones that lots of developers have trouble with).</p>
<p>Arch Linux has been stuck on Ruby 3.0 for a long time, which could be considered surprising given that Arch Linux is often on the bleeding edge of releases. I personally use Arch too. So I decided to ask, what is holding them up from making a release?</p>
<p>I found out they had many questions about how to distribute Ruby correctly. When I listened to those questions I felt that there are many ambiguities in how we build and package Ruby for operating system packages. This isn't to say that there isn't a good way to do it, just that we as a core team might be able to improve our communication about how Ruby is evolving and the implications for package managers (if any).</p>
<p>I've introduced <code>doc/distribution.md</code> as an effort to start having better documentation for people distributing Ruby. There are many ways which people distribute Ruby, and many "partial" documentation or assumptions being made about how to distribute Ruby and I'd like to provide a convenient standard location that can help people build package for Ruby distribution. Ultimately this makes my job easier because the latest versions of Ruby will be easier to install, so that's what I care about, but I don't care about what specifically is in the document, except that I think we should listen to the kinds of questions being asked and, in the best interest of Ruby, provide guidance.</p>
<p>There was a lot of good discussion on the PR, but my goal is not to make a finished document, but instead plant the seed so it can grow.</p>
<p><a href="https://github.com/ruby/ruby/pull/6856" class="external">https://github.com/ruby/ruby/pull/6856</a></p>
<p>Some follow up discussion is required:</p>
<ul>
<li>
<p>What is the best practice for building source packages. The documentation I wrote from this was removed as "out of scope" but I disagree with that (<a href="https://github.com/ruby/ruby/commit/c35ebed895e1a3f7bced3db50ea0db8f284744e8" class="external">https://github.com/ruby/ruby/commit/c35ebed895e1a3f7bced3db50ea0db8f284744e8</a>). I don't have a strong opinion about what it should look like, but I think we should give a clear example of how to build source packages like what I wrote.</p>
</li>
<li>
<p>Related to the above, what is the official location for source tarballs?</p>
</li>
<li>
<p>What optional dependencies are required for building vs distributing Ruby. Arch Linux currently lists: <code>doxygen gdbm graphviz libffi libyaml openssl ttf-dejavu tk</code> as dependencies but it's not clear if this list is up to date, or what the expectations are. When someone installs Ruby (e.g. <code>apt-get install ruby</code>) what dependencies should be installed? I think it would be helpful to list expected dependencies (from a system package POV), etc.</p>
</li>
<li>
<p>Is Ruby needed for building Ruby? Should source packages install Ruby before building from source? If so, what versions are supported?</p>
</li>
<li>
<p>Clear guidance on gems that are distributed an alongside Ruby, and how security changes to gems are managed.</p>
</li>
</ul>
<p>Even if we have more detailed documentation elsewhere, let's summarise it and then cross-reference it. People who build packages to distribute and install Ruby should feel supported, they are very important to our community. To this end, I established <code>#distribution</code> channel on Slack for discussion. We should listen to the questions being asked and use those questions to drive improvements to documentation.</p> Ruby master - Feature #19333 (Open): Setting (Fiber Local|Thread Local|Fiber Storage) to nil shou...https://redmine.ruby-lang.org/issues/193332023-01-11T22:21:06Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>As it stands, Fiber Locals, Thread Locals and Fiber Storage have no way of deleting key-value associations.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="mi">100</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="nb">name</span> <span class="o">=</span> <span class="ss">:"variable-</span><span class="si">#{</span><span class="n">i</span><span class="si">}</span><span class="ss">"</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="nb">name</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10</span>
<span class="k">end</span>
</code></pre>
<p>Because of this, dynamically generated associations can leak over time. This is worse for things like Threads that might be pooled (or maybe an argument against user-space pooling).</p>
<p>In any case, having a way to delete those associations would allow application code to at least delete the associations when they no longer make sense.</p>
<p>I propose that assigning <code>nil</code> to "locals" or "storage" should effectively delete them.</p>
<p>e.g.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="mi">100</span><span class="p">.</span><span class="nf">times</span> <span class="k">do</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span>
<span class="nb">name</span> <span class="o">=</span> <span class="ss">:"variable-</span><span class="si">#{</span><span class="n">i</span><span class="si">}</span><span class="ss">"</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="nb">name</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10</span>
<span class="no">Thread</span><span class="p">.</span><span class="nf">current</span><span class="p">[</span><span class="nb">name</span><span class="p">]</span> <span class="o">=</span> <span class="kp">nil</span> <span class="c1"># delete association</span>
<span class="k">end</span>
</code></pre>
<p>A more invasive alternative would be to define new interfaces like <code>Thread::Local</code>, <code>Fiber::Local</code> and <code>Fiber::Storage::Local</code> (or something) which correctly clean up on GC.</p> Ruby master - Feature #19306 (Open): Expand zlib interfacehttps://redmine.ruby-lang.org/issues/193062023-01-04T07:02:07Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>Introduce some extra methods like <code>window_size</code>, <code>level</code> (compression/decompression), enhance <code>inspect</code> and various other quality of life improvements.</p> Ruby master - Feature #19077 (Open): Introduce `Hash#dup` copy on write.https://redmine.ruby-lang.org/issues/190772022-10-22T21:55:25Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>Pull Request: <a href="https://github.com/ruby/ruby/pull/6613" class="external">https://github.com/ruby/ruby/pull/6613</a>.</p> Ruby master - Feature #19059 (Open): Introduce top level `module TimeoutError` for aggregating va...https://redmine.ruby-lang.org/issues/190592022-10-15T03:04:07Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>This proposal was originally part of <a href="https://bugs.ruby-lang.org/issues/18630" class="external">https://bugs.ruby-lang.org/issues/18630</a> but was removed because we could not decide on the name.</p>
<p>Introduce the following:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">TimeoutError</span>
<span class="k">end</span>
<span class="no">IO</span><span class="o">::</span><span class="no">TimeoutError</span><span class="p">.</span><span class="nf">include</span><span class="p">(</span><span class="no">TimeoutError</span><span class="p">)</span>
<span class="no">Regexp</span><span class="o">::</span><span class="no">TimeoutError</span><span class="p">.</span><span class="nf">include</span><span class="p">(</span><span class="no">TimeoutError</span><span class="p">)</span>
<span class="c1"># Maybe?</span>
<span class="no">Timeout</span><span class="o">::</span><span class="no">Error</span><span class="p">.</span><span class="nf">include</span><span class="p">(</span><span class="no">TimeoutError</span><span class="p">)</span>
</code></pre>
<p>It may be easier for users.</p>
<p>This was discussed before with the following conclusion:</p>
<ul>
<li>Top level <code>TimeoutError</code> is available.</li>
<li>Using a module for a <code>TimeoutError</code> may not be consistent with other top level <code>class #{thing}Error</code>.</li>
</ul> Ruby master - Feature #19057 (Assigned): Hide implementation of `rb_io_t`.https://redmine.ruby-lang.org/issues/190572022-10-15T01:54:57Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>In order to make improvements to the IO implementation like <a href="https://bugs.ruby-lang.org/issues/18455" class="external">https://bugs.ruby-lang.org/issues/18455</a>, we need to add new fields to <code>struct rb_io_t</code>.</p>
<p>By the way, ending types in <code>_t</code> is not recommended by POSIX, so I'm also trying to rename the internal implementation to drop <code>_t</code> where possible during this conversion.</p>
<p>Anyway, we should try to hide the implementation of <code>struct rb_io</code>. Ideally, we don't expose any of it, but the problem is backwards compatibility.</p>
<p>So, in order to remain backwards compatibility, we should expose some fields of <code>struct rb_io</code>, the most commonly used one is <code>fd</code> and <code>mode</code>, but several others are commonly used.</p>
<p>There are many fields which should not be exposed because they are implementation details.</p>
<a name="Current-proposal"></a>
<h2 >Current proposal<a href="#Current-proposal" class="wiki-anchor">¶</a></h2>
<p>The current proposed change <a href="https://github.com/ruby/ruby/pull/6511" class="external">https://github.com/ruby/ruby/pull/6511</a> creates two structs:</p>
<pre><code class="c syntaxhl" data-language="c"><span class="c1">// include/ruby/io.h</span>
<span class="cp">#ifndef RB_IO_T
</span><span class="k">struct</span> <span class="n">rb_io</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="c1">// ... public fields ...</span>
<span class="p">};</span>
<span class="cp">#else
</span><span class="k">struct</span> <span class="n">rb_io</span><span class="p">;</span>
<span class="cp">#endif
</span>
<span class="c1">// internal/io.h</span>
<span class="cp">#define RB_IO_T
</span><span class="k">struct</span> <span class="n">rb_io</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="c1">// ... public fields ...</span>
<span class="c1">// ... private fields ...</span>
<span class="p">};</span>
</code></pre>
<p>However, we are not 100% confident this is safe according to the C specification. My experience is not sufficiently wide to say this is safe in practice, but it does look okay to both myself, and <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/772">@Eregon (Benoit Daloze)</a> + <a class="user active user-mention" href="https://redmine.ruby-lang.org/users/73">@tenderlovemaking (Aaron Patterson)</a> have both given some kind of approval.</p>
<p>That being said, maybe it's not safe.</p>
<p>There are two alternatives:</p>
<a name="Hide-all-details"></a>
<h2 >Hide all details<a href="#Hide-all-details" class="wiki-anchor">¶</a></h2>
<p>We can make public <code>struct rb_io</code> completely invisible.</p>
<pre><code class="c syntaxhl" data-language="c"><span class="c1">// include/ruby/io.h</span>
<span class="cp">#define RB_IO_HIDDEN
</span><span class="k">struct</span> <span class="n">rb_io</span><span class="p">;</span>
<span class="kt">int</span> <span class="nf">rb_ioptr_descriptor</span><span class="p">(</span><span class="k">struct</span> <span class="n">rb_io</span> <span class="o">*</span><span class="n">ioptr</span><span class="p">);</span> <span class="c1">// accessor for previously visible state.</span>
<span class="c1">// internal/io.h</span>
<span class="k">struct</span> <span class="n">rb_io</span> <span class="p">{</span>
<span class="c1">// ... all fields ...</span>
<span class="p">};</span>
</code></pre>
<p>This would only be forwards compatible, and code would need to feature detect like this:</p>
<pre><code class="c syntaxhl" data-language="c"><span class="cp">#ifdef RB_IO_HIDDEN
#define RB_IOPTR_DESCRIPTOR rb_ioptr_descriptor
#else
#define RB_IOPTR_DESCRIPTOR(ioptr) rb_ioptr_descriptor(ioptr)
#endif
</span></code></pre>
<a name="Nested-public-interface"></a>
<h2 >Nested public interface<a href="#Nested-public-interface" class="wiki-anchor">¶</a></h2>
<p>Alternatively, we can nest the public fields into the private struct:</p>
<pre><code class="c syntaxhl" data-language="c"><span class="c1">// include/ruby/io.h</span>
<span class="k">struct</span> <span class="n">rb_io_public</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="c1">// ... public fields ...</span>
<span class="p">};</span>
<span class="c1">// internal/io.h</span>
<span class="cp">#define RB_IO_T
</span><span class="k">struct</span> <span class="n">rb_io</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">rb_io_public</span> <span class="n">public</span><span class="p">;</span>
<span class="c1">// ... private fields ...</span>
<span class="p">};</span>
</code></pre>
<a name="Considerations"></a>
<h2 >Considerations<a href="#Considerations" class="wiki-anchor">¶</a></h2>
<p>I personally think the "Hide all details" implementation is the best, but it's also the lest compatible. This is also what we are ultimately aiming for, whether we decide to take an intermediate "compatibility step" is up to us.</p>
<p>I think "Nested public interface" is messy and introduces more complexity, but it might be slightly better defined than the "Current proposal" which might create undefined behaviour. That being said, all the tests are passing.</p> Ruby master - Feature #19056 (Open): Introduce `Fiber.annotation` for attaching messages to fibers.https://redmine.ruby-lang.org/issues/190562022-10-14T23:09:31Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>It's useful to know what a fiber is doing especially when they have a temporal execution (i.e. sockets connecting vs connected, binding vs accepting, queue popping, etc)</p>
<p>Let's introduce <code>Fiber.annotate</code> and <code>Fiber#annotation</code> for logging a short message attached to Fibers.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Fiber</span><span class="p">.</span><span class="nf">annotate</span> <span class="s2">"Counting to 10"</span>
<span class="mi">10</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span><span class="o">|</span><span class="no">I</span><span class="o">|</span> <span class="nb">puts</span> <span class="no">I</span><span class="p">}</span>
<span class="c1"># Fiber.current.annotation => "Counting to 10"</span>
</code></pre>
<p>Pull Request: <a href="https://github.com/ruby/ruby/pull/6554" class="external">https://github.com/ruby/ruby/pull/6554</a></p> Ruby master - Feature #19019 (Open): Nicely formatted exception messages in HTMLhttps://redmine.ruby-lang.org/issues/190192022-09-23T07:52:32Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>We have made a lot of improvements to exception formatting. See <a href="https://bugs.ruby-lang.org/issues/18296" class="external">https://bugs.ruby-lang.org/issues/18296</a> for details.</p>
<p>I'd like us to consider adding support for HTML formatting of messages, e.g.</p>
<pre><code>exception.full_message(highlight: :html)
</code></pre>
<p>or something to that effect.</p>
<p>Another option is to convert terminal style errors to html by converting control characters. However, I'm less optimistic about parsing sequences like <code>^^^^^</code> to apply a style to the above line.</p>
<p>Maybe it's sufficient when <code>highlight: true</code> is specified to avoid <code>^^^^^</code> characters.</p>
<p>When I've personally implemented this in the past, I've used a formatting object, e.g.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">exception</span><span class="p">.</span><span class="nf">full_message</span><span class="p">(</span><span class="ss">highlight: </span><span class="no">HTMLFormatter</span><span class="p">.</span><span class="nf">new</span><span class="p">)</span>
</code></pre>
<p>The formatter has a rich interface for printing, and uses a set of abstractions to map to the underlying output.</p>
<p>e.g.</p>
<pre><code>formatter.puts(:exception, "NoMethodError", :reset, ": ", :message, "undefined method 'bar' for ", :object, "#<Foo...>")
formatter.puts(:code, "x = foo", :error, ".bar, :reset)
</code></pre>
<p>This can be easily mapped to HTML, XTerm, plain text, json, etc.</p> Ruby master - Bug #18455 (Open): `IO#close` has poor performance and difficult to understand sema...https://redmine.ruby-lang.org/issues/184552022-01-01T07:13:08Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p><code>IO#close</code> should be responsible for closing the file descriptor referred to by the IO instance. When dealing with buffered IO, one can also expect this to flush the internal buffers if possible.</p>
<p>Currently, all blocking IO operations release the GVL and perform the blocking system call using <code>rb_thread_io_blocking_region</code>. The current implementation takes a file descriptor and adds an entry to the VM global <code>waiting_fds</code> list. When the operation is completed, the entry is removed from <code>waiting_fds</code>.</p>
<p>When calling <code>IO#close</code>, this list is traversed and any threads performing blocking operations with a matching file descriptor are interrupted. The performance of this is O(number of blocking IO operations) which in practice the performance of <code>IO#close</code> can take milliseconds with 10,000 threads performing blocking IO. This performance is unacceptable.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/bin/env ruby</span>
<span class="nb">require</span> <span class="s1">'benchmark'</span>
<span class="k">class</span> <span class="nc">Reading</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@r</span><span class="p">,</span> <span class="vi">@w</span> <span class="o">=</span> <span class="no">IO</span><span class="p">.</span><span class="nf">pipe</span>
<span class="vi">@thread</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="vi">@r</span><span class="p">.</span><span class="nf">read</span>
<span class="k">rescue</span> <span class="no">IOError</span>
<span class="c1"># Ignore.</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">attr</span> <span class="ss">:r</span>
<span class="kp">attr</span> <span class="ss">:w</span>
<span class="kp">attr</span> <span class="ss">:thread</span>
<span class="k">def</span> <span class="nf">join</span>
<span class="vi">@thread</span><span class="p">.</span><span class="nf">join</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">measure</span><span class="p">(</span><span class="n">count</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">readings</span> <span class="o">=</span> <span class="n">count</span><span class="p">.</span><span class="nf">times</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span>
<span class="no">Reading</span><span class="p">.</span><span class="nf">new</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mi">10</span>
<span class="n">duration</span> <span class="o">=</span> <span class="no">Benchmark</span><span class="p">.</span><span class="nf">measure</span> <span class="k">do</span>
<span class="n">readings</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">reading</span><span class="o">|</span>
<span class="n">reading</span><span class="p">.</span><span class="nf">r</span><span class="p">.</span><span class="nf">close</span>
<span class="n">reading</span><span class="p">.</span><span class="nf">w</span><span class="p">.</span><span class="nf">close</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">average</span> <span class="o">=</span> <span class="p">(</span><span class="n">duration</span><span class="p">.</span><span class="nf">total</span> <span class="o">/</span> <span class="n">count</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1000.0</span>
<span class="n">pp</span> <span class="ss">count: </span><span class="n">count</span><span class="p">,</span> <span class="ss">average: </span><span class="nb">sprintf</span><span class="p">(</span><span class="s2">"%0.2fms"</span><span class="p">,</span> <span class="n">average</span><span class="p">)</span>
<span class="n">readings</span><span class="p">.</span><span class="nf">each</span><span class="p">(</span><span class="o">&</span><span class="ss">:join</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">measure</span><span class="p">(</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">measure</span><span class="p">(</span> <span class="mi">100</span><span class="p">)</span>
<span class="n">measure</span><span class="p">(</span> <span class="mi">1000</span><span class="p">)</span>
<span class="n">measure</span><span class="p">(</span><span class="mi">10000</span><span class="p">)</span>
</code></pre>
<p>In addition, the semantics of this operation are confusing at best. While Ruby programs are dealing with IO instances, the VM is dealing with file descriptors, in effect performing some internal de-duplication of IO state. In practice, this leads to strange behaviour:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1">#!/usr/bin/env ruby</span>
<span class="n">r</span><span class="p">,</span> <span class="n">w</span> <span class="o">=</span> <span class="no">IO</span><span class="p">.</span><span class="nf">pipe</span>
<span class="n">r2</span> <span class="o">=</span> <span class="no">IO</span><span class="p">.</span><span class="nf">for_fd</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="nf">to_i</span><span class="p">)</span>
<span class="n">pp</span> <span class="ss">r: </span><span class="n">r</span><span class="p">,</span> <span class="ss">r2: </span><span class="n">r2</span>
<span class="n">t</span> <span class="o">=</span> <span class="no">Thread</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="n">r2</span><span class="p">.</span><span class="nf">read</span> <span class="k">rescue</span> <span class="kp">nil</span>
<span class="n">r2</span><span class="p">.</span><span class="nf">read</span> <span class="c1"># EBADF</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mf">0.5</span>
<span class="n">r</span><span class="p">.</span><span class="nf">close</span>
<span class="n">t</span><span class="p">.</span><span class="nf">join</span> <span class="k">rescue</span> <span class="kp">nil</span>
<span class="n">pp</span> <span class="ss">r: </span><span class="n">r</span><span class="p">,</span> <span class="ss">r2: </span><span class="n">r2</span>
<span class="c1"># r is closed, r2 is valid but will raise EBADF on any operation.</span>
</code></pre>
<p>In addition, this confusing behaviour extends to Ractor and state is leaked between the two:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">r</span><span class="p">,</span> <span class="n">w</span> <span class="o">=</span> <span class="no">IO</span><span class="p">.</span><span class="nf">pipe</span>
<span class="n">ractor</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="nf">to_i</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">fd</span><span class="o">|</span>
<span class="n">r2</span> <span class="o">=</span> <span class="no">IO</span><span class="p">.</span><span class="nf">for_fd</span><span class="p">(</span><span class="n">fd</span><span class="p">)</span>
<span class="n">r2</span><span class="p">.</span><span class="nf">read</span>
<span class="c1"># r2.read # EBADF</span>
<span class="k">end</span>
<span class="nb">sleep</span> <span class="mf">0.5</span>
<span class="n">r</span><span class="p">.</span><span class="nf">close</span>
<span class="n">pp</span> <span class="ss">take: </span><span class="n">ractor</span><span class="p">.</span><span class="nf">take</span>
</code></pre>
<p>I propose the following changes to simplify the semantics and improve performance:</p>
<ul>
<li>Move the semantics of <code>waiting_fds</code> from per-fd to per-IO. This means that <code>IO#close</code> only interrupts blocking operations performed on the same IO instance rather than ANY IO which refers to the same file descriptor. I think this behaviour is easier to understand and still protects against the vast majority of incorrect usage.</li>
<li>Move the details of <code>struct rb_io_t</code> to <code>internal/io.h</code> so that the implementation details are not part of the public interface.</li>
</ul>
<a name="Benchmarks"></a>
<h2 >Benchmarks<a href="#Benchmarks" class="wiki-anchor">¶</a></h2>
<p>Before:</p>
<pre><code>{:count=>10, :average=>"0.19ms"}
{:count=>100, :average=>"0.11ms"}
{:count=>1000, :average=>"0.18ms"}
{:count=>10000, :average=>"1.16ms"}
</code></pre>
<p>After:</p>
<pre><code>{:count=>10, :average=>"0.20ms"}
{:count=>100, :average=>"0.11ms"}
{:count=>1000, :average=>"0.15ms"}
{:count=>10000, :average=>"0.68ms"}
</code></pre>
<p>After investigating this further I found that the <code>rb_thread_io_blocking_region</code> using <code>ubf_select</code> can be incredibly slow, proportional to the number of threads. I don't know whether it's advisable but:</p>
<pre><code class="c syntaxhl" data-language="c"> <span class="n">BLOCKING_REGION</span><span class="p">(</span><span class="n">blocking_node</span><span class="p">.</span><span class="kr">thread</span><span class="p">,</span> <span class="p">{</span>
<span class="n">val</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="n">data1</span><span class="p">);</span>
<span class="n">saved_errno</span> <span class="o">=</span> <span class="n">errno</span><span class="p">;</span>
<span class="p">},</span> <span class="nb">NULL</span> <span class="cm">/* ubf_select */</span><span class="p">,</span> <span class="n">blocking_node</span><span class="p">.</span><span class="kr">thread</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">);</span>
</code></pre>
<p>Disabling the UBF function and relying on <code>read(fd, ...)</code>/<code>write(fd, ...)</code> blocking operations to fail when <code>close(fd)</code> is invoked might be sufficient? This needs more investigation but after making this change, we have constant-time IO#close.</p>
<pre><code>{:count=>10, :average=>"0.13ms"}
{:count=>100, :average=>"0.06ms"}
{:count=>1000, :average=>"0.04ms"}
{:count=>10000, :average=>"0.09ms"}
</code></pre>
<p>Which is ideally what we want.</p> Ruby master - Feature #18227 (Open): Static class initialization.https://redmine.ruby-lang.org/issues/182272021-09-27T03:49:35Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>As a follow on from <a href="https://bugs.ruby-lang.org/issues/18189" class="external">https://bugs.ruby-lang.org/issues/18189</a> I would like to propose some kind of static class initialization. I'll investigate whether it's possible and create a PR.</p> Ruby master - Feature #18083 (Open): Capture error in ensure block.https://redmine.ruby-lang.org/issues/180832021-08-18T03:03:42Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>As discussed in <a href="https://bugs.ruby-lang.org/issues/15567" class="external">https://bugs.ruby-lang.org/issues/15567</a> there are some tricky edge cases.</p>
<p>As a general model, something like the following would be incredibly useful:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">begin</span>
<span class="o">...</span>
<span class="k">ensure</span> <span class="o">=></span> <span class="n">error</span>
<span class="n">pp</span> <span class="s2">"error occurred"</span> <span class="k">if</span> <span class="n">error</span>
<span class="k">end</span>
</code></pre>
<p>Currently you can get similar behaviour like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">begin</span>
<span class="o">...</span>
<span class="k">rescue</span> <span class="no">Exception</span> <span class="o">=></span> <span class="n">error</span>
<span class="k">raise</span>
<span class="k">ensure</span>
<span class="n">pp</span> <span class="s2">"error occurred"</span> <span class="k">if</span> <span class="n">error</span>
<span class="k">end</span>
</code></pre>
<p>The limitation of this approach is it only works if you don't need any other <code>rescue</code> clause. Otherwise, it may not work as expected or require extra care. Also, Rubocop will complain about it.</p>
<p>Using <code>$!</code> can be buggy if you call some method from <code>rescue</code> or <code>ensure</code> clause, since it would be set already. It was discussed extensively in <a href="https://bugs.ruby-lang.org/issues/15567" class="external">https://bugs.ruby-lang.org/issues/15567</a> if you want more details.</p> Ruby master - Feature #18035 (Open): Introduce general model/semantic for immutability.https://redmine.ruby-lang.org/issues/180352021-07-09T08:10:55Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>It would be good to establish some rules around mutability, immutability, frozen, and deep frozen in Ruby.</p>
<p>I see time and time again, incorrect assumptions about how this works in production code. Constants that aren't really constant, people using <code>#freeze</code> incorrectly, etc.</p>
<p>I don't have any particular preference but:</p>
<ul>
<li>We should establish consistent patterns where possible, e.g.
<ul>
<li>Objects created by <code>new</code> are mutable.</li>
<li>Objects created by literal are immutable.</li>
</ul>
</li>
</ul>
<p>We have problems with how <code>freeze</code> works on composite data types, e.g. <code>Hash#freeze</code> does not impact children keys/values, same for Array. Do we need to introduce <code>freeze(true)</code> or <code>#deep_freeze</code> or some other method?</p>
<p>Because of this, frozen does not necessarily correspond to immutable. This is an issue which causes real world problems.</p>
<p>I also propose to codify this where possible, in terms of "this class of object is immutable" should be enforced by the language/runtime, e.g.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">Immutable</span>
<span class="k">def</span> <span class="nf">new</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="k">super</span><span class="p">.</span><span class="nf">freeze</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">MyImmutableObject</span>
<span class="kp">extend</span> <span class="no">Immutable</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="vi">@x</span> <span class="o">=</span> <span class="n">x</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">freeze</span>
<span class="k">return</span> <span class="nb">self</span> <span class="k">if</span> <span class="nb">frozen?</span>
<span class="vi">@x</span><span class="p">.</span><span class="nf">freeze</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">o</span> <span class="o">=</span> <span class="no">MyImmutableObject</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
<span class="nb">puts</span> <span class="n">o</span><span class="p">.</span><span class="nf">frozen?</span>
</code></pre>
<p>Finally, this area has an impact to thread and fiber safe programming, so it is becoming more relevant and I believe that the current approach which is rather adhoc is insufficient.</p>
<p>I know that it's non-trivial to retrofit existing code, but maybe it can be done via magic comment, etc, which we already did for frozen string literals.</p>
<p>Proposed PR: <a href="https://github.com/ruby/ruby/pull/4879" class="external">https://github.com/ruby/ruby/pull/4879</a></p> Ruby master - Feature #15456 (Open): Adopt some kind of consistent versioning mechanismhttps://redmine.ruby-lang.org/issues/154562018-12-23T21:29:22Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>After the discussion <a href="https://github.com/ruby/bigdecimal/issues/114" class="external">https://github.com/ruby/bigdecimal/issues/114</a> I feel like we would benefit from some consistent versioning mechanism across all of Ruby.</p>
<p>So far, I feel the majority of Ruby uses some form of semantic versioning.</p>
<p>For the sanity of all Ruby users, I think it would be a good policy to adopt this across core Ruby and standard gems.</p>
<p>There are some previous discussions around this:</p>
<ul>
<li><a href="https://bugs.ruby-lang.org/issues/9215" class="external">https://bugs.ruby-lang.org/issues/9215</a></li>
<li><a href="https://bugs.ruby-lang.org/projects/ruby/wiki/GeneralMaintenancePolicy" class="external">https://bugs.ruby-lang.org/projects/ruby/wiki/GeneralMaintenancePolicy</a></li>
<li><a href="https://bugs.ruby-lang.org/issues/8835" class="external">https://bugs.ruby-lang.org/issues/8835</a></li>
</ul>
<p>So, the questions are as follows:</p>
<ul>
<li>Can we adopt Semantic Versioning (or as much of it as possible) across Ruby?</li>
<li>Would such a change help users of Ruby?</li>
<li>Is there existing documentation about how version number works?</li>
<li>How does it deviate from Semantic Versioning?</li>
<li>Is this deviation important and worth the additional complexity for our users?</li>
</ul>
<p>As an aside:</p>
<ul>
<li>How do other implementations advertise compatibility with Ruby?</li>
<li>JRuby and RBX have totally different version numbers that are difficult to understand w.r.t. compatibility with mainline CRuby.</li>
</ul>
<p>My main concern is how difficult this is for everyone to keep track of and also the implied assumptions (e.g. breaking change if and only if major versions bump). If different parts of Ruby use different versioning scheme, it is hard for our users to define dependencies which don't cause broken software.</p> Ruby master - Feature #14771 (Open): Add method to create DNS resource from data stringhttps://redmine.ruby-lang.org/issues/147712018-05-17T12:34:45Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>I recently played around with DNS over HTTPS.</p>
<p>I found that it's a little bit tricky to generate the appropriate resource using the Resolv::DNS::Resource hierarchy.</p>
<p>Here is what my code ended up:</p>
<pre><code> if klass = Resolv::DNS::Resource.get_class(answer["type"], resource_class::ClassValue)
if klass < Resolv::DNS::Resource::DomainName
resource = klass.new(Resolv::DNS::Name.create(answer["data"]))
else
resource = klass.new(answer["data"])
end
</code></pre>
<p>It would be nice to have a simpler interface, e.g.</p>
<pre><code> if klass = Resolv::DNS::Resource.get_class(answer["type"], resource_class::ClassValue)
klass.create(answer["data"])
end
</code></pre>
<p>In this case, the <code>Resource#create</code> method would take a data string and convert it to a name if required.</p>
<p>The base resource class could just have <code>alias create new</code>, while the <code>DomainName</code> class could have:</p>
<pre><code>def create(data)
self.new(Name.create(data))
end
</code></pre>
<p>Or something like that.</p> Ruby master - Bug #14681 (Open): `syswrite': stream closed in another thread (IOError)https://redmine.ruby-lang.org/issues/146812018-04-12T03:16:08Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>Perhaps related to <a href="https://bugs.ruby-lang.org/issues/13632" class="external">https://bugs.ruby-lang.org/issues/13632</a></p>
<p>Here is a sample to reproduce the issue.</p>
<pre><code>#!/usr/bin/env ruby
require 'thread'
puts RUBY_VERSION
100.times.collect do
Thread.new do
input, output = IO.pipe
worker = Thread.new do
sleep(0.1)
output.syswrite('.')
end
input.read(1)
input.close
output.close
worker.join
end
end.each(&:join)
</code></pre>
<p>If you run this, you will get output like so:</p>
<pre><code>2.5.0
#<Thread:0x00007fb7a4956ee8@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a50bb468@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4964250@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a49386f0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a493ab08@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495fb88@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a50bbb98@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4948820@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4939820@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a486a458@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4860020@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4970258@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4973f48@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4948618@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495f728@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495f868@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4848628@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4843858@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4809400@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a5085278@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495e0f8@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a48417d8@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a5037c80@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4948398@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4948c30@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4939b18@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4957500@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a480a9b8@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a5036d30@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a5085c50@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495e2b0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495f070@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495e6e8@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a49572d0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495e580@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a494a350@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4811060@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a50842b0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a494bb10@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a493ae00@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a495e878@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a494be30@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4809c70@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a480a4e0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a50b90f0@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:11 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
#<Thread:0x00007fb7a4965600@/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:8 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
Traceback (most recent call last):
1: from /private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `block (3 levels) in <main>'
/private/var/folders/3x/tvygzl0s65520b6t4tzqbt980000gn/T/16789369-c982-4cbc-a0b3-c836af60bf03:13:in `syswrite': stream closed in another thread (IOError)
Exited with status 1 after 0.291 seconds
</code></pre>
<p>However, you can clearly see from the order of the sample code it's not possible for such an error to occur. <code>#close</code> is only invoked after a successful <code>#read</code> which is only possible after a successful <code>#write</code>. Yet, the error implies that the write failed because it was already closed.</p> Ruby master - Feature #13626 (Open): Add String#byteslice!https://redmine.ruby-lang.org/issues/136262017-06-03T00:01:27Zioquatix (Samuel Williams)samuel@oriontransfer.net
<p>It's a common pattern in IO buffering, to read a part of a string while leaving the remainder.</p>
<pre><code># Consume only part of the read buffer:
result = @read_buffer.byteslice(0, size)
@read_buffer = @read_buffer.byteslice(size, @read_buffer.bytesize)
</code></pre>
<p>It would be nice if this code could be simplified to:</p>
<pre><code>result = @read_buffer.byteslice!(size)
</code></pre>
<p>Additionally, this allows a significantly improved implementation by the interpreter.</p>