<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Noteflakes</title><link>https://noteflakes.com/</link><description>A website by Sharon Rosner</description><language>en-us</language><pubDate>Sat, 14 Mar 2026 16:39:24 GMT</pubDate><emit>&lt;atom:link href=&quot;https://noteflakes.com/feeds/rss&quot; rel=&quot;self&quot; type=&quot;application/rss+xml&quot; /&gt;</emit><item><title>Threads vs Fibers - Can&#39;t We Be Friends?</title><link>https://noteflakes.com/articles/2025-12-19-friday-update</link><guid>https://noteflakes.com/articles/2025-12-19-friday-update</guid><pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last few weeks I&amp;#8217;ve been writing here about my work on
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine&quot;&gt;UringMachine&lt;/a&gt;, a Ruby gem for
doing I/O with io_uring. Before I talk about my work this week, I&amp;#8217;d like to
address something that is important to me.&lt;/p&gt;

&lt;h2 id=&quot;maybe-well-see&quot;&gt;Maybe, We&amp;#8217;ll See&amp;#8230;&lt;/h2&gt;

&lt;p&gt;I usually share my blog posts in a few different places on the internet, and
sometime people write their reactions. Last week, on Reddit, someone didn&amp;#8217;t like
the title (of all things&amp;#8230; 🙂) I gave my &lt;a href=&quot;/articles/2025-12-12-friday-update&quot;&gt;last blog
post&lt;/a&gt;, and made the following remark:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;When a project or people aren&amp;#8217;t open on what their tech is good for, and what
it&amp;#8217;s not good for, it really drives me away&amp;#8230;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To which I replied:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I try to be as open as possible, and the thing is I don&amp;#8217;t yet know what this
is good for, but I&amp;#8217;m excited about the possibilities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To which that person retorted:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I find it surprising one would get involved with such project without having
an idea of the result to expect. The pros and cons of fibers and async are
pretty well known.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, starting this project I did have some vague idea of what will happen if
we combined Ruby fibers with io_uring. At the same time, I wanted to be able to
show some concrete results (as in, benchmarks), and in that sense I don&amp;#8217;t think
anyone could claim they could know the expected result in advance.&lt;/p&gt;

&lt;p&gt;But also, in a more general sense, I believe it would have been presumtuous,
even arrogant for me to claim I already knew everything there was to know about
io_uring, Ruby fibers and how the two would work together. Yes, there&amp;#8217;s a
general agreement that fibers are good for I/O-bound work and less useful for
CPU-bound work, but that is just a general concept.&lt;/p&gt;

&lt;p&gt;Beyond that, there are a lot of questions to answer: Where are the limits? Can
an I/O-bound workload become CPU-bound, or vice versa? What about mixed
workloads? What are the shortcomings of the Fiber Scheduler interface? Are there
other things we can improve about Ruby&amp;#8217;s I/O layer? And finally, would this work
lead to new solutions for apps written in Ruby? I don&amp;#8217;t know the answer to these
questions, but that&amp;#8217;s what I want to find out!&lt;/p&gt;

&lt;p&gt;In that regard, I really appreciate the remarks that Matz made at the last Euruko
conference in Viana do Castelo. He talked about the spirit of openess and
experimentation and inquisitiveness: is this project worth anything, or is it
just a &lt;em&gt;big waste of time&lt;/em&gt;? Who knows? I guess we&amp;#8217;ll see&amp;#8230;&lt;/p&gt;

&lt;h2 id=&quot;so-back-to-uringmachine&quot;&gt;So, Back to UringMachine&lt;/h2&gt;

&lt;p&gt;This week I continued work on benchmarking UringMachine against other
concurrency solutions. I also spent more time testing the functioning of the
UringMachine fiber scheduler. I noticed that when using an &lt;code&gt;IO&lt;/code&gt; with buffered
writes (which is the default in Ruby,) when the IO&amp;#8217;s write buffer is flushed,
the fiber scheduler&amp;#8217;s &lt;code&gt;#io_write&lt;/code&gt; hook would not get invoked. Instead, Ruby
would invoke the &lt;code&gt;#blocking_operation_wait&lt;/code&gt; hook, which run a blocking &lt;code&gt;write&lt;/code&gt;
system call on a worker thread. This, of course, is far from being satisfactory,
as io_uring lets us run file I/O asynchronously. So I submitted a
&lt;a href=&quot;https://github.com/ruby/ruby/pull/15609&quot;&gt;fix&lt;/a&gt; which has been already been
merged into the Ruby 4.0 release.&lt;/p&gt;

&lt;p&gt;Measuring how fast UringMachine goes led to some work on the UringMachine
low-level API implementation. For example, I added methods for doing vectorized
&lt;code&gt;write&lt;/code&gt; and &lt;code&gt;send&lt;/code&gt; (io_uring provides support for vectorized &lt;code&gt;send&lt;/code&gt; as of kernel
version 6.17). So, &lt;code&gt;UM#writev&lt;/code&gt; and &lt;code&gt;UM#sendv&lt;/code&gt; let you write or send multiple
buffers at once. I still need to measure how those methods stack up against the
other ways of writing tro a socket, but from some experiments I did this does
seem promising.&lt;/p&gt;

&lt;h2 id=&quot;between-uringmachine-and-the-ruby-gvl&quot;&gt;Between UringMachine and the Ruby GVL&lt;/h2&gt;

&lt;p&gt;While I was working on the UringMachine benchmarks, it occurred to me that the
UM implementation of I/O operations has one substantial shortcoming: while
UringMachine does a great job of switching between fibers that are runnable,
every once in a while it still has to enter the kernel in order to process
submission entries, and completion entries.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s back up a bit: the way UringMachine works is that when a fiber calls one
of the UringMachine I/O methods, UringMachine prepares a submission queue entry
(SQE), and switches execution to the next fiber in the runqueue. When the
runqueue is exhausted, UringMachine calls, depending on the context,
&lt;code&gt;io_uring_submit&lt;/code&gt; or &lt;code&gt;io_uring_wait_for_cqe&lt;/code&gt; in order to tell the kernel to
process pending entries in the submission queues, and also to wait for at least
one completion entry (CQE) to become available.&lt;/p&gt;

&lt;p&gt;Those functions to interact with io_uring (from
&lt;a href=&quot;https://github.com/axboe/liburing/&quot;&gt;liburing&lt;/a&gt;, which provides a nicer interface
to the io_uring kernel API), are actually wrappers to the &lt;code&gt;io_uring_enter&lt;/code&gt;
system call, and therefore are potentially blocking. In order to play nice with
Ruby, UringMachine will release the Ruby GVL while this system call is in
progress, and will reacquire the Ruby GVL when the system call returns.&lt;/p&gt;

&lt;p&gt;What this means is that with UringMachine, when there&amp;#8217;s no more CPU-bound work
to do, and all fibers are waiting on some I/O operation to complete,
UringMachine will &amp;#8220;stop the world&amp;#8221; and wait for one or more I/O operations to
complete. It follows then that in a single-thread setup, the GVL will be
periodically unused for a certain amount of time, depending on the I/O work
being done. Naturally, the exact amount of time the GVL is available will be
reduced as the I/O load increases, but it will still be there, and therefore the
GVL as a resource will never be 100% saturated.&lt;/p&gt;

&lt;p&gt;So, if we ran multiple instances of UringMachine on separate threads, we might
be able to better saturate the GVL and thus achieve higher throughput for our
I/O workload. This, at least was my theory.&lt;/p&gt;

&lt;p&gt;It should be noted that up until now, when discussing the different solutions
for concurrency in Ruby, we&amp;#8217;ve been talking about &lt;em&gt;either&lt;/em&gt; threads &lt;em&gt;or&lt;/em&gt; fibers.
The basic approach says: you can &lt;em&gt;either&lt;/em&gt; run your workload on multiple threads,
where you&amp;#8217;ll enjoy some parallelism when any thread does I/O work; &lt;em&gt;or&lt;/em&gt; you can
run your workload on a single thread using multiple fibers, which lets you run a
very large number of concurrent I/O operations and amortize some of the cost of
talking to the kernel over multiple operations (preferably using io_uring).&lt;/p&gt;

&lt;p&gt;But in fact those two approaches have important shortcomings: with threads
you&amp;#8217;re limited in terms of how many I/O operations you can do concurrently, and
you pay a heavy price on GVL contention (that is, you&amp;#8217;ll eventually see worse
performance as you increase the number of threads); with fibers, your CPU-bound
code is penalized because you need to periodically &amp;#8220;stop the world&amp;#8221; and talk to
the kernel in order to process your I/O.&lt;/p&gt;

&lt;p&gt;But what would happen if we actually used multiple threads, and on each thread
we ran multiple fibers? What if it wasn&amp;#8217;t an either / or situation, but rather
threads &lt;em&gt;and&lt;/em&gt; fibers?&lt;/p&gt;

&lt;h2 id=&quot;whats-faster-than-fibers-threads--fibers&quot;&gt;What&amp;#8217;s Faster than Fibers? Threads + Fibers!&lt;/h2&gt;

&lt;p&gt;So eager to test my theory, I rolled my sleeves and added some code to the
UringMachine benchmarks. I wanted to test how UringMachine performs when the
workload is split across 2 or more threads. Here&amp;#8217;s the code that drives the
implementation:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_um_x2&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;threads&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;do_um_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await_fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;threads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here&amp;#8217;s the &lt;code&gt;do_um_x&lt;/code&gt; code for the &lt;code&gt;bm_io_pipe&lt;/code&gt; benchmark:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_um_x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GROUPS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;ITERATIONS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DATA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;ITERATIONS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close_async&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Basically, it does the same thing, but since it&amp;#8217;s invoked for each thread, we
divide the number of &amp;#8220;groups&amp;#8221; by the number of threads, so if we have say 2
threads and 48 groups in total, on each thread we&amp;#8217;ll start 24 of them. Here are
the results:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;                    user     system      total        real
Threads         4.638775   5.723567  10.362342 (  9.306601)
Async uring     2.197082   1.101110   3.298192 (  3.298313)
Async uring x2  2.471700   1.186602   3.658302 (  3.654717)
UM FS           1.167294   0.668348   1.835642 (  1.835746)
UM FS x2        1.169006   0.726825   1.895831 (  1.208773)
UM              0.430376   0.666198   1.096574 (  1.096809)
UM x2           0.463280   0.708560   1.171840 (  0.622890)
UM x4           0.589586   0.995353   1.584939 (  0.795669)
UM x8           0.889519   1.210246   2.099765 (  1.251179)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we just look at the pure UM implementations, we see the 2 threads version
providing a significant speedup over the single thread version (0.62s vs. 1.10s,
or ~1.77 times faster). But we also see that as we increase the thread count
we got dimishing returns, as the 4 thread version is slower than the 2 thread
version, and the 8 thread version is even slower than the single thread version.&lt;/p&gt;

&lt;p&gt;Looking at the fiber scheduler implementations, we see the Async fiber scheduler
actually performing worse with 2 threads, and the UM fiber scheduler performing
somewhat better (1.21s vs. 1.84s, about 1.52 times faster).&lt;/p&gt;

&lt;p&gt;Running the &lt;code&gt;bm_io_socketpair&lt;/code&gt; benchmark will yield a somewhat different result:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;                    user     system      total        real
Threads         3.107412   4.375018   7.482430 (  5.518199)
Async uring     1.168644   2.116141   3.284785 (  3.285159)
Async uring x2  2.115308   4.267043   6.382351 (  4.233598)
UM FS           1.002312   1.910953   2.913265 (  2.913601)
UM FS x2        2.077869   4.041456   6.119325 (  3.984509)
UM              0.319183   1.618411   1.937594 (  1.937892)
UM x2           0.367900   1.700580   2.068480 (  1.114170)
UM x4           0.431765   1.881608   2.313373 (  0.768043)
UM x8           0.497737   2.286719   2.784456 (  0.831759)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we see the UringMachine fiber scheduler also performing worse in a
multithreaded setup. This might be due to the fact that the fiber scheduler
interface does not have hooks for socket operations such as &lt;code&gt;send&lt;/code&gt; and &lt;code&gt;recv&lt;/code&gt;,
and it only participates in the actual I/O operations via its &lt;code&gt;io_wait&lt;/code&gt; hook,
which is invoked to check for readiness before Ruby performs the actual socket
I/O.&lt;/p&gt;

&lt;p&gt;Looking at the pure UringMachine implementation, we also see a performance
increase as we increase the number of threads, but eventually we&amp;#8217;ll see a
performance degradation as we go past 4 threads.&lt;/p&gt;

&lt;p&gt;So, this technique of starting up multiple threads, each with its UringMachine
instance and multiple concurrent fibers, definitely has value, but the actual
performance increase we&amp;#8217;ll get, and the ideal number of threads we start,
is highly dependant on the actual workload.&lt;/p&gt;

&lt;p&gt;I wanted to go a bit beyond those synthetic benchmarks and see how UM does under
a more realistic scenario - that of a web server. So I sketched a very simple
web server that listens for incoming connections, parses HTTP requests, and
sends response headers followed by a response body. I then measured how it did
using &lt;a href=&quot;https://github.com/lnx-search/rewrk&quot;&gt;rewrk&lt;/a&gt; with the following command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rewrk &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 256 &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; 4 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; 10s &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt; http://127.0.0.1:1234
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Thread count&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;reqs/s&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;latency-avg&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;latency-stdev&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;latency-min&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;latency-max&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;103917&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.46ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.70ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.30ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;39.00ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;184753&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.38ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.47ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.05ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;39.60ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;216203&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.18ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.46ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.04ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;42.49ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;208420&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.23ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.48ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.04ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;35.80ms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;205969&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.24ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.53ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.05ms&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;50.64ms&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;(The benchmark source code - it&amp;#8217;s a just a rough sketch! - is
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine/blob/main/benchmark/http_server_multi_accept.rb&quot;&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;So, putting aside the fact this sketch of a web server doesn&amp;#8217;t do any CPU-bound
work, like rendering templates, for example, we can see that we can more than
double the throughput if we spread the workload over multiple threads. What I
also find interesting is that the latency numbers actually improve as we go
beyond a single thread. But, as we saw in the benchmarks above, we finally hit
diminishing returns as we continue to increase the number of threads.&lt;/p&gt;

&lt;h2 id=&quot;the-sidecar-thread-scenario&quot;&gt;The Sidecar Thread Scenario&lt;/h2&gt;

&lt;p&gt;Another possibility that occured to me as I thought about combining threads and
fibers, is a sidecar setup, where we have one thread that runs the fibers, and
an auxiliary, or sidecar, thread that calls the kernel on behalf of the primary
thread.&lt;/p&gt;

&lt;p&gt;In this setup, we have a single UringMachine instance running on a primary
thread, handling the workload in multiple fibers, and it&amp;#8217;s basically CPU-bound.
The UringMachine instance starts an auxiliary thread that runs in a loop,
invoking the &lt;code&gt;io_uring_enter&lt;/code&gt; system call repeatedly in order to submit new I/O
operations, and processing completions as they arrive.&lt;/p&gt;

&lt;p&gt;It remains to be seen how this will perform, and hopefully I&amp;#8217;ll be able to
report on this next week.&lt;/p&gt;

&lt;h2 id=&quot;better-buffer-management-in-uringmachine&quot;&gt;Better Buffer Management in UringMachine&lt;/h2&gt;

&lt;p&gt;My work on vectorized write/send this week also ties in with the future work I
intend to do on buffer management. To make a long story short, io_uring provides
the possibility to register buffer rings where the application can create
multiple buffer rings and then dynamically add buffers to those buffer rings for
them to be used for sending or receiving to sockets.&lt;/p&gt;

&lt;p&gt;UringMachine already supports setting up buffer rings, but in a somewhat
primitive form: to use a buffer ring you need to first call
&lt;code&gt;UM#setup_buffer_ring&lt;/code&gt;, which takes a buffer size and a buffer count and returns
a buffer group id. You can then use this buffer group to receive messages
from a socket:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# setup a buffer ring 1024 buffers with a size of 64KB each:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bgid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setup_buffer_ring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# receive using the buffer ring:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv_each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bgid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process_msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What&amp;#8217;s nice about this is that you can setup a single buffer ring and use it to
receive on multiple sockets at once. The problem with the current implementation
is that there&amp;#8217;s no way to know if the buffer ring is close to being exhausted,
and thus you risk getting a &lt;code&gt;ENOBUFS&lt;/code&gt; error.&lt;/p&gt;

&lt;p&gt;Also, while you can also send messages with buffer rings (using the
&lt;code&gt;UM#send_bundle&lt;/code&gt; method), it seems to me that this has limited utility since you
cannot use a single buffer ring to send on multiple sockets at once. That&amp;#8217;s why
I did the work on vectorized write/send, which performs similarly to
&lt;code&gt;UM#send_bundle&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my plan is to provide an API to make the buffer management completely
transparent, so you&amp;#8217;ll be able to setup a buffer pool (for reading/receiving
only), which will automatically manage buffer groups. It will also automatically
add and reuse buffers as needed. It will probably look something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# setup a buffer pool that allocates 64KB buffers&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buffer_pool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BufferPool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;buffer_size: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv_each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_pool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The idea is to minimize buffer allocations by reusing buffers as much as
possible. In more recent kernels, io_uring can optimize buffer use by being able
to partially use buffers, so even if we allocate large buffers, they will be
incrementally consumed by io_uring.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In just five days, we&amp;#8217;ll all get a very nice Christmas gift - Ruby 4.0! If you
want to get a sense of all the good stuff that&amp;#8217;s arriving, just take a look at
the &lt;a href=&quot;https://github.com/ruby/ruby/pulls&quot;&gt;activity&lt;/a&gt; on the &lt;code&gt;ruby/ruby&lt;/code&gt; repo.
There&amp;#8217;s so many bug fixes and so many improvements on there, it&amp;#8217;s really
mind-boggling!&lt;/p&gt;

&lt;p&gt;What I intend to do next week, between spending time with the family and seeing
friends, is to continue to explore the combination of threads and fibers, and to
start working on my idea for a fiber pool for automatically managing buffers for
reading/receiving.&lt;/p&gt;

&lt;p&gt;Beyond that, beginning in January I plan to start working on OpenSSL integration
in UringMachine. I already have some ideas on how to do this, but we&amp;#8217;ll discuss
them when the time arrives.&lt;/p&gt;

&lt;p&gt;Merry Christmas!&lt;/p&gt;
</description></item><item><title>OSS Friday Update - Fibers are the Future of Ruby</title><link>https://noteflakes.com/articles/2025-12-12-friday-update</link><guid>https://noteflakes.com/articles/2025-12-12-friday-update</guid><pubDate>Fri, 12 Dec 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last few days I&amp;#8217;ve managed to finalize work on the UringMachine fiber
scheduler. Beyond making sure the fiber scheduler is feature complete, that is,
it implements all the different Fiber Scheduler hooks and their expected
behaviour. To make sure of this, I also spent a couple of days writing test
cases, not only of the fiber scheduler, but also of UM&amp;#8217;s low-level API.&lt;/p&gt;

&lt;p&gt;Beyond the tests, I wrote a series of benchmarks to have an idea of how
UringMachine compares to other concurrency solutions:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/digital-fabric/uringmachine/0c1f85129fee295de5a10f6536edff8f3c1fc9a6/benchmark/chart.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can consult the full results
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine/blob/main/benchmark/README.md&quot;&gt;here&lt;/a&gt;.
I&amp;#8217;ll refrain from making overly generalized statements about what these
benchmark results mean, but I think they demonstrate the promise of working with
fibers to create concurrent Ruby apps.&lt;/p&gt;

&lt;p&gt;So, as these benchmarks show, the Fiber Scheduler can bring significant benefits
to concurrent Ruby apps, with minimal changes to the code (basically, instead of
&lt;code&gt;Thread.new&lt;/code&gt; you&amp;#8217;ll use &lt;code&gt;Fiber.schedule&lt;/code&gt;). The fact that the scheduler does the
I/O transparently behind the scenes and integrates with the rest of the Ruby
ecosystem feels almost like magic.&lt;/p&gt;

&lt;p&gt;So I think this really validates the approach of Samuel Williams in designing
how the fiber scheduler interfaces with the rest of the Ruby runtime. And the
fact that the web server he authored,
&lt;a href=&quot;https://github.com/socketry/falcon/&quot;&gt;Falcon&lt;/a&gt;, is now used in production at
Shopify, is an even stronger validation!&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a detailed report of my work this last week:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Samuel has &lt;a href=&quot;https://github.com/ruby/ruby/pull/15428&quot;&gt;fixed&lt;/a&gt; the issue with the
hanging &lt;code&gt;#pwrite&lt;/code&gt; (it turns out the the &lt;code&gt;#io_pwrite&lt;/code&gt; hook was being invoked
with the GVL released.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added support for &lt;a href=&quot;https://unixism.net/loti/tutorial/sq_poll.html&quot;&gt;SQPOLL
mode&lt;/a&gt; when setting up a
UringMachine instance. It&amp;#8217;s not clear to me what are the performance
implications of that, but I&amp;#8217;ll try to make some time to check this against
&lt;a href=&quot;https://github.com/noteflakes/tp2&quot;&gt;TP2&lt;/a&gt;, a UringMachine-based web server I&amp;#8217;m
currently using in a bunch of projects.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;started looking at getting &lt;code&gt;#io_close&lt;/code&gt; to work, and found out that Samuel has
already done the work, that is the code was already there, but was commented
out. Samuel explained that it was impossible to get it to work due to the
complexity of the implementation of &lt;code&gt;IO#close&lt;/code&gt;, and indeed when I tried it
myself I saw that in fact it was just not possible the way the IO state is
managed when an IO is closed. I then had the idea that maybe we could pass the
underlying fd instead of the IO object itself to the &lt;code&gt;#io_close&lt;/code&gt; hook. The
only issue is that this breaks the convention where the different &lt;code&gt;io_xxx&lt;/code&gt;
hooks take an io as their first argument. Nevertheless, I suggested this idea
to Samuel and gladly he accepted when he saw this is the only we can make this
hook work. Samuel then proceeded to prepare a
&lt;a href=&quot;https://github.com/ruby/ruby/pull/15434&quot;&gt;PR&lt;/a&gt; and merge it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added the &lt;code&gt;#io_close&lt;/code&gt; hook to the UringMachine fiber scheduler, as well as a
&lt;code&gt;#yield&lt;/code&gt; hook for dealing with thread interrupts in response to another
&lt;a href=&quot;https://github.com/ruby/ruby/pull/14700&quot;&gt;PR&lt;/a&gt; by Samuel. I also added missing
docs for the different methods in the fiber scheduler.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Spent a lot of time writing lots of tests for the fiber scheduler. I tried to
cover the entire &lt;code&gt;IO&lt;/code&gt; API - both class- and instance methods. I also wrote
some &amp;#8220;integration&amp;#8221; tests - different scenarios not unlike those in the
benchmarks, which exercise the different hooks in the fiber scheduler.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added some new APIs to help with testing: &lt;code&gt;UM#await_fibers&lt;/code&gt; is a method for
waiting for one or more fibers to terminate. Unlike &lt;code&gt;UM#join&lt;/code&gt;, it doesn&amp;#8217;t
return the return values of the given fibers, it just waits for them to
terminate. Another new API is &lt;code&gt;UM.socketpair&lt;/code&gt;, which is like
&lt;code&gt;Socket.socketpair&lt;/code&gt; except it returns raw fd&amp;#8217;s.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fixed some small issues in the UM fiber scheduler and in the UM low-level API
implementation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added and streamlined metrics that indicate the following:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;The ring size&lt;/li&gt;
      &lt;li&gt;Total number of ops&lt;/li&gt;
      &lt;li&gt;Total number of fiber switches&lt;/li&gt;
      &lt;li&gt;Total number of waits for CQEs&lt;/li&gt;
      &lt;li&gt;Current number of pending ops&lt;/li&gt;
      &lt;li&gt;Current number of unsubmitted ops&lt;/li&gt;
      &lt;li&gt;Current size of runqueue&lt;/li&gt;
      &lt;li&gt;Current number of transient ops&lt;/li&gt;
      &lt;li&gt;Current number of free ops&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;I also added some basic time measurements:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Total CPU time&lt;/li&gt;
      &lt;li&gt;Total time spent waiting for CQEs&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;These are off by default, but can be enabled by calling &lt;code&gt;UM#profile(true)&lt;/code&gt;.
I&amp;#8217;d like to do a lot more with profiling, like measuring the CPU time spent on
each fiber, but I&amp;#8217;m a bit apprehensive of the performance costs involved, as
getting the &lt;code&gt;CLOCK_THREAD_CPUTIME_ID&lt;/code&gt; clock is relatively slow, and then
managing this for each fiber means getting and setting a couple of instance
variables, which can &lt;em&gt;really&lt;/em&gt; slow things down. On top of that, I&amp;#8217;m not that
sure this is really needed.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-next-for-uringmachine&quot;&gt;What&amp;#8217;s Next for UringMachine&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;One of the ideas I discussed with Samuel is to add support for registered
buffers that integrates with the &lt;code&gt;IO::Buffer&lt;/code&gt; class. While UringMachine
already has support for buffer rings, it uses a custom implementation of
buffers. So I might start by converting this to use &lt;code&gt;IO::Buffer&lt;/code&gt; instead.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I&amp;#8217;d also like to do a bit more work on performance tuning the UringMachine
low-level API, specifically to be able to control the maximum number of fiber
context switches before doing I/O work, i.e. submitting ops and checking for
completions.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Beyond that, I also want to spend some time documenting the UringMachine API,
as it is sorely lacking, and I&amp;#8217;d like for other people to be able to play with
it.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description></item><item><title>OSS Friday Update - The Shape of Ruby I/O to Come</title><link>https://noteflakes.com/articles/2025-12-05-friday-update</link><guid>https://noteflakes.com/articles/2025-12-05-friday-update</guid><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;I&amp;#8217;m currently doing grant work for the Japanese Ruby Association on
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine&quot;&gt;UringMachine&lt;/a&gt;, a new Ruby gem
that provides a low-level API for working with
&lt;a href=&quot;https://unixism.net/loti/what_is_io_uring.html&quot;&gt;io_uring&lt;/a&gt;. As part of my work
I&amp;#8217;ll be providing weekly updates on this website. Here&amp;#8217;s what I did this week:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Last week I wrote about the work I did under the guidance of &lt;a href=&quot;https://github.com/ioquatix&quot;&gt;Samuel
Williams&lt;/a&gt; to improve the behavior of fiber
schedulers when forking. After some discussing the issues around forking with
Samuel, we decided that the best course of action would be to remove the fiber
scheduler after a fork. Samuel did work around &lt;a href=&quot;https://github.com/ruby/ruby/pull/15354&quot;&gt;cleaning up schedulers in
threads that terminate on fork&lt;/a&gt;, and
I submitted a &lt;a href=&quot;https://github.com/ruby/ruby/pull/15385&quot;&gt;PR&lt;/a&gt; for removing the
scheduler from the active thread on fork, as well as resetting the fiber to
blocking mode. This is my first contribution to Ruby core!&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I Continued implementing the missing fiber scheduler hooks:
&lt;code&gt;#fiber_interrupt&lt;/code&gt;, &lt;code&gt;#address_resolve&lt;/code&gt;, &lt;code&gt;#timeout_after&lt;/code&gt;. For the most part,
they were simple to implement. I probably spent most of my time figuring out
how to test these, rather than implementing them. Most of the hooks involve
just a few lines of code, with many of them consisting of a single line of
code, calling into the relevant UringMachine low-level API.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Implemented the &lt;code&gt;#io_select&lt;/code&gt; hook, which involved implementing a low-level
&lt;code&gt;UM#select&lt;/code&gt; method. This method took some effort to implement, since it needs
to handle an arbitrary number of file descriptors to check for readiness. We
need to create a separate SQE for each fd we want to poll. When one or more
CQEs arrive for polled fd&amp;#8217;s, we also need to cancel all poll operations that
have not completed.&lt;/p&gt;

    &lt;p&gt;Since in many cases, &lt;code&gt;IO.select&lt;/code&gt; is called with just a single IO, I also added
a special-case implementation of &lt;code&gt;UM#select&lt;/code&gt; that specifically handles a
single fd.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Implemented a worker pool for performing blocking operations in the scheduler.
Up until now, each scheduler started their own worker thread for performing
blocking operations for use in the &lt;code&gt;#blocking_operation_wait&lt;/code&gt; hook. The new
implementation uses a worker thread pool shared by all schedulers, with a
worker count limited to CPU count. Workers are started when needed.&lt;/p&gt;

    &lt;p&gt;I also added an optional &lt;code&gt;entries&lt;/code&gt; argument to set the SQE and CQE buffer
sizes when starting a new &lt;code&gt;UringMachine&lt;/code&gt; instance. The default size is 4096
SQE entries (liburing by default makes the CQE buffer size double that of the
SQE buffer). The blocking operations worker threads specify a value of 4 since
they only use their UringMachine instance for popping jobs off the job queue
and pushing the blocking operation result back to the scheduler.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added support for &lt;code&gt;file_offset&lt;/code&gt; argument in &lt;code&gt;UM#read&lt;/code&gt; and &lt;code&gt;UM#write&lt;/code&gt; in
preparation for implementing the &lt;code&gt;#io_pread&lt;/code&gt; and &lt;code&gt;#io_pwrite&lt;/code&gt; hooks. The
&lt;code&gt;UM#write_async&lt;/code&gt; API, which permits writing to a file descriptor without
waiting for the operation to complete, got support for specifying &lt;code&gt;length&lt;/code&gt; and
&lt;code&gt;file_offset&lt;/code&gt; arguments as well. In addition, &lt;code&gt;UM#write&lt;/code&gt; and &lt;code&gt;UM#write_async&lt;/code&gt;
got short-circuit logic for writes with a length of 0.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added support for specifying buffer offset in &lt;code&gt;#io_read&lt;/code&gt; and &lt;code&gt;#io_write&lt;/code&gt;
hooks, and support for timeout in &lt;code&gt;#block&lt;/code&gt;, &lt;code&gt;#io_read&lt;/code&gt; and &lt;code&gt;#io_write&lt;/code&gt; hooks.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I found and fixed a problem with how &lt;code&gt;futex_wake&lt;/code&gt; was done in the low-level
UringMachine code handling mutexes and queues. This fixed a deadlock in the
scheduler background worker pool where clients of the pool where not properly
woken after the submitted operation was done.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I finished work on the &lt;code&gt;#io_pread&lt;/code&gt; and &lt;code&gt;#io_pwrite&lt;/code&gt; hooks. Unfortunately, the
test for &lt;code&gt;#io_pwrite&lt;/code&gt; consistently hangs (not in &lt;code&gt;IO#pwrite&lt;/code&gt; itself, rather on
closing the file.) With Samuel&amp;#8217;s help, hopefully we&amp;#8217;ll find a solution&amp;#8230;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;With those two last hooks, the fiber scheduler implementation is now feature
complete!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-is-the-fiber-scheduler-important&quot;&gt;Why is The Fiber Scheduler Important?&lt;/h2&gt;

&lt;p&gt;I think there is some misunderstanding around the Ruby fiber scheduler
interface. This is the only Ruby API that does not have a built-in
implementation in Ruby itself, but rather requires an external library or gem.
The question has been raised lately on Reddit, why doesn&amp;#8217;t Ruby include an
&amp;#8220;official&amp;#8221; implementation of the fiber scheduler?&lt;/p&gt;

&lt;p&gt;I guess Samuel is really the person to ask this, but personally I would say this
is really about experimentation, and seeing how far we can take the idea of a
pluggable I/O implementation. Also, the open-ended design of this interface
means that we can use a low-level API such as UringMachine to implement it.&lt;/p&gt;

&lt;h2 id=&quot;whats-coming-next-week&quot;&gt;What&amp;#8217;s Coming Next Week?&lt;/h2&gt;

&lt;p&gt;Now that the fiber scheduler is feature complete, I&amp;#8217;m looking to make it as
robust as possible. For this, I intend to add a lot of tests. Right now, the
fiber scheduler has 25 tests with 77 assertions, in about 560LOC (the fiber
scheduler itself is at around 220LOC). To me this is not enough, so next week
I&amp;#8217;m going to add tests for the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;IO - tests for all IO instance methods.&lt;/li&gt;
  &lt;li&gt;working with queues: multiple concurrent readers / writers.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;net/http&lt;/code&gt; test: ad-hoc HTTP/1.1 server + &lt;code&gt;Net::HTTP&lt;/code&gt; client.&lt;/li&gt;
  &lt;li&gt;sockets: echo server + many clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conjunction with all those tests, I&amp;#8217;ll also start working on benchmarks for
measuring the performance of the UringMachine low-level API against the
UringMachine fiber scheduler and against the &amp;#8220;normal&amp;#8221; thread-based Ruby APIs.&lt;/p&gt;

&lt;p&gt;In addition, I&amp;#8217;m working on a pull request for adding an &lt;code&gt;#io_close&lt;/code&gt; hook to the
fiber scheduler interface in Ruby. Samuel already did some preparation for this,
so I hope I can finish this in time for it to be merged in time for the release
of Ruby 4.0.&lt;/p&gt;

&lt;p&gt;I intend to release UringMachine 1.0 on Christmas, to mark the release of Ruby
4.0.&lt;/p&gt;

&lt;h2 id=&quot;what-about-papercraft&quot;&gt;What About Papercraft?&lt;/h2&gt;

&lt;p&gt;This week I also managed to take the time to reflect on what I want to do next
in &lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt;. I already wrote
here about wanting to implement template inlining for Papercraft. I also wanted
to rework how the compiled code is generated. I imagined a kind of DSL for code
generation, but I didn&amp;#8217;t really know what such a DSL would look like.&lt;/p&gt;

&lt;p&gt;Then, a few days ago, the idea hit me. I&amp;#8217;ve already played with this idea a last
year, when I wrote Sirop, a sister gem to Papercraft that does a big part of the
work of converting code into AST&amp;#8217;s and vice versa. Here&amp;#8217;s what I put in the
readme:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Future directions: implement a macro expander with support for quote/unquote:&lt;/p&gt;

  &lt;p&gt;trace_macro = Sirop.macro do |ast|
  source = Sirop.to_source(ast)
  quote do
    result = unquote(ast)
    puts &amp;#8220;The result of #{source} is: #{result}&amp;#8221;
    result
  end
end&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;def add(x, y)
  trace(x + y)
end&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Sirop.expand_macros(method(:add), trace: trace_macro)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The example is trivial and contrived, but I suddenly understand how such an
interface could be used to actually generate code in Papercraft. I wrote up an
&lt;a href=&quot;https://github.com/digital-fabric/sirop/issues/1&quot;&gt;issue&lt;/a&gt; for this, and
hopefully I&amp;#8217;ll have some time to work on this in January.&lt;/p&gt;
</description></item><item><title>OSS Friday Update - The Fiber Scheduler is Taking Shape</title><link>https://noteflakes.com/articles/2025-11-28-friday-update</link><guid>https://noteflakes.com/articles/2025-11-28-friday-update</guid><pubDate>Fri, 28 Nov 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;This week I made substantial progress on the
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine&quot;&gt;UringMachine&lt;/a&gt; fiber scheduler
implementation, and also learned quite a bit about the inner workings of the
Ruby I/O layer. Following is my weekly report:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;I added some benchmarks measuring how the UringMachine mutex performs against
the stock Ruby Mutex class. It turns out the &lt;code&gt;UM#synchronize&lt;/code&gt; was much slower
than core Ruby &lt;code&gt;Mutex#synchronize&lt;/code&gt;. This was because the UM version was always
performing a futex wake before returning, even if no fiber was waiting to lock
the mutex. I rectified this by adding a &lt;code&gt;num_waiters&lt;/code&gt; field to &lt;code&gt;struct
um_mutex&lt;/code&gt;, which indicates the number of fibers currently waiting to lock the
mutex, and avoiding calling &lt;code&gt;um_futex_wake&lt;/code&gt; if it&amp;#8217;s 0.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I also noticed that the &lt;code&gt;UM::Mutex&lt;/code&gt; and &lt;code&gt;UM::Queue&lt;/code&gt; classes were marked as
&lt;code&gt;RUBY_TYPED_EMBEDDABLE&lt;/code&gt;, which means the underlying &lt;code&gt;struct um_mutex&lt;/code&gt; and
&lt;code&gt;struct um_queue&lt;/code&gt; were subject to moving. Obviously, you cannot just move a
futex var while the kernel is potentially waiting on it to change. I fixed
this by removing the &lt;code&gt;RUBY_TYPED_EMBEDDABLE&lt;/code&gt; flag.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added support for &lt;code&gt;IO::Buffer&lt;/code&gt; in all low-level I/O APIs, which also means
the fiber scheduler doesn&amp;#8217;t need to convert from &lt;code&gt;IO::Buffer&lt;/code&gt; to strings in
order to invoke the UringMachine API.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added a custom &lt;code&gt;UM::Error&lt;/code&gt; exception class raised on bad arguments or other
API misuse. I&amp;#8217;ve also added a &lt;code&gt;UM::Stream::RESPError&lt;/code&gt; exception class to be
instantiated on RESP errors. (commit 72a597d9f47d36b42977efa0f6ceb2e73a072bdf)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;I explored the fiber scheduler behaviour after forking. A fork done from a
thread where a scheduler was set will result in a main thread with the same
scheduler instantance. For the scheduler to work correctly after a fork, its
state must be reset. This is because sharing the same io_uring instance
between parent and child processes is not possible
(https://github.com/axboe/liburing/issues/612), and also because the child
process keeps only the fiber from which the fork was made as its main fiber
(the other fibers are lost).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;On Samuel&amp;#8217;s suggestions, I&amp;#8217;ve submitted a
&lt;a href=&quot;https://github.com/ruby/ruby/pull/15342&quot;&gt;PR&lt;/a&gt; for adding a
&lt;code&gt;Fiber::Scheduler#process_fork&lt;/code&gt; hook that is automatically invoked after a
fork. This is in continuation to the &lt;code&gt;#post_fork&lt;/code&gt; method. I still have a lot
to learn about working with the Ruby core code, but I&amp;#8217;m really excited about
the possibility of this PR (and the &lt;a href=&quot;https://github.com/ruby/ruby/pull/15213&quot;&gt;previous
one&lt;/a&gt; as well) getting merged in time
for the Ruby 4.0 release.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added two new low-level APIs for waiting on processes, instead of
&lt;code&gt;UM#waitpid&lt;/code&gt;, using the io_uring version of &lt;code&gt;waitid&lt;/code&gt;. The vanilla version
&lt;code&gt;UM#waitid&lt;/code&gt; returns an array containing the terminated process pid, exit
status and code. The &lt;code&gt;UM#waitid_status&lt;/code&gt; method returns a &lt;code&gt;Process::Status&lt;/code&gt;
with the pid and exit status. This method is present only if the
&lt;code&gt;rb_process_status_new&lt;/code&gt; function is available (see above).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Implemented &lt;code&gt;FiberScheduler#process_wait&lt;/code&gt; hook using &lt;code&gt;#waitid_status&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For the sake of completeness, I also added &lt;code&gt;UM.pidfd_open&lt;/code&gt; and
&lt;code&gt;UM.pidfd_send_signal&lt;/code&gt; for working with PID. A simple example:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;child_pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;fork&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pidfd_open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;child_pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pidfd_send_signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SIGUSR1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pid2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;waitid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;P_PIDFD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;WEXITED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Wrote a whole bunch of tests for &lt;code&gt;UM::FiberScheduler&lt;/code&gt;: socket I/O, file I/O,
mutex, queue, waiting for threads. In the process I discovered a lots of
things that can be improved in the way Ruby invokes the fiber scheduler.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;things-i-learned-this-week&quot;&gt;Things I Learned This Week&lt;/h2&gt;

&lt;p&gt;As I dive deeper into integrating UringMachine with the &lt;code&gt;Fiber::Scheduler&lt;/code&gt;
interface, I&amp;#8217;m discovering all the little details about how Ruby does I/O. As I
wrote last week, Ruby treats files differently than other &lt;code&gt;IO&lt;/code&gt; types, such as
sockets and pipes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For regular files, Ruby assumes file I/O can never be non-blocking (or async),
and thus invokes the &lt;code&gt;#blocking_operation_wait&lt;/code&gt; hook in order to perform the
I/O in a separate thread. With io_uring, of course, file I/O &lt;em&gt;is&lt;/em&gt;
asynchronous.&lt;/li&gt;
  &lt;li&gt;For sockets there are no specialized hooks, like &lt;code&gt;#socket_send&lt;/code&gt; etc. Instead,
Ruby makes the socket fd&amp;#8217;s non-blocking and invokes &lt;code&gt;#io_wait&lt;/code&gt; to wait for the
socket to be ready when performing a &lt;code&gt;send&lt;/code&gt; or &lt;code&gt;recv&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I find it interesting how io_uring breaks a lot of assumptions about how I/O
should be done. Basically, with io_uring you can treat &lt;em&gt;all&lt;/em&gt; fd&amp;#8217;s as blocking
(i.e. without the &lt;code&gt;O_NONBLOCK&lt;/code&gt; control flag), and you can use io_uring to
perform asynchrnous I/O on them, files included!&lt;/p&gt;

&lt;p&gt;It remains to be seen if in the future the Ruby I/O implementation could be
simplified to take full advantage of io_uring. Right now, the way things are
done in the core Ruby IO classes leaves a lot of performance opportunities on
the table. So, while the UringMachine fiber scheduler implementation will help
in integrating UringMachine with the rest of the Ruby ecosystem, to really do
high-performance I/O, one would still need to use UringMachine&amp;#8217;s low-level API.&lt;/p&gt;

&lt;h2 id=&quot;whats-coming-next-week&quot;&gt;What&amp;#8217;s Coming Next Week&lt;/h2&gt;

&lt;p&gt;Next week I hope to finish the fiber scheduler implementation by adding the last
few things that are missing: handling of timeout, the &lt;code&gt;#io_pread&lt;/code&gt; and
&lt;code&gt;io_pwrite&lt;/code&gt; hooks, and a few more minor features, as well as a lot more testing.&lt;/p&gt;

&lt;p&gt;I also plan to start benchmarking UringMachine and compare the performance of
its low-level API, the UringMachine fiber scheduler, and the regular
thread-based concurrent I/O.&lt;/p&gt;

&lt;p&gt;I also have some ideas for improvements to the UringMachine low-level
implementation, which hopefully I&amp;#8217;ll be able to report on next week.&lt;/p&gt;

&lt;p&gt;If you appreciate my OSS work, please consider becoming a
&lt;a href=&quot;https://github.com/sponsors/noteflakes&quot;&gt;sponsor&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>OSS Friday Update</title><link>https://noteflakes.com/articles/2025-11-21-friday-update</link><guid>https://noteflakes.com/articles/2025-11-21-friday-update</guid><pubDate>Fri, 21 Nov 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;Note: while my schedule is quite hectic these last few weeks, I&amp;#8217;ve taken the
decision to dedicate at least one day per week for developing open-source tools,
and henceforth I plan to post an update on my progress in this regard every
Friday evening. Here&amp;#8217;s the first update:&lt;/p&gt;

&lt;h2 id=&quot;uringmachine-grant-work&quot;&gt;UringMachine Grant Work&lt;/h2&gt;

&lt;p&gt;As I wrote here previously, a few weeks ago I learned I&amp;#8217;ve been selected as one
of the recipients of a &lt;a href=&quot;https://www.ruby.or.jp/en/news/20251030&quot;&gt;grant&lt;/a&gt; from the
Ruby Association in Japan, for working on UringMachine, a new gem that brings
low-level io_uring I/O to Ruby. For this project, I&amp;#8217;ve been paired with a
terrific mentor - &lt;a href=&quot;https://github.com/ioquatix/&quot;&gt;Samuel Williams&lt;/a&gt; - who is &lt;em&gt;the&lt;/em&gt;
authority on all things related to Ruby fibers. We&amp;#8217;ve had a talk about the
project and discussed the different things that I&amp;#8217;ll be able to work on. I&amp;#8217;m
really glad to be doing this project under his guidance.&lt;/p&gt;

&lt;p&gt;UringMachine implements a quite low-level API for working with I/O. You
basically work with raw file descriptors, you can spin up fibers for doing
multiple things concurrently, and there are low-level classes for mutexes and
queues (based on the io_uring implementation of the
&lt;a href=&quot;https://www.man7.org/linux/man-pages/man2/futex.2.html&quot;&gt;futex&lt;/a&gt; API).
Incidentally, I find it really cool that futexes can be used with io_uring to
synchronize fibers, with very low overhead.&lt;/p&gt;

&lt;p&gt;The problem with this, of course, is that this API is useless when you want to
use the standard Ruby I/O classes, or any third-party library that relies on
those standard classes.&lt;/p&gt;

&lt;p&gt;This is where the Ruby fiber scheduler comes into the picture. Early on in my
work on UringMachine, it occurred to me that the &lt;code&gt;Fiber::Scheduler&lt;/code&gt; added to
Ruby by Samuel is a perfect way to integrate such a low-level API with the Ruby
I/O layer and the entire Ruby ecosystem. An implementation of &lt;code&gt;Fiber::Scheduler&lt;/code&gt;
for UringMachine would use the different scheduler hooks to punt work to the
low-level UringMachine API.&lt;/p&gt;

&lt;p&gt;So this week I finally got around to making some progress on the UringMachine
fiber scheduler, and there&amp;#8217;s finally a basic working version that can do basic
I/O, as well as some other stuff like sleeping, waiting on blocking operations
(such as locking a mutex or waiting on a queue), and otherwise managing the life
cycle of a scheduler.&lt;/p&gt;

&lt;p&gt;This is also a learning process. The Ruby &lt;code&gt;IO&lt;/code&gt; class implementation is really
complex: the &lt;a href=&quot;https://github.com/ruby/ruby/blob/master/io.c&quot;&gt;&lt;code&gt;io.c&lt;/code&gt;&lt;/a&gt; file itself
is about 10K LOCs! I&amp;#8217;m still figuring out the mechanics of the fiber scheduler
as I go, and lots of things are still unclear, but I&amp;#8217;m taking it one step at a
time, and when I hit a snag I just try to take the problem apart and try to
understand what&amp;#8217;s going on. But now that I have moved from a rough sketch to
something that works and has some tests, I intend to continue working on it by
adding more and more tests and TDD&amp;#8217;ing my way to an implementation that is both
complete (feature-wise) and robust.&lt;/p&gt;

&lt;p&gt;Here are some of the things I&amp;#8217;ve learned while working on the fiber scheduler:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;When you call &lt;code&gt;Kernel.puts&lt;/code&gt;, the trailing newline character is actually
written separately (i.e. with a separate &lt;code&gt;write&lt;/code&gt; operation), which can lead to
unexpected output if for example you have multiple fibers writing to STDOUT at
the same time. To prevent this, Ruby seems to use a mutex (per IO instance) to
synchronize writes to the same IO.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There are inconsistencies in how different kinds of IO objects are handled,
with regards to blocking/non-blocking operation
(&lt;a href=&quot;https://linux.die.net/man/2/fcntl&quot;&gt;O_NONBLOCK&lt;/a&gt;):&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Files and standard I/O are blocking.&lt;/li&gt;
      &lt;li&gt;Pipes are non-blocking.&lt;/li&gt;
      &lt;li&gt;Sockets are non-blocking.&lt;/li&gt;
      &lt;li&gt;OpenSSL sockets are non-blocking.&lt;/li&gt;
    &lt;/ul&gt;

    &lt;p&gt;The problem is that for io_uring to function properly, the fds passed to it
should always be in blocking mode. To rectify this, I&amp;#8217;ve added code to the
fiber scheduler implementation that makes sure the IO instance is blocking:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;io_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;reset_nonblock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fileno&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EINTR&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;retry&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reset_nonblock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@ios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;key?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
  &lt;span class=&quot;vi&quot;&gt;@ios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;io_set_nonblock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A phenomenon I&amp;#8217;ve observed is that in some situations of multiple fibers doing
I/O, some of those I/O operations would raise an &lt;code&gt;EINTR&lt;/code&gt;, which should mean
the I/O operation was interrupted because of a signal sent to the process.
This is weird! I&amp;#8217;m still not sure where this is coming from, certainly
something I&amp;#8217;ll ask Samuel about.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;There&amp;#8217;s some interesting stuff going on when calling &lt;code&gt;IO#close&lt;/code&gt;. Apparently
there&amp;#8217;s a mutex involved, and I noticed two scheduler hooks are being called:
&lt;code&gt;#blocking_operation_wait&lt;/code&gt; which means a blocking operation that should be ran
on a separate thread, and &lt;code&gt;#block&lt;/code&gt;, which means a mutex is being locked. I
still need to figure out what is going on there and why it is so complex.
FWIW, UringMachine has a &lt;code&gt;#close_async&lt;/code&gt; method which, as its name suggests,
submits a close operation, but does not wait for it to complete.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;improving-and-extending-the-fiber-scheduler-interface&quot;&gt;Improving and extending the fiber scheduler interface&lt;/h2&gt;

&lt;p&gt;One of the things I&amp;#8217;ve discussed with Samuel is the possibility of extending the
fiber scheduler interface by adding more hooks, for example a hook for closing
an IO (from what I saw there&amp;#8217;s already some preparation for that in the Ruby
runtime), or a hook for doing a &lt;code&gt;splice&lt;/code&gt;. We&amp;#8217;ve also discussed working with
&lt;code&gt;pidfd_open&lt;/code&gt; to prevent race conditions when waiting on child processes. I think
there&amp;#8217;s still a lot of cool stuff that can be done by bringing low-level I/O
functionality to Ruby.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve also suggested to Samuel to use the relatively recent
&lt;code&gt;io_uring_prep_waitid&lt;/code&gt; API to wait for child processes, and more specifically to
do this in Samuel&amp;#8217;s own &lt;a href=&quot;https://github.com/socketry/io-event/&quot;&gt;io-event&lt;/a&gt; gem,
which provides a low-level cross-platform API For building async programs in
Ruby. With the io_uring version of &lt;code&gt;waitid&lt;/code&gt;, there&amp;#8217;s no need to use &lt;code&gt;pidfd_open&lt;/code&gt;
(in order to poll for readiness when the relevant process terminates). Instead,
we use the io_uring interface to directly wait for the process to terminate.
Upon termination, the operation completes and we get back the pid and status of
the terminated process. This is also has the added advantage that you can wait
for any child process, or any child process in the process group, which means
better compatibility with the &lt;code&gt;Process.wait&lt;/code&gt; and associated methods.&lt;/p&gt;

&lt;p&gt;One problem is that the fiber scheduler &lt;code&gt;process_wait&lt;/code&gt; hook is supposed to
return an instance of &lt;code&gt;Process::Status&lt;/code&gt;. This is a core Ruby class, but you
cannot create instances of it. So, if we use io_uring to directly wait for a
child process to terminate, we also need a way to instantiate a
&lt;code&gt;Process::Status&lt;/code&gt; object with the information we get back from io_uring. I&amp;#8217;ve
submitted a &lt;a href=&quot;https://github.com/ruby/ruby/pull/15213&quot;&gt;PR&lt;/a&gt; that hopefully will be
merged before the release of Ruby 4.0. I&amp;#8217;ve also submitted a
&lt;a href=&quot;https://github.com/socketry/io-event/pull/154&quot;&gt;PR&lt;/a&gt; to io-event with the
relevant changes.&lt;/p&gt;

&lt;h2 id=&quot;going-forward&quot;&gt;Going forward&lt;/h2&gt;

&lt;p&gt;So here&amp;#8217;s where the UringMachine project is currently at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The fiber scheduler &lt;a href=&quot;https://github.com/digital-fabric/uringmachine/blob/main/lib/uringmachine/fiber_scheduler.rb&quot;&gt;implementation&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;The fiber scheduler &lt;a href=&quot;https://github.com/digital-fabric/uringmachine/blob/main/test/test_fiber_scheduler.rb&quot;&gt;tests&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;My grant &lt;a href=&quot;https://github.com/digital-fabric/uringmachine/blob/main/grant-2025/journal.md&quot;&gt;development journal&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you appreciate my OSS work, please consider &lt;a href=&quot;https://github.com/sponsors/noteflakes&quot;&gt;sponsoring
me&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;my-consulting-work&quot;&gt;My Consulting Work&lt;/h2&gt;

&lt;p&gt;Apart from my open-source work, I&amp;#8217;m also doing consulting work for. Here&amp;#8217;s some
of the things I&amp;#8217;m currently working on for my clients:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Transitioning a substantial PostgreSQL database (~4.5TB of data) from RDS to
EC2. This is done strictly for the sake of reducing costs. My client should
see a reduction of about 1000USD/month.&lt;/li&gt;
  &lt;li&gt;Provisioning of machines for the RealiteQ web platform to be used for
industrial facilities in India.&lt;/li&gt;
  &lt;li&gt;Exploring the integration of AI tools for analyzing the performance of
equipment such as water pumps for water treatment facilities. I&amp;#8217;m still quite
sceptical about LLM&amp;#8217;s being the right approach for this. ML algorithms might
be a better fit. Maybe, we&amp;#8217;ll see&amp;#8230;&lt;/li&gt;
&lt;/ul&gt;

</description></item><item><title>You Win Some, You Lose Some: on Papercraft and more</title><link>https://noteflakes.com/articles/2025-11-11-win-some-lose-some</link><guid>https://noteflakes.com/articles/2025-11-11-win-some-lose-some</guid><pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last few weeks I&amp;#8217;ve been busy with a few different projects, and I guess
I&amp;#8217;m not the only freelancer who has trouble finding their work-life balance.
That is, in the last few weeks life has been hitting me in the face, and it was
a bit overwhelming. But I still did manage to make some progress on my work, and
thought I might share it here.&lt;/p&gt;

&lt;h2 id=&quot;papercraft-update&quot;&gt;Papercraft Update&lt;/h2&gt;

&lt;p&gt;Since &lt;a href=&quot;/articles/2025-10-20-papercraft-3&quot;&gt;releasing Papercraft 3.0&lt;/a&gt;, I&amp;#8217;ve been
busy preparing a talk on Papercraft for Paris.rb. To me this was a major
undertaking, since I&amp;#8217;ve near-zero experience doing conference talks, and for me
this was a test of my writing abilities, as well as my talking abilities. More
on that in a moment.&lt;/p&gt;

&lt;p&gt;I also managed to release a few more versions of Papercraft, which is now at
version 3.2.0. Here are the major changes since version 3.0:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Fixed compilation of ternary operator expressions, so stuff like &lt;code&gt;x ?
h1(&#39;foo&#39;) : h2(&#39;bar&#39;)&lt;/code&gt; will compile correctly.&lt;/li&gt;
  &lt;li&gt;Added an optional &lt;code&gt;Proc&lt;/code&gt; API, so you could do stuff like &lt;code&gt;template.html(...)&lt;/code&gt;
instead of calling &lt;code&gt;Papercraft.html(template, ...)&lt;/code&gt;. To use this API you need
to first &lt;code&gt;require &quot;papercraft/proc&quot;&lt;/code&gt;. This essentially restores the pre-3.0
API, but as an opt-in.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Added Tilt integration, so you could use Papercraft with Tilt.
&lt;a href=&quot;https://github.com/jeremyevans/tilt&quot;&gt;Tilt&lt;/a&gt; is a generic interface for using
different Ruby template engines. Here&amp;#8217;s an example of how to use it:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;tilt/papercraft&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Tilt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;papercraft&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;
    h1 locals[:a]
    p locals[:b]
    render block if block
  &quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;a: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;b: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;hr&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;foo&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;bar&amp;lt;/p&amp;gt;&amp;lt;hr&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-parisrb-talk&quot;&gt;The Paris.rb Talk&lt;/h2&gt;

&lt;p&gt;It actually took me a few weeks of writing and rewritng to finally arrive at a
talk that I found both interesting and to the point. I wanted to talk about
Papercraft and the functional style in Ruby, but without falling into the trap
of discussing all of the theoretical stuff around functional programming. I
mean, there&amp;#8217;s already a lot of that on YouTube, and I also find it kind of
tedious. I really prefer to stick to practical stuff, like what can we learn
from functional programming that makes us into better programmers.&lt;/p&gt;

&lt;p&gt;So, when the day came I took the train to Paris, had a couple hours to burn so I
just sat in a café and rehearsed the text over and over. I thought I had it. But
when the time finally came to give the talk later that evening, something
happened, something that hasn&amp;#8217;t happened to me in a while - I had a panic on
stage. Well, I didn&amp;#8217;t start screaming or anything, but as I started talking I
suddenly felt like I had no air. I tried to calm myself and breathe but it was
like my body was tightening into a coil, I felt like I was drowning. Somehow, I
pulled through, I just went through the text and the slides as best I could, and
as I got to the end, I had calmed down enough to be able to be present and
responsive to the people in the audience. There were some questions from the
audience, and I was calm enough to be able to answer, but inside I just felt
crushed and beaten.&lt;/p&gt;

&lt;p&gt;This, I mean stage fright, is something I&amp;#8217;ve been struggling with over the
years, but after the positive experience of my lightning talk at
&lt;a href=&quot;/articles/2025-09-23-euruko&quot;&gt;Euruko&lt;/a&gt; I thought I finally made some progress on
this front. This experience was so discouraging that afterwards I felt
completely empty and without energy. I still want to do more public speaking,
and I think I have interesting stuff to share, but every such negative
experience just adds to my predicament. Well, I guess I still have more work to
do, you win some, you lose some&amp;#8230;&lt;/p&gt;

&lt;p&gt;By the way, the Euruko talks are now available on YouTube. You can find my
Papercraft lightning talk
&lt;a href=&quot;https://www.youtube.com/watch?v=sgAysDO3mwU&amp;amp;t=1800s&quot;&gt;here&lt;/a&gt;. The slides for the
Paris.rb talk are &lt;a href=&quot;https://papercraft.noteflakes.com/talks/2025-11&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;uringmachine-grant-work&quot;&gt;UringMachine Grant Work&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve &lt;a href=&quot;/articles/2025-06-28-introducing-uringmachine&quot;&gt;written before&lt;/a&gt; about
&lt;a href=&quot;https://github.com/digital-fabric/uringmachine&quot;&gt;UringMachine&lt;/a&gt;, a Ruby gem
low-level I/O using io_uring. I&amp;#8217;m pleased to announce that I&amp;#8217;m the recipient of
a &lt;a href=&quot;https://www.ruby.or.jp/en/news/20251030&quot;&gt;grant&lt;/a&gt; for working on UringMachine
from the Ruby Association in Japan.&lt;/p&gt;

&lt;p&gt;In this project, I&amp;#8217;ll work on three things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Developing a &lt;code&gt;FiberScheduler&lt;/code&gt; implementation for UringMachine, in order to be
able to allow its use in any fiber-based Ruby application.&lt;/li&gt;
  &lt;li&gt;Bringing SSL/TLS capabilities to UringMachine, in order to allow building
high-performance clients and servers using encrypted connections.&lt;/li&gt;
  &lt;li&gt;Bringing more io_uring features to UringMachine, such as &lt;code&gt;writev&lt;/code&gt;, &lt;code&gt;splice&lt;/code&gt;,
&lt;code&gt;fsync&lt;/code&gt;, &lt;code&gt;fadvise&lt;/code&gt; etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I&amp;#8217;ll also take the time to work on documentation, benchmarks, and
correctness of the implementation.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ll write here regularly about my progress. The grant also requires me publish
a progress report around December, and then a final report on my work in March.&lt;/p&gt;

&lt;h2 id=&quot;a-new-client&quot;&gt;A New Client&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve just received a new commission for creating a blog website. The client is a
person I love deeply - my daughter Noa. She has some very specific requirements
regarding the design, functionality, and privacy concerns of the blog, so for
me this is a challenge to see how far I can take my &lt;a href=&quot;https://github.com/digital-fabric/syntropy&quot;&gt;personal web
framework&lt;/a&gt;, and how far I can push
the new ideas and techniques I&amp;#8217;ve been developing for my work.&lt;/p&gt;

&lt;p&gt;As I develop and discover solutions for the different problems this new project
presents, I&amp;#8217;ll try to generalize them and fold them into Syntropy, so I&amp;#8217;ll be
able to use them for other projects as well.&lt;/p&gt;
</description></item><item><title>Papercraft 3.0 Released</title><link>https://noteflakes.com/articles/2025-10-20-papercraft-3</link><guid>https://noteflakes.com/articles/2025-10-20-papercraft-3</guid><pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;I have just released &lt;a href=&quot;https://papercraft.noteflakes.com/&quot;&gt;Papercraft&lt;/a&gt; version
3.0. This release includes a new API for rendering templates, improved XML
support and an improved API for the &lt;code&gt;Papercraft::Template&lt;/code&gt; wrapper class. Below
is a discussion of the changes in this version, as well as what&amp;#8217;s coming in the
near future.&lt;/p&gt;

&lt;h2 id=&quot;a-new-rendering-api&quot;&gt;A New Rendering API&lt;/h2&gt;

&lt;p&gt;Papercraft 2.0 was all about embracing lambdas as the basic building block for
HTML templates. Papercraft 2.0 introduced automatic compilation of Papercraft
templates into an optimized form that provides best-in-class performance. The
two most important operations on templates were &lt;code&gt;#render&lt;/code&gt; and &lt;code&gt;#apply&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Papercraft 2.0:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# alternatively&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GreetWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GreetWorld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While this API is certainly very elegant and convenient, there was legitimate
concern among potential users that Papercraft was in effect extending the core
&lt;a href=&quot;https://rubyapi.org/o/proc&quot;&gt;&lt;code&gt;Proc&lt;/code&gt;&lt;/a&gt; class, with generic name methods that are
specific to Papercraft templates, while &lt;code&gt;Proc&lt;/code&gt;s are in fact used everywhere in a
given Ruby codebase, and not only for templates (everytime you call a method
with a &lt;em&gt;block&lt;/em&gt;, that block is in fact a &lt;code&gt;Proc&lt;/code&gt; instance).&lt;/p&gt;

&lt;p&gt;So, after giving it some thought I&amp;#8217;ve decided to change the Papercraft API such
that the act of rendering a template or applying arguments to a template will be
done with singelton methods on the &lt;code&gt;Papercraft&lt;/code&gt; module:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Papercraft 3.0:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# alternatively&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;GreetWorld&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;GreetWorld&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think this change is a big step forward for Papercraft. It demarcates an
important distinction between writing templates in the form of lambdas using the
Papercraft DSL, and the actual rendering (or application) of said templates,
which is done at the edges of the program, when those templates are actually
used.&lt;/p&gt;

&lt;p&gt;This change further embraces the functional style in Ruby, a style of
programming I&amp;#8217;ve been gravitating towards in the last few years, with an
emphasis on explicitness and conciseness. I like that &lt;code&gt;Papercraft.render&lt;/code&gt; and
&lt;code&gt;Papercraft.apply&lt;/code&gt; are simply functions that take templates (and optional
arguments) as input and return a string as output.&lt;/p&gt;

&lt;h2 id=&quot;improved-xml-support&quot;&gt;Improved XML Support&lt;/h2&gt;

&lt;p&gt;While Papercraft 2.0 was concerned exclusively with HTML, I&amp;#8217;ve decided to bring
back support for rendering XML, even if only for the sake of being able to
render RSS feeds. Version 3.0 introduces improved support for rendering XML. You
can now render XML templates by calling &lt;code&gt;Papercraft.xml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;articles&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;xml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Papercraft 3.0 also adds support for rendering self-closing XML tags, for
elements with no inner text or child nodes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;xml&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;ref: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;item ref=\&quot;foo\&quot;/&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-streamlined-papercrafttemplate-class&quot;&gt;A Streamlined Papercraft::Template Class&lt;/h2&gt;

&lt;p&gt;A few months ago, Papercraft version 2.4 introduced a wrapper class for
templates called &lt;code&gt;Papercraft::Template&lt;/code&gt;. The use case for this class was to be
able to distinguish between template &lt;code&gt;Proc&lt;/code&gt;s and non-template &lt;code&gt;Proc&lt;/code&gt;s. With the
new rendering API introduced in version 3.0, the &lt;code&gt;Papercraft::Template&lt;/code&gt; class
has also undergone some changes, with its interface streamlined and simplified:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;world&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also use this class to render XML templates, by passing &lt;code&gt;mode: :xml&lt;/code&gt; to
&lt;code&gt;Template.new&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;mode: :xml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;People that are have been using Papercraft since before version 2.0 API may
prefer to use the &lt;code&gt;Papercraft::Template&lt;/code&gt; class, which is in many ways similar to 
the original Papercraft API.&lt;/p&gt;

&lt;h2 id=&quot;coming-soon-support-for-inlining&quot;&gt;Coming Soon: Support for Inlining&lt;/h2&gt;

&lt;p&gt;When rendering complex HTML, and as your application grows, there&amp;#8217;s a natural
tendency to prefer to put separate parts of the markup in separate templates,
which are then composed together. Papercraft makes this very easy to do, whether
in the form of layouts, derived layouts, components or partials. But while the
quality of your code is improved, this may come at a significant cost to
rendering performance.&lt;/p&gt;

&lt;p&gt;This problem is not unique to Papercraft. Any templating solution, be it
&lt;a href=&quot;https://github.com/ruby/erb&quot;&gt;ERB&lt;/a&gt; or &lt;a href=&quot;https://www.phlex.fun/&quot;&gt;Phlex&lt;/a&gt; is going
to suffer from the same problem. ERB is especially
&lt;a href=&quot;https://github.com/rails/rails/issues/41452&quot;&gt;susceptible&lt;/a&gt; to this.&lt;/p&gt;

&lt;p&gt;One of the ideas I&amp;#8217;ve been exploring since the introduction of automatic
template compilation in Papercraft 2.0, was automatic inlining of sub-templates.
Consider the following example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Card&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;card&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Deck&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;deck&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Currently, Papercraft will optimize &lt;code&gt;Card&lt;/code&gt; and &lt;code&gt;Deck&lt;/code&gt; separately, and the
compiled &lt;code&gt;Deck&lt;/code&gt; template while call the compiled &lt;code&gt;Card&lt;/code&gt; template for each item:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# compiled code (edited for legibility)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;deck&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Card&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;__papercraft_compiled_proc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/deck&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If Papercraft were capable of inlining sub-templates, the compiled &lt;code&gt;Deck&lt;/code&gt;
template would have looked something like the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;deck&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;card&amp;gt;&amp;lt;h1&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html_escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html_escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])))&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/p&amp;gt;&amp;lt;/card&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/deck&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;its-asts-all-the-way-down&quot;&gt;It&amp;#8217;s AST&amp;#8217;s All the Way Down!&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve spent months thinking about this problem and had no clear idea of how this
could be implemented. A quick recap: when Papercraft compiles a template it does
it in three steps: first it loads the source code for the template and parses it
using &lt;a href=&quot;https://github.com/ruby/prism&quot;&gt;Prism&lt;/a&gt;, then the AST is mutated to convert
tag method calls to custom nodes, and finally the mutated AST is converted back
to optimized source code that is eval&amp;#8217;d to produce the compiled template proc.&lt;/p&gt;

&lt;p&gt;At first I presumed that inlining should be done at the last step, when
converting the mutated AST to source code. But I had no clear idea on how to do
this. Then, yesterday, I was making some notes for a talk I&amp;#8217;m preparing about
Papercraft and functional programming in Ruby, and I had a &lt;em&gt;Eureka&lt;/em&gt; moment when
I realized this could be solved by mutating and combining ASTs!&lt;/p&gt;

&lt;p&gt;If we look at each template in terms of an AST, instead of its source code, the
solution becomes clear: whenever we encounter a &lt;code&gt;CallNode&lt;/code&gt; with the tag &lt;code&gt;Card&lt;/code&gt;,
we can simply replace this node with the AST of the corresponding template.&lt;/p&gt;

&lt;p&gt;There&amp;#8217;s some work to be done around translating arguments between the original
call and the actual arguments used by the inlined AST, but this is certainly
doable. In addition, this technique can be applied not only to rendering
components, but also to the composition of layouts using &lt;code&gt;render_yield&lt;/code&gt;, or any
usage of &lt;code&gt;Papercraft.apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m really excited to be implementing this feature, and making Papercraft the
best &lt;em&gt;and fastest&lt;/em&gt; HTML templating engine for Ruby. In the meanwhile, feel free
to explore &lt;a href=&quot;https://papercraft.noteflakes.com/&quot;&gt;Papercraft&lt;/a&gt; and start using it
in your app.&lt;/p&gt;
</description></item><item><title>Papercraft update: IRB Support, Bug Fixes, More Speed</title><link>https://noteflakes.com/articles/2025-10-12-papercraft-updates</link><guid>https://noteflakes.com/articles/2025-10-12-papercraft-updates</guid><pubDate>Sun, 12 Oct 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;This week I was away on a little trip to Paris to attend a
&lt;a href=&quot;https://paris-rb.org/&quot;&gt;Paris.rb&lt;/a&gt; meetup and meet some friends, so I was less
productive, but still got some stuff done, and still managed to do some work on
&lt;a href=&quot;https://papercraft.noteflakes.com/&quot;&gt;Papercraft&lt;/a&gt;. Here&amp;#8217;s what&amp;#8217;s changed:&lt;/p&gt;

&lt;h2 id=&quot;using-papercraft-in-irb&quot;&gt;Using Papercraft in IRB&lt;/h2&gt;

&lt;p&gt;Up until now, perhaps the biggest limitation of Papercraft was that you couldn&amp;#8217;t
use it in an IRB session. That was because Papercraft always &lt;em&gt;compiles&lt;/em&gt; your
templates, and for that it needs access to the templates&amp;#8217; source code. But if
you&amp;#8217;re defining a template in IRB, where is that source code?&lt;/p&gt;

&lt;p&gt;Then, while taking the train to Paris, it occurred to me that maybe IRB keeps
the lines of code you input into it somewhere, and maybe it would be possible to
access those lines of code. It took a bit of digging, but finally I&amp;#8217;ve &lt;a href=&quot;https://github.com/digital-fabric/sirop/blob/eec1c528475dbdd0e4ddfe898abb23e10f57a45d/lib/sirop.rb#L82-L86&quot;&gt;found it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a result, you can now define ad-hoc Papercraft templates right in your IRB
session:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sharon@nf1:~$ irb -rpapercraft
irb(main):001&amp;gt; -&amp;gt;{ h1 &quot;Hello, IRB!&quot; }.render
=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, IRB!&amp;lt;/h1&amp;gt;&quot;
irb(main):002&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It&amp;#8217;s fun to be able to explore Papercraft templates in IRB, enjoy!&lt;/p&gt;

&lt;h2 id=&quot;some-bug-fixes&quot;&gt;Some Bug Fixes&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve also had the time to deal with some edge cases I discovered while using
Papercraft:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Fix compilation of an empty template:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Raise error on a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/Void_element&quot;&gt;void
element&lt;/a&gt; with
child nodes or inner text:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; !!! Papercraft::Error&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; !!! Papercraft::Error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fix &lt;code&gt;apply&lt;/code&gt; parameter handling when called with a block:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_yield&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;p: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_yield&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;q: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;43&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:q&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;body&amp;gt;&amp;lt;article&amp;gt;&amp;lt;h1 class=\&quot;42\&quot;&amp;gt;foo&amp;lt;/h1&amp;gt;&amp;lt;h2 class=\&quot;43\&quot;&amp;gt;bar&amp;lt;/h2&amp;gt;&amp;lt;/article&amp;gt;&amp;lt;/body&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;html-escaping-gets-a-speed-boost&quot;&gt;HTML Escaping Gets a Speed Boost&lt;/h2&gt;

&lt;p&gt;Last but not least, I was looking at the source code to
&lt;code&gt;ERB::Escape.html_escape&lt;/code&gt;, which is the method Papercraft uses to escape all
HTML content (in order to prevent HTML injection). I figured the implementation
could be improved, and opened a &lt;a href=&quot;https://github.com/ruby/erb/pull/87&quot;&gt;PR&lt;/a&gt; with the proposed change:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The existing &lt;code&gt;html_escape&lt;/code&gt; implementation always allocates buffer space (6
times the length of the input string), even when the input string does not
contain any character that needs to be escaped.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;This PR modifies the implementation of &lt;code&gt;optimized_escape_html&lt;/code&gt; to not
pre-allocate an output buffer, but instead allocate it on the first occurence
of a character that needs escaping. In addition, instead of copying
non-escaped characters one by one to the output buffer, continuous non-escaped
segments of characters are copied using &lt;code&gt;memcpy&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;A synthetic benchmark employing the input strings used in the
&lt;code&gt;test_html_escape&lt;/code&gt; method in &lt;code&gt;test/test_erb.rb&lt;/code&gt; shows the modified
implementation to be about 35% faster than the original&amp;#8230;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The PR was merged and a new version of ERB is already
&lt;a href=&quot;https://rubygems.org/gems/erb&quot;&gt;released&lt;/a&gt;. And the best part - this speed up is
available to the entire Ruby ecosystem, not just Papercraft. Everybody that uses
ERB (or its &lt;code&gt;html_escape&lt;/code&gt; method) will benefit from this!&lt;/p&gt;
</description></item><item><title>Hanami on Papercraft</title><link>https://noteflakes.com/articles/2025-10-05-papercraft-hanami</link><guid>https://noteflakes.com/articles/2025-10-05-papercraft-hanami</guid><pubDate>Sun, 05 Oct 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;Lately I&amp;#8217;ve been really excited about Papercraft and the possibilities it brings
to developing web apps with Ruby. Frankly, the more I use it, the more I see how
simple and joyful it can be to write beautiful HTML templates in plain Ruby.&lt;/p&gt;

&lt;p&gt;Now that the &lt;a href=&quot;https://papercraft.noteflakes.com/&quot;&gt;Papercraft website&lt;/a&gt; is up, I&amp;#8217;d
like to concentrate on making it easier for everyone to use Papercraft in their
apps, whatever their web framework. So this is exactly what I set out to do this
weekend. First on my list: &lt;a href=&quot;https://hanamirb.org/&quot;&gt;Hanami&lt;/a&gt;, an established Ruby
web framework with a substantial following.&lt;/p&gt;

&lt;p&gt;Since I never used Hanami, I decided to follow the &lt;a href=&quot;https://guides.hanamirb.org/v2.3/introduction/getting-started/&quot;&gt;Getting Started
guide&lt;/a&gt; and then
started to peek under the hood to see how I could replace the ERB templates with
Papercraft ones.&lt;/p&gt;

&lt;p&gt;After a few hours and a quite a bit of fiddling, I had a working proof of
concept. I then proceeded to extract the code into a new gem I&amp;#8217;m releasing today called &lt;a href=&quot;https://github.com/digital-fabric/hanami-papercraft&quot;&gt;hanami-papercraft&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use it, do the following:&lt;/p&gt;

&lt;h2 id=&quot;1-add-hanami-papercraft&quot;&gt;1. Add hanami-papercraft&lt;/h2&gt;

&lt;p&gt;In your &lt;code&gt;Gemfile&lt;/code&gt;, add the following line:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hanami-papercraft&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then run &lt;code&gt;bundle install&lt;/code&gt; to update your dependencies.&lt;/p&gt;

&lt;h2 id=&quot;2-set-your-apps-basic-view-class&quot;&gt;2. Set your app&amp;#8217;s basic view class&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;app/view.rb&lt;/code&gt;, change the &lt;code&gt;View&lt;/code&gt; classes superclass to &lt;code&gt;Hanami::PapercraftView&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/view.rb&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Bookshelf&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;View&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Hanami&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PapercraftView&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-use-a-papercraft-layout-template&quot;&gt;3. Use a Papercraft layout template&lt;/h2&gt;

&lt;p&gt;Replace the app&amp;#8217;s layout template stored in &lt;code&gt;app/templates/layouts/app.html.erb&lt;/code&gt;
with a file named &lt;code&gt;app/templates/layouts/app.papercraft.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/templates/layouts/app.papercraft.rb&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;lang: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;charset: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;viewport&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;width=device-width, initial-scale=1.0&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bookshelf&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;favicon_tag&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;stylesheet_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render_children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;javascript_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;4-use-papercraft-view-templates&quot;&gt;4. Use Papercraft view templates&lt;/h2&gt;

&lt;p&gt;You can now start writing your view templates with Papercraft, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/templates/books/index.papercraft.rb&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Books&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;books&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;book&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;passing-template-parameters&quot;&gt;Passing Template Parameters&lt;/h2&gt;

&lt;p&gt;While theoretically you have access to the view class in your templates (through
&lt;code&gt;self&lt;/code&gt;), you should use explicit arguments in your templates, as shown in the
examples above. The &lt;code&gt;PapercraftView&lt;/code&gt; class always passes template parameters as
keyword arguments to the layout and the view templates.&lt;/p&gt;

&lt;p&gt;In the view template above, the &lt;code&gt;books&lt;/code&gt; keyword argument is defined because the
view class exposes such a parameter:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# app/views/books/index.rb&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Bookshelf&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Views&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Books&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Bookshelf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;View&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;expose&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:books&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Test Driven Development&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Practical Object-Oriented Design in Ruby&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s it for now. There&amp;#8217;s probably a lot of stuff that won&amp;#8217;t work. If you run into any problems, please let me know. I&amp;#8217;ll gladly accept contributions in the form of bug reports or PR&amp;#8217;s. Just head on over to the &lt;a href=&quot;https://github.com/digital-fabric/hanami-papercraft&quot;&gt;hanami-papercraft&lt;/a&gt; repo&amp;#8230;&lt;/p&gt;
</description></item><item><title>Papercraft Update: New Version, New Website</title><link>https://noteflakes.com/articles/2025-10-03-papercraft-website</link><guid>https://noteflakes.com/articles/2025-10-03-papercraft-website</guid><pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;I&amp;#8217;ve been working quite a bit on Papercraft these last few weeks. Yesterday I
released Papercraft version 2.16, and here are some of the notable changes
introduced since the last update:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Emit &lt;code&gt;DOCTYPE&lt;/code&gt; for &lt;code&gt;html&lt;/code&gt; tag by default. Before this change, you needed to
use the &lt;code&gt;html5&lt;/code&gt; tag to include the &lt;code&gt;DOCTYPE&lt;/code&gt; at the top of the generated
markup. Now you can just use &lt;code&gt;html&lt;/code&gt;. This is important since this way you
avoid &lt;a href=&quot;https://en.wikipedia.org/wiki/Quirks_mode&quot;&gt;quirks mode&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Do not content of &lt;code&gt;style&lt;/code&gt; and &lt;code&gt;script&lt;/code&gt; tags. This makes it easier to write
inline CSS and Javascript.&lt;/li&gt;
  &lt;li&gt;Add &lt;code&gt;Papercraft.markdown_doc&lt;/code&gt; convenience method which returns a
&lt;code&gt;Kramdown::Document&lt;/code&gt; instance for further processing of Markdown content.&lt;/li&gt;
  &lt;li&gt;Add support for rendering of namespaced components, so you can now do stuff
like &lt;code&gt;Foo::Bar(&#39;baz&#39;)&lt;/code&gt; right in your templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;new-papercraft-website&quot;&gt;New Papercraft Website&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve also been working on a website for Papercraft and it&amp;#8217;s finally online. Check it out:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://papercraft.noteflakes.com&quot; target=&quot;_blank&quot;&gt;papercraft.noteflakes.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Like the noteflakes.com website, which you&amp;#8217;re currently reading, the Papercraft
website is made using &lt;a href=&quot;https://github.com/digital-fabric/syntropy&quot;&gt;Syntropy&lt;/a&gt;.
All of the documentation pages are written using Markdown. Let&amp;#8217;s look at some
examples of how Papercraft is used on its own website:&lt;/p&gt;

&lt;h2 id=&quot;the-default-layout&quot;&gt;The Default Layout&lt;/h2&gt;

&lt;p&gt;Here is the content of the default layout (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/papercraft.noteflakes.com/_layout/default.rb&quot; target=&quot;_blank&quot;&gt;source code&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;page_title: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page_title&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Papercraft - &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;page_title&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Papercraft - Functional HTML Templating for Ruby&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;charset: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;viewport&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;width=device-width, initial-scale=1.0&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;rel: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;stylesheet&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;text/css&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/assets/style.css&#39;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;render_children&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;auto_refresh_watch!&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It&amp;#8217;s all pretty standard except for that &lt;code&gt;export&lt;/code&gt; at the top, which means that
this file is loaded by Syntropy as a Syntropy module (more on that later).
There&amp;#8217;s also the &lt;code&gt;auto_refresh_watch!&lt;/code&gt; directive, which is a Syntropy extension
that permits refreshing the page automatically whenever the source code changes
in development mode.&lt;/p&gt;

&lt;h2 id=&quot;the-docs-layout&quot;&gt;The Docs Layout&lt;/h2&gt;

&lt;p&gt;The documentation page layout (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/papercraft.noteflakes.com/_layout/docs.rb&quot; target=&quot;_blank&quot;&gt;source
code&lt;/a&gt;)
is a bit more involved, bit basically it is &lt;em&gt;derived&lt;/em&gt; from the default layout
using &lt;code&gt;apply&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;DefaultLayout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_layout/default&#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_pages&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DefaultLayout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sidebar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;raw&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nav&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Previous page&quot;&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:prev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Next page&quot;&lt;/span&gt;
              &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I&amp;#8217;ve omitted the header and the side bar for the sake of brevity, so let&amp;#8217;s look
at the &lt;code&gt;article&lt;/code&gt; element which contains the actual content of the page. There&amp;#8217;s
the title, there&amp;#8217;s the pre-rendered HTML rendered from the Markdown content (but
you can just as well use the &lt;code&gt;markdown&lt;/code&gt; method to render it in-place), and then
the &lt;code&gt;nav&lt;/code&gt; element holds links to the previous and next pages. You can see how
the logic flows naturally along the HTML content expressed with plain Ruby.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s the code that renders the documentation pages (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/papercraft.noteflakes.com/docs%2B.rb&quot; target=&quot;_blank&quot;&gt;source code&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_layout/docs&#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_pages&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;href&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;page_title: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;pages: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MimeTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Syntropy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;not_found&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The page entry is retrieved from the &lt;code&gt;Pages&lt;/code&gt; collection, and then passed to the layout template, along with some other metadata.&lt;/p&gt;

&lt;h2 id=&quot;the-landing-page&quot;&gt;The Landing Page&lt;/h2&gt;

&lt;p&gt;The landing page (a.k.a. the &lt;em&gt;index&lt;/em&gt; page) also uses the apply method to fill
the default layout with content. Here&amp;#8217;s a part of it (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/papercraft.noteflakes.com/index.rb&quot; target=&quot;_blank&quot;&gt;source code&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_pages&#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;_layout/default&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;single&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hero&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;logo&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;src: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/assets/papercraft.png&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Papercraft&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Functional HTML Templating for Ruby&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;snippet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MD&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
            ```ruby
            -&amp;gt; {
              h1 &quot;Hello from Papercraft!&quot;
            }.render
            ```
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;          MD&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;links&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Documentation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Pages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default_href&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Source Code&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/digital-fabric/papercraft&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;target: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;_blank&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hr&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, we can mix HTML and Markdown content freely. Another thing that
may stick out is the fact that I (almost) don&amp;#8217;t use any CSS classes. I prefer
using semantic tag names, which not only makes the templates much more readable,
but also makes the generated HTML much smaller in size, which helps in creating a
snappy user experience.&lt;/p&gt;

&lt;h2 id=&quot;refactoring-opportunities&quot;&gt;Refactoring Opportunities&lt;/h2&gt;

&lt;p&gt;For the Papercraft site, since there are basically just two kinds of layouts,
with little in common (except for the outer HTML envelope), I didn&amp;#8217;t really feel
there was a need to create components. But this possibility always exists. For
example, let&amp;#8217;s look at another snippet from the landing page:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/docs/01-introduction/01-overview&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MD&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      #### Easy to write &amp;amp; read
      
      Write your HTML templates in plain Ruby. Use beautiful
      syntax for generating HTML.
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;    MD&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/docs/03-template-composition/01-component-templates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MD&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
      #### Layouts &amp;amp; Components
      
      Compose and reuse your templates for layouts,
      components and partials.
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;    MD&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are a total of six &amp;#8220;featurettes&amp;#8221; like that on the landing page, so
supposing we wanted to create a featurette component, it might look like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Featurette&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then the landing page markup would look as follows:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;features&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Featurette&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/docs/01-introduction/01-overview&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Easy to write &amp;amp; read&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
      Write your HTML templates in plain Ruby. Use beautiful
      syntax for generating HTML.
    &quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;Featurette&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/docs/03-template-composition/01-component-templates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Layouts &amp;amp; Components&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;ss&quot;&gt;text: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;
      Compose and reuse your templates for layouts,
      components and partials.
    &quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I think from the point of view of effort vs gain it&amp;#8217;s not so interesting to do
this, but this is certainly a possibility, and just goes to show how easy it is
to compose and reuse templates in Papercraft.&lt;/p&gt;

&lt;h2 id=&quot;integration-with-other-apis&quot;&gt;Integration with Other APIs&lt;/h2&gt;

&lt;p&gt;Another thing that occurred to me while working on the Papercraft website is
that in fact a lot of the difficulties or issues surrounding the integration of
a templating library with existing frameworks or tools just disappear with
Papercraft. There&amp;#8217;s no boilerplate code, no ceremony around setting up state or
context objects. Your templates become pure functions that take some parameters
as input, and give you back HTML code, ready to serve. And, did I mention it&amp;#8217;s
&lt;a href=&quot;https://papercraft.noteflakes.com/docs/05-papercraft-internals/01-how-papercraft-works&quot;&gt;really fast&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Please feel free to test-drive Papercraft in your projects. Head on over to the
&lt;a href=&quot;https://papercraft.noteflakes.com/&quot;&gt;Papercraft website&lt;/a&gt;, and enjoy!&lt;/p&gt;
</description></item><item><title>Words Can Hurt: A Plea to the Ruby Community</title><link>https://noteflakes.com/articles/2025-09-27-words</link><guid>https://noteflakes.com/articles/2025-09-27-words</guid><pubDate>Sat, 27 Sep 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;I&amp;#8217;ve been watching the recent drama within the Ruby community slowly devolve in
the last few days into name-calling and virtue-signalling, and frankly just
plain silliness. I won&amp;#8217;t repeat here the details of the disagreement, and I
won&amp;#8217;t link to any posts written about what&amp;#8217;s happened.&lt;/p&gt;

&lt;p&gt;It is clear to me that some of this has to do with business interests of the
different parties involved, some of this has to do with political views, and
some of this apparently also has to with a clash of personalities. But what
really troubles me is not the details of the disagreements themselves, however
strongly each of us may feel about them, but rather how people have come to
treat each other over these disagreements.&lt;/p&gt;

&lt;p&gt;While some people have been very vocal about what&amp;#8217;s happening, I believe many
people have been keeping quiet not because they don&amp;#8217;t care, or because they
don&amp;#8217;t have an opinion about the issue at hand, but because there&amp;#8217;s a growing
sense of violence - verbal violence, and people are afraid if they express
themselves they&amp;#8217;ll become targets for this violence. In fact, I&amp;#8217;m pretty sure
when this article is published I&amp;#8217;ll become a target too. But I need to speak up.&lt;/p&gt;

&lt;h2 id=&quot;words-can-hurt&quot;&gt;Words Can Hurt&lt;/h2&gt;

&lt;p&gt;A lot of people have reacted very strongly to DHH&amp;#8217;s &lt;a href=&quot;https://world.hey.com/dhh/as-i-remember-london-e7d38e64&quot;&gt;recent post about
London&lt;/a&gt;, which I&amp;#8217;ll get
to in a moment, but I&amp;#8217;d like to address an earlier post of his, which is in
my eyes more relevant to the present moment. In &lt;a href=&quot;https://world.hey.com/dhh/words-are-not-violence-c751f14f&quot;&gt;&amp;#8220;Words are not
violence&amp;#8221;&lt;/a&gt;, David
talks about the murder of Charlie Kirk, and how some people have justified this
horrific act of violence as a response to Charlie&amp;#8217;s words:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;What I cannot come to terms with, though, is the modern equation of words with
violence. The growing sense of permission that if the disagreement runs deep
enough, then violence is a justified answer to settle it. That sounds so
obvious that we shouldn&amp;#8217;t need to state it in a civil society, but clearly it
is not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Needless to say I agree with David that an argument or disagreement can &lt;em&gt;never&lt;/em&gt;
serve as an excuse for physical violence. But I cannot agree that words cannot
be violent, they &lt;em&gt;can&lt;/em&gt;, and unfortunately violent speech is becoming more and
more present in public discourse.&lt;/p&gt;

&lt;p&gt;For the sake of discussion let&amp;#8217;s define verbal violence as any speech that you
would not wish on people dear to you, yourself included - name calling, hurtful
remarks, etc.&lt;/p&gt;

&lt;p&gt;Verbal violence can be just as hurtful as physical violence - calling someone a
Nazi, a facist, a loony, stupid, retarded, and even worse &lt;em&gt;is&lt;/em&gt; violent. &lt;a href=&quot;https://en.wikipedia.org/wiki/Verbal_abuse&quot;&gt;Verbal
abuse&lt;/a&gt; can also in some cases be
illegal. You can think it&amp;#8217;s quite harmless to call someone names, but when done
repeatedly it can incite other people to physical violence (as in the case of
the late Charlie Kirk, or MLK, or Gandhi), or even cause people to &lt;a href=&quot;https://en.wikipedia.org/wiki/Bullying#Effects&quot;&gt;commit
suicide&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;we-all-should-strive-for-compassion&quot;&gt;We All Should Strive for Compassion&lt;/h2&gt;

&lt;p&gt;Look, I believe that human kind is naturally kind and good. We all need to be
loved and respected, we all want dignity, we all want to have a peaceful,
meaningful life. No one wants to be despised or called names, so why do we do
this to each other? We should treat each other with respect and compassion.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve read David&amp;#8217;s London post and was hurt. I&amp;#8217;m an immigrant, so I know how hard
it is to be one, how hard it is to start a new life in a country where you
sometimes don&amp;#8217;t even know the language, don&amp;#8217;t have any network of friends and
family to rely on, where you don&amp;#8217;t know the social ettiquete, where you don&amp;#8217;t
know how to deal with the government, where your legal situation may be
precarious.&lt;/p&gt;

&lt;p&gt;I know that immigration is a complex subject, I know that immigration sometimes
has negative effects on the so-called &amp;#8220;native&amp;#8221; population (which itself may be
composed of 2nd- or 3rd generation immigrants). I know that sometimes
immigration engenders crime. But then again, I also know those people, some of
whom have have been through hell, some of whom have had their lives broken, all
need dignity. Nobody wants to spend entire days out on the street in idleness.
They all want to have a dignified, productive life.&lt;/p&gt;

&lt;p&gt;There&amp;#8217;s of course a lot more to be said about immigration, this can&amp;#8217;t be
summarized in the space of two paragraphs. But what I want to say is that we
should all give each other the benefit of the doubt, and assume that our
interlocutor is naturally good. When I meet a stranger, I want to be able to
treat them with compassion, not only for their sake, but also for &lt;em&gt;my&lt;/em&gt; sake.
And if you&amp;#8217;re reading this and you&amp;#8217;re still bitter and cynical about my words,
here are some quotes for you from greater and smarter people than me:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We must learn to live together as brothers or perish together as fools. Every
individual must learn this, and every nation must learn this. - &lt;a href=&quot;https://www.rev.com/transcripts/the-american-dream-july-4th-speech-transcript-martin-luther-king-jr&quot;&gt;Martin Luther
King&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Do to others as you would have them do to you. -
&lt;a href=&quot;https://www.biblestudytools.com/luke/6.html&quot;&gt;Jesus&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;You shall not hate your brother in your heart. - &lt;a href=&quot;https://biblehub.com/nkjv/leviticus/19.htm&quot;&gt;Leviticus 19,
17&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;David, if you&amp;#8217;re reading this, I don&amp;#8217;t know you personally, but I&amp;#8217;ve been a fan
of your work for many years. I still am. Like many others, I started programming
in Ruby because of you. I think you&amp;#8217;re a true visionary, and I think in many
ways you&amp;#8217;ve revolutionized how web apps are built. I really admire your work.
But your words have hurt me, and they have hurt others.&lt;/p&gt;

&lt;p&gt;I don&amp;#8217;t hate you, I&amp;#8217;m not even mad at you, I&amp;#8217;m just sad that a person in your
position would not think their words through before saying them out loud. I know
you are opinionated, and I believe you mean well, but please consider that even
if you have strong convictions about political and societal matters, how you
express them can have a lot of impact on the community, and on people who look
up to you.&lt;/p&gt;

&lt;p&gt;How should an immigrant who uses Ruby on Rails feel when you basically accuse
them of causing crime and of ruining London? How can they read your message as
anything but racist when you reduce them to &amp;#8220;a statistic as evident as day when
you walk the streets of London now&amp;#8221;? (How can it be evident if it were not for
their skin color, or their clothes?) Should people of color feel welcome at all at
RailsWorld? Should they feel welcome at 37signals?&lt;/p&gt;

&lt;p&gt;I know you were hurt by people calling you a Nazi, because you wrote about it. I
would be hurt too. Nothing that you said can justify that, nothing that you said
can justify any violent act towards you. But that is exactly the point, words
can hurt, words can be violent!&lt;/p&gt;

&lt;p&gt;So I ask you, please think about your words, and think about your message. As
BDFL of Rails (and you do this marvelously well when it&amp;#8217;s about technology), you
have a responsibility to the community. You are responsible for making everybody
feel welcome and included. I really hope my words find their way to your heart.&lt;/p&gt;

&lt;h2 id=&quot;a-plea-to-the-ruby-community&quot;&gt;A Plea to the Ruby Community&lt;/h2&gt;

&lt;p&gt;And to the Ruby community in general, I&amp;#8217;d like to say this: please stop the
name-calling, please stop vilifying people. There&amp;#8217;s a big difference between
saying a person is expressing racist views (which can be debated rationally) and
saying a person is a &amp;#8220;Nazi&amp;#8221; (which doesn&amp;#8217;t allow any kind of debate).
Name-calling will not stop racism, name-calling will not rectify the situation,
and reducing another person to a label such as &amp;#8220;Nazi&amp;#8221;, &amp;#8220;facist&amp;#8221; etc is to me
just as bad as racist speech. Being called names doesn&amp;#8217;t give you the right to
do the same. For the sake of us all, let&amp;#8217;s return to a calm and respectful
discourse, if not in society at large, then at least in the Ruby community.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m also really hopeful that the people behind the recent debacle around Ruby
Central find the courage in their hearts to come together and work on a solution
that will be acceptable to everybody involved, and that will bring relief to the
community. It is incumbent upon all of us to do our part to arrive at a peaceful
resolution.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It is not for you to finish the work, but neither are you at liberty to
neglect it. - Rabbi Tarfon, &lt;a href=&quot;https://en.wikipedia.org/wiki/Pirkei_Avot&quot;&gt;Pirkey
Avot&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That&amp;#8217;s it, I will not speak publicly any further on this subject. If you want to
talk to me privately, please feel free to &lt;a href=&quot;/about&quot;&gt;contact&lt;/a&gt; me. I leave you with
the following words from another great visionary:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Carefully watch your thoughts, for they become your words. Manage and watch
your words, for they will become your actions. Consider and judge your
actions, for they have become your habits. Acknowledge and watch your habits,
for they shall become your values. Understand and embrace your values, for
they become your destiny. - &lt;a href=&quot;https://en.wikipedia.org/wiki/Mahatma_Gandhi&quot;&gt;Mahatma
Gandhi&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description></item><item><title>My Thoughts on Euruko</title><link>https://noteflakes.com/articles/2025-09-23-euruko</link><guid>https://noteflakes.com/articles/2025-09-23-euruko</guid><pubDate>Tue, 23 Sep 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;I&amp;#8217;ve just got back home from Euruko last night. The conference ended on Friday,
but I decided to stay two more nights in Portugal and visit Porto. In between
walking all over the city, eating great food and enjoying the dancing and music
making in the street, I&amp;#8217;ve also had time to think about all the wonderful people
I met at the conference (and even some I&amp;#8217;ve met and talked to by chance on the
streets of Viana do Castelo and Porto), and the incredible experiences I&amp;#8217;ve had
at Euruko.&lt;/p&gt;

&lt;p&gt;First, I&amp;#8217;d like to express my deep appreciation for the organizers, headed by
Henrique. This was my first ever programming conference that I go to, so I had
no idea how it was going to go. But it was obvious from the first moment, taking
the shuttle kindly offered by the conference to take us from the airport to
Viana, how much the conference organizers cared about making everybody feel
welcome. It really felt like Henrique and the team went out of their way to
tend all of our needs, from the great food and drinks available at all times, to
sharing the wonderful cultural heritage of Viana with us all.&lt;/p&gt;

&lt;p&gt;I was also really impressed with the level of production. The branding work was
magnificent and was present everywhere down to the smallest details. It was also
much bigger than I expected. I imagined there would be about 200 people, but I
was told there were actually almost 600 of us. I&amp;#8217;ve met a lot of people I knew
online, and even more people I didn&amp;#8217;t know at all, and I was really grateful for
the more personal encounters I&amp;#8217;ve had. Talking about code is great, but talking
about life and sharing our common humanity is even better!&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve also noticed there were a lot more women than I imagined, and a lot more
older people (i.e. people of &lt;em&gt;my&lt;/em&gt; age), and both of these observations made me
very happy. I&amp;#8217;m a strong believer in diversity!&lt;/p&gt;

&lt;h2 id=&quot;an-auspicious-start&quot;&gt;An Auspicious Start&lt;/h2&gt;

&lt;p&gt;The conference kicked off with a keynote by Matz, the legendary creator of Ruby,
who 30-some years later still sets the tone for the Ruby community, putting the
accent on &lt;em&gt;developer happiness&lt;/em&gt; and inspiring the saying &amp;#8220;Matz is nice so we are
nice&amp;#8221;. At the beginning of his talk Matz said he was going to talk about &amp;#8220;my
favorite things&amp;#8221;. He was reminiscing about how he got started with computing and
about the fact that it took him about 10 years from wanting to invent his own
programming language to actually gaining the knowledge and the experience to be
able to do it. Good things come to those who wait!&lt;/p&gt;

&lt;p&gt;Matz has positioned the creation of Ruby at the intersection of three &amp;#8220;favorite
things&amp;#8221;: human recognition, programming and language, and he continued by
talking about the different types of joy that he got from Ruby: the joy of
creation, the joy of design and the joy of power, and made a remark that for him
joy was more important than performance, hence the importance of &amp;#8220;developer
happiness&amp;#8221;.&lt;/p&gt;

&lt;p&gt;He talked about how now he was less involved in the implementation details of
Ruby, but with the recent work on ZJIT (Ruby&amp;#8217;s next JIT engine), more people can
now get involved in improving Ruby&amp;#8217;s performance. Matz continued by telling the
chinese parable of the &lt;a href=&quot;https://en.wikipedia.org/wiki/The_old_man_lost_his_horse&quot;&gt;old man who lost his
horse&lt;/a&gt;, sharing with
all of us an approach of openness and inquisitiveness, of refraining from
pre-judging what happens to us but rather saying &amp;#8220;maybe, we&amp;#8217;ll see&amp;#8221;. He finished
his talk by saying that &lt;strong&gt;our greatest treasure is the community&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-euruko-talks&quot;&gt;The Euruko Talks&lt;/h2&gt;

&lt;p&gt;Following Matz is a tough act for anybody, but the following talk was just
fantastic: &lt;a href=&quot;https://marcoroth.dev/&quot;&gt;Marco Roth&lt;/a&gt; talked about his recent work on
&lt;a href=&quot;https://github.com/marcoroth/herb&quot;&gt;HERB&lt;/a&gt; and
&lt;a href=&quot;https://github.com/marcoroth/reactionview&quot;&gt;ReactionView&lt;/a&gt; which can really
revolutionize the Ruby on Rails view layer, and brings some important
innovations to developing web UI&amp;#8217;s using Ruby. His work is a great inspiration
to me personally, and I guess also to a lot of other people in the community.&lt;/p&gt;

&lt;p&gt;What really blew my mind was that Marco has in fact built a parser for ERB
templates that builds an AST of the template, which then permits not only
compilation, but also all kinds of other operations such as linting, annotation,
analysis and perhaps in the future the addition of reactivity!&lt;/p&gt;

&lt;p&gt;I found it really interesting that at more or less the same time, there are
three different people (Marco, myself and &lt;a href=&quot;https://github.com/joeldrapper/&quot;&gt;Joel
Drapper&lt;/a&gt;, the author of
&lt;a href=&quot;https://github.com/yippee-fun/phlex&quot;&gt;Phlex&lt;/a&gt;) having the same idea of parsing
templates into ASTs! But this is how things are sometimes, maybe this has less
to do with our respective talents but rather with a certain &lt;em&gt;Zeitgeist&lt;/em&gt; that
arises out of the emergence of tools such as
&lt;a href=&quot;https://github.com/ruby/prism&quot;&gt;Prism&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have been somewhat less attentive to the later talks, and have also missed
some of them because I wanted to talk to people, not only about technical stuff,
but also get to know people on a more personal level. One talk though that
really stood out to me was given by &lt;a href=&quot;https://github.com/rhannequin&quot;&gt;Rémy
Hannequin&lt;/a&gt;, who talked about the value of niche
OSS projects, and his own project
&lt;a href=&quot;https://github.com/rhannequin/astronoby&quot;&gt;Astronoby&lt;/a&gt; which does astronomical
calculations. I guess it really struck a chord with me because my projects also
tend to be very niche. In fact, one of my goals in coming to Euruko was to share
with the Ruby community my own projects and maybe that way they&amp;#8217;ll become a bit
less niche!&lt;/p&gt;

&lt;h2 id=&quot;my-lightning-talk&quot;&gt;My Lightning Talk&lt;/h2&gt;

&lt;p&gt;So, arriving in Viana for the conference I already had an idea of what I wanted
to talk about, and had prepared some slides, which I submitted to the conference
organizers as a lightning talk (i.e. a talk that takes only 5 minutes). My
proposal was accepted and I was able to talk on stage in front of a lot of
people about &lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt;, which
was a pretty scary experience, but I think it went OK - I stuck to the script I
prepared and was done within the allocated time.&lt;/p&gt;

&lt;p&gt;Luckily for me, the day before my talk I met &lt;a href=&quot;https://github.com/fidel&quot;&gt;Szymon
Fiedler&lt;/a&gt;, who not only has a wonderfully warm and open
personality, he also gave me some tips on how to prepare myself for my lightning
talk, which proved really helpful, since I mentioned to him that I had terrible
stage fright.&lt;/p&gt;

&lt;p&gt;So later that evening I just did what he said and practiced my script again and
again in front of the mirror, until I knew the text by heart and could take some
liberties with it, and felt more or less fluent with it while staying within the
5 minute allocated to my talk. So, while I was a bit shaky (I felt my voice
tremble,) I came off the stage not only with a sense of relief, but also of
accomplishment and a taste for more of it, because at the end of the day, like
all OSS developers I&amp;#8217;m driven by the passion of creating something cool and
beautiful that I want to share with others.&lt;/p&gt;

&lt;h2 id=&quot;the-picoruby-workshop&quot;&gt;The PicoRuby Workshop&lt;/h2&gt;

&lt;p&gt;Immediately after giving my lightning talk I scrambled off to a side room to
join the workshop on &lt;a href=&quot;https://picoruby.github.io/&quot;&gt;PicoRuby&lt;/a&gt;, which is an
implementation of Ruby for microcontrollers. Each of the participants got a
little kit of electronic bits (including a Raspberry Pi &lt;a href=&quot;https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#pico-2-family&quot;&gt;Pico 2
W&lt;/a&gt;
microcontroller) and a bunch of instructions on how to connect to it and get an
IRB session running on it.&lt;/p&gt;

&lt;p&gt;It took some fiddling and some help from
&lt;a href=&quot;https://github.com/hasumikin&quot;&gt;Hitoshi&lt;/a&gt;, our workshop guide, but once I had an
IRB session running on the microcontroller, it was only a question of hooking up
the various components to the breadboard and having fun with it. So, the first
thing to do was to connect a LED component and make it blink. I was so excited
to make this work, and it reminded me of the joy I felt as a kid when I started
programming and making stuff work on my first computer (it was a
&lt;a href=&quot;https://en.wikipedia.org/wiki/TI-99/4A&quot;&gt;TI-99/4A&lt;/a&gt;). We got to keep the kit, and
I know one day I&amp;#8217;ll find something useful to do with it.&lt;/p&gt;

&lt;h2 id=&quot;ai-at-euruko&quot;&gt;AI at Euruko&lt;/h2&gt;

&lt;p&gt;One important aspect of Euruko was the place of AI in it. There were I think
four different talks about AI tooling, and Matz even made mention of the fact
that he was now using Claude.code, which was a bit surprising to me. But I guess
even more surprising to me was hearing people expressing skepticism about AI. I
thought I was the only one!&lt;/p&gt;

&lt;p&gt;So I was quite happy to listen to other people express apprehension at using
LLM&amp;#8217;s for programming, not so much for the political/social implications of it,
but just for the practical (or rather, impractical) aspects of it. But I&amp;#8217;m still
not completely sure these tools don&amp;#8217;t have any merit, and am curious what value
they &lt;em&gt;can&lt;/em&gt; bring to software development.&lt;/p&gt;

&lt;p&gt;Out of the different AI tools presented at the conference, and which each had
their own approach to integration with Ruby programming workflows, the one that
was the most interesting to me was &lt;a href=&quot;https://rubyllm.com/&quot;&gt;RubyLLM&lt;/a&gt; by &lt;a href=&quot;https://github.com/crmne&quot;&gt;Carmine
Paolino&lt;/a&gt;. To me it hits the right chord, in that it
provides a uniform interface to interacting with AI agents, but doesn&amp;#8217;t make too
many assumptions on what you want to do with it. To me this really hits the
sweet spot of abstraction - and also in a very Ruby-esque way!&lt;/p&gt;

&lt;h2 id=&quot;the-end-of-euruko&quot;&gt;The End of Euruko&lt;/h2&gt;

&lt;p&gt;As the end of the conference drew nearer, I was less and less interested in
following the talks and frankly was a bit absent-minded. The afternoon after the
workshops (and the lightning talks) was composed of two talks about AI tools
(which personally didn&amp;#8217;t really resonate with me), but I was really interested
in the closing keynote, given by &lt;a href=&quot;https://github.com/eileencodes&quot;&gt;Eileen
Uchitelle&lt;/a&gt; of Shopify, which was about problems
with the &amp;#8220;mythical modular monolith&amp;#8221;, or really problems with code organization,
especially in big companies.&lt;/p&gt;

&lt;p&gt;Now, throughout the conference I felt like a very small fish swimming along all
those big fish, not only because I&amp;#8217;m a nobody in the community and here I am
talking to all those people that are all over GitHub and HN and YouTube etc.,
but also because I&amp;#8217;m a one-man company, and the conference was filled with
people from very big organizations, such as Shopify.&lt;/p&gt;

&lt;p&gt;The fact that I&amp;#8217;m not using Rails also made me feel somewhat marginalized, but I
quickly found out that I wasn&amp;#8217;t the only one. I was talking to &lt;a href=&quot;https://github.com/khasinski&quot;&gt;Chris
Hasinski&lt;/a&gt; and he suggested that I talk to Matz,
because Matz doesn&amp;#8217;t use Rails, and apparently in Japan they all don&amp;#8217;t use
Rails! Chris told me that actually at RubyKaigi (the Japanese Ruby conference)
it&amp;#8217;s taboo to talk about Rails, which I found absolutely hilarious.&lt;/p&gt;

&lt;p&gt;This gave me an excuse to talk to Matz, so I jumped at the opportunity to
present myself to him. He was very kind, and indeed he told me he does not use
Rails, and that I should come to RubyKaigi and present my projects. Maybe I
will!&lt;/p&gt;

&lt;p&gt;Going back to the closing keynote, what struck me was the difference in tone to
the opening keynote by Matz. Matz was talking about openness, the importance of
&amp;#8220;human recognition&amp;#8221;, embracing the unknown, and the joy of programming. But
Eileen&amp;#8217;s talk was very dense (lots of text, I had some trouble following), and
the impression I got was that she was laying down all those rules for what
works, what doesn&amp;#8217;t.&lt;/p&gt;

&lt;p&gt;The talk was also delivered in somewhat negative, absolute terms (&amp;#8220;this doesn&amp;#8217;t
work&amp;#8221;, &amp;#8220;that doesn&amp;#8217;t work&amp;#8221;), and the injunctions were also somewhat
heavy-handed: improve developer education, &lt;em&gt;indoctrinate&lt;/em&gt; developers, etc. While
she did stress how all code-organization problems are really human problems,
there was no compassion there, just a bunch of do&amp;#8217;s and dont&amp;#8217;s, and while she
mentioned developer happiness, I was wondering how can there be happiness
without compassion?&lt;/p&gt;

&lt;p&gt;So to me it was a bit of a letdown, because it made the conference end on a
somewhat negative, cold tone. Maybe this talk would have fared better as an
internal talk at Shopify, or maybe in front of other big orgs that use Rails. To
me it was more of an example of how I &lt;em&gt;don&amp;#8217;t&lt;/em&gt; want to do software development,
and how I don&amp;#8217;t want to deal with human problems in a work situation.&lt;/p&gt;

&lt;p&gt;This experience also ties in to the drama currently going on in the Ruby
community, which Joel shared with all of us on the morning of the second day of
the conference. I was never a user of Rails (except for a very short time when
it first came out like 20 years ago), then again I really admired DHH for his
vision. But having read some of his recent writings I was quite alarmed that a
person with such views should hold such a prominent position in the Ruby
community, especially if that community cherishes diversity and inclusivity.&lt;/p&gt;

&lt;p&gt;While there are some signs that DHH&amp;#8217;s toxic views have played a role in the
current crisis enveloping Ruby Central and the Rubygems system, what&amp;#8217;s even more
bothering to me is the heavy-handed approach we are seeing from Ruby Central,
and through them, Shopify (and perhaps other big actors in the Ruby community).
While I&amp;#8217;m a long time Ruby developer, I&amp;#8217;ve only been marginally involved in the
community, so to me a lot of this is new. But what is clear to me is that what I
don&amp;#8217;t want in my life, and what I don&amp;#8217;t want to see in the community, is big
corporations running the show and deciding for the rest of us what we can or
can&amp;#8217;t do, or how we do it.&lt;/p&gt;

&lt;p&gt;This is the time to show that Ruby is not just Rails, and that there is a Ruby
ecosystem outside of Rails (or off the rails if you will). At Euruko I met some
of the people who are actively working on this problem, be it developing
&lt;a href=&quot;https://github.com/spinel-coop/rv&quot;&gt;alternatives to bundler&lt;/a&gt;, or new and
&lt;a href=&quot;https://roda.jeremyevans.net/&quot;&gt;exciting&lt;/a&gt; &lt;a href=&quot;https://hanamirb.org/&quot;&gt;alternative&lt;/a&gt;
&lt;a href=&quot;https://github.com/yippee-fun&quot;&gt;web&lt;/a&gt; &lt;a href=&quot;https://www.bridgetownrb.com/&quot;&gt;frameworks&lt;/a&gt;
&lt;a href=&quot;https://github.com/digital-fabric/syntropy&quot;&gt;for&lt;/a&gt; Ruby.&lt;/p&gt;

&lt;p&gt;The strength of an ecosystem is in its diversity. I&amp;#8217;m already seeing this in the
recent work on HTML templating that I mentioned above, where different solutions
to the same problem arise at the same time and cross-fertilize. This can also
happen with other parts of the Ruby ecosystem. The more participation we have of
small players, the more resilient our community will be. The current crisis
teaches us that to prevent abuse of power in our community we need to double
down on creating and participating. I definitely intend to do my part to see
this happen.&lt;/p&gt;

&lt;h2 id=&quot;last-but-not-least&quot;&gt;Last But Not Least&lt;/h2&gt;

&lt;p&gt;I think a special mention should be made about the incredible cultural heritage
that the Euruko organizers have shared with us all. It was such a joy to hear
and see the richness of Portuguese culture that was on display in the opening
and closing ceremonies! And also to taste it: I loved everything I tasted (and I
tried to eat mostly where the locals eat), and a glass (or two) of porto just
makes you happy!&lt;/p&gt;

&lt;p&gt;Hopefully, I&amp;#8217;ll come back many times more to this beautiful country and this
beautiful people. See you at the next Ruby conference!&lt;/p&gt;
</description></item><item><title>P2 is the New Papercraft</title><link>https://noteflakes.com/articles/2025-09-12-p2-papercraft</link><guid>https://noteflakes.com/articles/2025-09-12-p2-papercraft</guid><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last few months I&amp;#8217;ve been busy working on a few different open-source
projects. Some of those projects are still at a preliminary stage, and I&amp;#8217;m
taking my time in continuing to develop them, use them ( &lt;a href=&quot;/about&quot;&gt;on this
website&lt;/a&gt; for example), but one project I&amp;#8217;ve devoting a lot of attention
to is P2, a Ruby gem for writing HTML templates in plain Ruby, which I&amp;#8217;ve been
writing &lt;a href=&quot;/articles/2025-08-07-introducing-p2&quot;&gt;about&lt;/a&gt;
&lt;a href=&quot;/articles/2025-08-18-how-to-make-ruby-faster&quot;&gt;lately&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;P2 has started as an exploration of how to make Papercraft faster. Papercraft,
about which I&amp;#8217;ve &lt;a href=&quot;/articles/2022-02-04-papercraft&quot;&gt;also written&lt;/a&gt;, was centered
on the idea that HTML templates should be fun to read and write, and fun to
compose in various ways, following the idea of promoting developer happiness, a
central tenet in the Ruby community.&lt;/p&gt;

&lt;p&gt;While I absolutely adore making tools that are fun to use, I also very much like
maximizing performance in the different projects I develop. And benchmarks I did
showed me that Papercraft&amp;#8217;s performance was somewhat lacking. That is why at a
certain point I started looking at how to improve Papercraft&amp;#8217;s performance, and
I started to explore the idea of doing some kind of transformation (or
compilation) of templates in order to make rendering them faster. While I had a
general idea of how to do this (by parsing the template&amp;#8217;s source code, then
transforming the AST, and finally converting it back to source code), and have
made sketches that showed this could work, I was not sure about how to integrate
all this work into Papercraft.&lt;/p&gt;

&lt;p&gt;Fast-forward to a few months ago, after some discussions with &lt;a href=&quot;https://github.com/joeldrapper/&quot;&gt;Joel
Drapper&lt;/a&gt;, who&amp;#8217;s the author of
&lt;a href=&quot;https://www.phlex.fun/&quot;&gt;Phlex&lt;/a&gt; and who also is interested in using the same
techniques to compile Phlex templates to make them faster, I&amp;#8217;ve decided to see
if I could look again at this problem, but without the baggage of an
already-existing codebase.&lt;/p&gt;

&lt;p&gt;As an aside, this idea of &lt;em&gt;re&lt;/em&gt;-examining old ideas, old codebases, and old
assumptions, is something I&amp;#8217;ve been doing more and more of lately. Basically, I
try to take a certain functionality which is already implemented in a project,
and try to see if I can rethink it, and see if I can arrive at something that is
simpler, has less lines of code, works faster, more robust, and has less
dependencies.&lt;/p&gt;

&lt;p&gt;So that&amp;#8217;s how P2 started - it was a reimagining of a HTML-generation Ruby DSL
that is &lt;em&gt;always&lt;/em&gt; compiled. After a few months of work, I got P2 to where I
wanted it to be in terms of performance, namely it is now as fast as ERB, and
this is because finally the &amp;#8220;compiled&amp;#8221; HTML generation source code is almost
identical between P2, ERB and ERubi.&lt;/p&gt;

&lt;p&gt;So now that P2 is &lt;em&gt;done&lt;/em&gt; more or less, it&amp;#8217;s time to fold this work back into
Papercraft, and concentrate on further improving the developer experience.
There&amp;#8217;s some work I&amp;#8217;ve already done on being able to inject HTML attributes into
the rendered HTML, in order to allow the implementation of frontend template
debugging tools, such as those recently shown by Marco Roth in
&lt;a href=&quot;https://reactionview.dev/&quot;&gt;ReactionView&lt;/a&gt;. And since finally, the Papercraft/P2
templates go through a process of conversion and transformation of AST&amp;#8217;s, other
future directions Marco&amp;#8217;s talking about, such as reactive templates, are also
possible.&lt;/p&gt;

&lt;p&gt;Now it&amp;#8217;s time to turn my attention to my other projects, which I&amp;#8217;ll write about
in the coming months. Meanwhile, please feel free to take
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt; for a ride. It&amp;#8217;s
really fun to use.&lt;/p&gt;
</description></item><item><title>How I Made Ruby Faster than Ruby</title><link>https://noteflakes.com/articles/2025-08-18-how-to-make-ruby-faster</link><guid>https://noteflakes.com/articles/2025-08-18-how-to-make-ruby-faster</guid><pubDate>Mon, 18 Aug 2025 00:00:00 GMT</pubDate><description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: P2 is now folded back into
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt;. The P2 repository
will eventually be archived.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you&amp;#8217;re a Ruby programmer, you most probably will be familiar ERB templates
and the distinctive syntax where you mix normal HTML with snippets of Ruby for
embedding dynamic values in HTML.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href=&quot;/articles/2025-08-07-introducing-p2&quot;&gt;recently&lt;/a&gt; about
&lt;a href=&quot;https://github.com/digital-fabric/p2&quot;&gt;P2&lt;/a&gt;, a new HTML templating library for
Ruby, where HTML is expressed using plain Ruby. Now this is nothing new or even
unique. There&amp;#8217;s a lot of other Ruby gems that allow you to do that:
&lt;a href=&quot;https://www.phlex.fun/&quot;&gt;Phlex&lt;/a&gt;, (my own)
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt; and
&lt;a href=&quot;https://github.com/sebyx07/ruby2html&quot;&gt;Ruby2html&lt;/a&gt; come to mind.&lt;/p&gt;

&lt;p&gt;What is different about P2 is that the template source code is always compiled
into an efficient Ruby code that generates the HTML. In other words, the code
you write inside a P2 template is actually never run, it just serves as a
&lt;em&gt;description&lt;/em&gt; of what you actually want to do.&lt;/p&gt;

&lt;p&gt;While there have been some previous attempts to use this technique for speeding
up template generation, namely Phlex and Papercraft, to the best of my knowledge
P2 is the first Ruby gem that actually employs this technique &lt;em&gt;exclusively&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this post I&amp;#8217;ll discuss how I took P2&amp;#8217;s template generation performance from
&amp;#8220;OK&amp;#8221; to &amp;#8220;Great&amp;#8221;. Along the way I was
&lt;a href=&quot;https://github.com/digital-fabric/p2/pull/1&quot;&gt;helped&lt;/a&gt; by Jean Boussier, a.k.a.
&lt;a href=&quot;https://github.com/byroot&quot;&gt;byroot&lt;/a&gt; who not only showed me how far P2 still has
to go in terms of performance, but also gave me some possible directions to
explore.&lt;/p&gt;

&lt;h2 id=&quot;how-p2-templates-work&quot;&gt;How P2 Templates Work&lt;/h2&gt;

&lt;p&gt;Here&amp;#8217;s a brief explanation of how P2 compiles template code. In P2, HTML
templates are expressed as Ruby Procs, for example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello from P2&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# &quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Hello from P2&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Calling the &lt;code&gt;#render&lt;/code&gt; method will automatically compile and run the generated
code, which will look something like the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html_escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, while the original source code is made of nested blocks, the
generated code takes an additional &lt;code&gt;__buffer__&lt;/code&gt; parameter and pushes snippets of
HTML into it. Any dynamic value is pushed separately after being properly
escaped.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s quickly go over how this code transformation is achieved. First, P2
locates the source file where the template is defined, and parses the template&amp;#8217;s
source code (using a little gem I wrote called
&lt;a href=&quot;https://github.com/digital-fabric/sirop&quot;&gt;Sirop&lt;/a&gt;) into a Prism AST. Here&amp;#8217;s a
part of the AST for the above example, showing the call to &lt;code&gt;body&lt;/code&gt; with the
nested &lt;code&gt;h1&lt;/code&gt; (with non-relevant parts removed):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@ CallNode (location: (6,4)-(8,5))
├── receiver: ∅
├── name: :body
├── arguments: ∅
└── block:
    @ BlockNode (location: (6,9)-(8,5))
    ├── locals: []
    ├── parameters: ∅
    └── body:
        @ StatementsNode (location: (7,6)-(7,14))
        └── body: (length: 1)
            └── @ CallNode (location: (7,6)-(7,14))
                ├── receiver: ∅
                ├── name: :h1
                ├── arguments:
                │   @ ArgumentsNode (location: (7,9)-(7,14))
                │   └── arguments: (length: 1)
                │       └── @ LocalVariableReadNode (location: (7,9)-(7,14))
                │           ├── name: :title
                │           └── depth: 2
                └── block: ∅
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(You can look at the AST for any proc by calling &lt;code&gt;Sirop.to_ast(my_proc)&lt;/code&gt; or
&lt;code&gt;my_proc.ast&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;Now if we look at the above DSL we can see that the calls to &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt; and
&lt;code&gt;h1&lt;/code&gt; are represented as nodes of type &lt;code&gt;CallNode&lt;/code&gt;, and those nodes have the
&lt;code&gt;receiver&lt;/code&gt; set to nil (because there&amp;#8217;s no receiver), and that the HTML tag name
is stored in &lt;code&gt;name&lt;/code&gt;. So the first step in transforming the code is to translate
each CallNode into a custom node type that could later be used to generate
snippets of HTML that will be added to the HTML buffer. The translation is
performed by the &lt;code&gt;TagTranslator&lt;/code&gt; class, which looks for specific patterns and
when a pattern is matched, replaces the given node with a custom node. Let&amp;#8217;s
look at &lt;code&gt;TagTranslator#visit_call_node&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TagTranslator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Prism&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;MutationCompiler&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit_call_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;dont_translate: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dont_translate&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;match_builtin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_const_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_block_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;match_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;Prism::MutationCompiler&lt;/code&gt; is a class that returns a modified AST based on the
return value of each &lt;code&gt;#visit_xxx&lt;/code&gt; method. So &lt;code&gt;#visit_call_node&lt;/code&gt;, as its name
suggests, visits nodes of type &lt;code&gt;CallNode&lt;/code&gt; and the return value is used for
mutating the AST. If we look at the &lt;code&gt;#match_tag&lt;/code&gt; method, we&amp;#8217;ll see how the call
node is transformed:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;match_tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;receiver&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;TagNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So what happens is that for normal HTML tags, the &lt;code&gt;#match_tag&lt;/code&gt; method will
return a custom &lt;code&gt;TagNode&lt;/code&gt;. Once the entire AST is traversed, we we&amp;#8217;ll have a
mutated AST where all relevant calls have been translated into instances of
&lt;code&gt;TagNode&lt;/code&gt; (there are other custom node classes that correspond to other parts of
the P2 DSL).&lt;/p&gt;

&lt;p&gt;The next step is to transform the mutated AST back to source. The heavy lifting
is done by the Sirop gem, with the &lt;code&gt;Sourcifier&lt;/code&gt; class, which allows us to
transform a given AST to Ruby source code. But the Sirop sourcifier doesn&amp;#8217;t know
anything about those custom P2 node types, such as &lt;code&gt;TagNode&lt;/code&gt;, so we need to help
it a bit. We do this by subclassing it, and adding some code for dealing with
all those custom nodes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;visit_tag_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;is_void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_void_element?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# emit open tag&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;emit_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag_location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_html_tag_open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_void&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# emit nested block&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;block&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Prism&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BlockNode&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;visit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Prism&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BlockArgumentNode&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;flush_html_parts!&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;adjust_whitespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.compiled_proc.(__buffer__)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# emit inner text&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner_text&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_static_node?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html_escape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format_literal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;to_s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_string_type_node?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;.to_s&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interpolated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ERB::Escape.html_escape((&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inner_text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# emit close tag&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;emit_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;location&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_html_tag_close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When HTML is emitted, the corresponding code is not generated immediately.
Instead, each piece of HTML is pushed into an array of pending HTML parts. When
the time comes to flush the pending HTML parts and generate code for them, we
concatenate all static strings together into a single buffer push, while each
dynamic part is escaped and pushed separately.&lt;/p&gt;

&lt;p&gt;The P2 compiler does similar work for dealing with other parts of the P2 DSL,
such as template composition, deferred execution, extension tags etc. In
addition there&amp;#8217;s quite a bit of work around generating a source map that maps
lines from the compiled code to lines in the original source code. When an
exception is raised while generating a template, P2 uses these source maps to
translate the exception&amp;#8217;s backtrace such that it will point to the original
source code.&lt;/p&gt;

&lt;h2 id=&quot;so-how-can-we-make-ruby-faster-than-ruby&quot;&gt;So How Can We Make Ruby Faster than Ruby?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/yo-dawg-ruby.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now that we have an idea of how P2 works, let&amp;#8217;s look at how I&amp;#8217;ve taken P2
performance from OK to great. When I first released P2, I was quite content with
its performance, since it was significantly faster than Papercraft, and the
benchmark I wrote compared it against ERB. But I haven&amp;#8217;t taken into account the
fact that I know so little about ERB, and especially about getting the best
performance out of ERB templates.&lt;/p&gt;

&lt;p&gt;Luckily, right after first publishing the repository, I got a nice &lt;a href=&quot;https://github.com/digital-fabric/p2/pull/1&quot;&gt;PR from
byroot&lt;/a&gt; that showed that P2 was not
so fast as I thought. While the discussion above shows how P2 generates code
&lt;em&gt;now&lt;/em&gt;, at the time it was generating code that was not the best. Here&amp;#8217;s how the
code P2 generated at the time looked (for the same template example shown
above):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CGI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;escape_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;P2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;translate_backtrace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now there are a few things in the above code that prevent it from being as fast
as compiled ERB (using the ERB or the ERubi gems):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Pushing an interpolated string to the buffer is slower than pushing each part
separately. This is kind of obvious when you take into account the fact with
an interpolated string, you first need to create a string that receives the
static and dynamic parts of the interpolated string, and then push that string
to &lt;code&gt;__buffer__&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The &lt;code&gt;rescue&lt;/code&gt; clause adds some overhead. This can be especially expensive when
you have nested templates. If each partial has its own rescue block, that can
quickly add up to a significant overhead.&lt;/li&gt;
  &lt;li&gt;As byroot pointed out, when the generated code is &lt;code&gt;eval&lt;/code&gt;ed into a Proc,
literal strings will be default not be frozen, which adds allocation overhead
and GC pressure.&lt;/li&gt;
  &lt;li&gt;As &lt;a href=&quot;https://github.com/mrinterweb&quot;&gt;mrinterweb&lt;/a&gt; pointed out in a separate
&lt;a href=&quot;https://github.com/digital-fabric/p2/pull/2&quot;&gt;PR&lt;/a&gt;, escaping HTML with the
&lt;code&gt;ERB::Escape.html_escape&lt;/code&gt; is faster than &lt;code&gt;CGI.escape_html&lt;/code&gt; (just a couple
percentage points but still&amp;#8230;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So taking all this advice into account, I&amp;#8217;ve rewritten the compiler code to do
the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Separate HTML code generation, such that static HTML strings are contatenated
and pushed to the HTML buffer once, and any dynamic parts are pushed
separately.&lt;/li&gt;
  &lt;li&gt;Remove the &lt;code&gt;rescue&lt;/code&gt; clause and instead do the backtrace translation only once
in &lt;code&gt;Proc#render&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Add the &lt;code&gt;# frozen_string_literal: true&lt;/code&gt; magic comment at the top of the
compiled code, so all static HTML content is made of frozen strings, which
reduces allocation and GC pressure. BTW, when are we going to get frozen
literal strings by default?&lt;/li&gt;
  &lt;li&gt;Switch from using &lt;code&gt;CGI.escape_html&lt;/code&gt; to &lt;code&gt;ERB::Escape.html_escape&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When byroot made his PR, the benchmark looked like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +YJIT +PRISM [arm64-darwin24]
Warming up --------------------------------------
                 erb    31.381k i/100ms
                  p2    65.312k i/100ms
               erubi   179.937k i/100ms
Calculating -------------------------------------
                 erb    314.436k (± 1.3%) i/s    (3.18 μs/i) -      1.600M in   5.090675s
                  p2    669.849k (± 1.1%) i/s    (1.49 μs/i) -      3.396M in   5.070806s
               erubi      1.869M (± 2.3%) i/s  (535.01 ns/i) -      9.357M in   5.008683s

Comparison:
                 erb:   314436.3 i/s
               erubi:  1869118.6 i/s - 5.94x  faster
                  p2:   669849.2 i/s - 2.13x  faster
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Which showed the P2 still had a lot to improve, as it was almost 3 times slower
than ERubi. (I later also found out how to make ERB compile its templates, its
compiled performance is more or less the same as compiled ERubi.) After the
changes I&amp;#8217;ve implemented here are the updated benchmark results:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ruby 3.4.5 (2025-07-16 revision 20cda200d3) +YJIT +PRISM [x86_64-linux]
Warming up --------------------------------------
                  p2   128.815k i/100ms
          papercraft    17.480k i/100ms
               phlex    15.620k i/100ms
                 erb   159.678k i/100ms
               erubi   154.085k i/100ms
Calculating -------------------------------------
                  p2      1.454M (± 2.4%) i/s  (687.59 ns/i) -      7.342M in   5.051705s
          papercraft    173.686k (± 2.7%) i/s    (5.76 μs/i) -    874.000k in   5.035996s
               phlex    155.211k (± 2.5%) i/s    (6.44 μs/i) -    781.000k in   5.035369s
                 erb      1.567M (± 4.2%) i/s  (637.97 ns/i) -      7.824M in   5.000791s
               erubi      1.498M (± 4.2%) i/s  (667.45 ns/i) -      7.550M in   5.048427s

Comparison:
                  p2:  1454360.2 i/s
                 erb:  1567482.7 i/s - 1.08x  faster
               erubi:  1498238.4 i/s - same-ish: difference falls within error
          papercraft:   173686.1 i/s - 8.37x  slower
               phlex:   155211.0 i/s - 9.37x  slower
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The benchmark shows that P2 is now on par with ERB and ERubi in terms of the
performance of compiled templates (and basically, the generated code for all
three is more or less identical.) I&amp;#8217;ve also added Papercraft and Phlex to show
the difference compilation makes, especially since P2 is really an offshoot of
Papercraft, and the DSL in P2 and Papercraft is almost identical. (Phlex has
also seen some &lt;a href=&quot;https://github.com/yippee-fun/phlex/pull/917&quot;&gt;work&lt;/a&gt; on template
compilation, but I don&amp;#8217;t know how far advanced this is.)&lt;/p&gt;

&lt;p&gt;As you can see, the compiled approach can be about 10X as fast as the
non-compiled approach. Of course, there&amp;#8217;s the usual caveat about benchmarks:
it&amp;#8217;s a very simple template with just two partials and not a lot of dynamic
parts, but this is indicative of the kind of performance you can expect from P2.
As far as I know, P2 is the first Ruby HTML-generation DSL that offers the same
performance as compiled ERB/ERubi.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;What I find most interesting about the changes I&amp;#8217;ve made to code generation in
P2, is that the currently compiled code is more than twice as fast as it was
when P2 first came out, which just goes to show than in fact Ruby is not slow,
it is actually quite fast, you just need to know how to write fast code! (And I
guess this is true for any programming language.)&lt;/p&gt;

&lt;p&gt;Hopefully, the Ruby-to-Ruby compilation technique discussed above would be
adpoted for other uses, and for more DSL&amp;#8217;s. I already have some ideas rolling
around in my head&amp;#8230;&lt;/p&gt;
</description></item><item><title>P2 - a Functional HTML Templating Engine for Ruby</title><link>https://noteflakes.com/articles/2025-08-07-introducing-p2</link><guid>https://noteflakes.com/articles/2025-08-07-introducing-p2</guid><pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate><description>&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: P2 is now folded back into
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt;. The P2 repository
will eventually be archived.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I&amp;#8217;ve just released &lt;a href=&quot;https://github.com/digital-fabric/p2&quot;&gt;P2&lt;/a&gt;, a new HTML
templating engine for Ruby. P2 builds on the work I did in
&lt;a href=&quot;/articles/2022-02-04-papercraft&quot;&gt;Papercraft&lt;/a&gt;, but takes things quite a bit
farther: templates are expressed as plain procs and are automatically compiled
in order to make it fast. How fast?&lt;/p&gt;

&lt;p&gt;Here are the results of benchmarking Papercraft against ERB:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sharon@nf1:~/repo/papercraft&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;--yjit&lt;/span&gt; examples/perf.rb
Warming up &lt;span class=&quot;nt&quot;&gt;--------------------------------------&lt;/span&gt;
          papercraft    10.187k i/100ms
                 erb    14.435k i/100ms
Calculating &lt;span class=&quot;nt&quot;&gt;-------------------------------------&lt;/span&gt;
          papercraft    106.983k &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;± 4.2%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; i/s -    539.911k &lt;span class=&quot;k&quot;&gt;in   &lt;/span&gt;5.057203s
                 erb    155.081k &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;± 2.4%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; i/s -    779.490k &lt;span class=&quot;k&quot;&gt;in   &lt;/span&gt;5.029535s

Comparison:
                 erb:   155080.9 i/s
          papercraft:   106983.3 i/s - 1.45x  slower
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here are the results of benchmarking P2 against ERB, with the same template:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sharon@nf1:~/repo/p2&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;--yjit&lt;/span&gt; examples/perf.rb
Warming up &lt;span class=&quot;nt&quot;&gt;--------------------------------------&lt;/span&gt;
                  p2    27.859k i/100ms
                 erb    14.935k i/100ms
Calculating &lt;span class=&quot;nt&quot;&gt;-------------------------------------&lt;/span&gt;
                  p2    309.524k &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;± 3.4%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; i/s -      1.560M &lt;span class=&quot;k&quot;&gt;in   &lt;/span&gt;5.047232s
                 erb    154.619k &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;± 2.5%&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; i/s -    776.620k &lt;span class=&quot;k&quot;&gt;in   &lt;/span&gt;5.026242s

Comparison:
                  p2:   309523.6 i/s
                 erb:   154619.0 i/s - 2.00x  slower
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, while Papercraft was about 30% slower than ERB, P2 is about twice as fast!
How does it do that?&lt;/p&gt;

&lt;h2 id=&quot;writing-html-with-a-ruby-dsl&quot;&gt;Writing HTML with a Ruby DSL&lt;/h2&gt;

&lt;p&gt;Of course, Papercraft and P2 are not the only Ruby gems that allow expressing
HTML using plain Ruby. Here&amp;#8217;s a simple example of a template that will work in
both Papercraft and P2:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;div&amp;gt;&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This approach to writing HTML templates is very appealing (to me at least). I
find that it is easy to write, easy to read, and also very easy to embed dynamic
values in the produced HTML. But, this approach does have a cost: all those
curly brackets - they denote blocks, and calling methods with blocks is kind of
slow.&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s not like ERB is super fast, but ERB templates are normally compiled to code
that just stuffs strings and interpolated values into a buffer. Surely we can do
the same with the above template. An ideal compiled version of the above
template would look like the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CGI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;escape_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/h1&amp;gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&amp;#8230; which looks so deceptively simple. But how can we transform the original
template, with all its curly brackets, into the single line of code above?&lt;/p&gt;

&lt;h2 id=&quot;parsing-ruby-code-with-prism&quot;&gt;Parsing Ruby Code with Prism&lt;/h2&gt;

&lt;p&gt;We first need to parse our template code, so we&amp;#8217;ll be able to transform it. We
do this with &lt;a href=&quot;https://github.com/ruby/prism&quot;&gt;Prism&lt;/a&gt;, Ruby&amp;#8217;s new parser gem.
Prism takes a piece of source code, and returns an AST (abstract syntax tree)
that we can then manipulate:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Prism&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-&amp;gt; { h1 &#39;Hello, world!&#39; }&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;value&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; @ ProgramNode ... (a whole page&#39;s worth of AST nodes)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, some stuff is still missing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We want to be able to compile Procs on the fly. How can we dynamically get the
source code for each proc we want to compile?&lt;/li&gt;
  &lt;li&gt;We also need to be able to convert ASTs back into source code. How do we do
this?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To answer these two questions I have created another gem which I call
&lt;a href=&quot;https://github.com/digital-fabric/sirop&quot;&gt;Sirop&lt;/a&gt;. Sirop takes a &lt;code&gt;Proc&lt;/code&gt; or a
&lt;code&gt;Method&lt;/code&gt; object, finds its source code, and then uses Prism to return its AST:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;sirop&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Sirop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_ast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; @LambdaNode ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sirop also allows us to get the source of the given template:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Sirop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;-&amp;gt; { h1 &#39;Hello, world!&#39; }&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One important limitation that Sirop has is that it can only work on Procs (or
methods) defined in a file. In other words, it will not work on procs defined in
an IRB session. The same limitation holds for P2 as well.&lt;/p&gt;

&lt;p&gt;Sirop provides a &lt;code&gt;Sourcifier&lt;/code&gt; class that does the conversion of an AST back into
source code. P2 uses the &lt;code&gt;Sourcifier&lt;/code&gt; class and overrides its stock behaviour in
order to be able to convert calls such as &lt;code&gt;h1 &#39;Hello, world!&#39;&lt;/code&gt; into strings that
are emitted into a buffer.&lt;/p&gt;

&lt;h2 id=&quot;how-p2-compiles-templates&quot;&gt;How P2 Compiles Templates&lt;/h2&gt;

&lt;p&gt;The template compilation process is done using the following steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Convert the given template Proc into an AST (using Sirop)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/digital-fabric/p2/blob/00382d1da232264d08127e4fa57fbd5c7e10f61a/lib/p2/compiler.rb#L156C2-L188C1&quot;&gt;Transform the AST&lt;/a&gt; - replacing each instance of &lt;code&gt;Prism::CallNode&lt;/code&gt; with a
&lt;a href=&quot;https://github.com/digital-fabric/p2/blob/00382d1da232264d08127e4fa57fbd5c7e10f61a/lib/p2/compiler.rb#L8C3-L49C6&quot;&gt;&lt;code&gt;P2::TagNode&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Convert the transformed AST into source code, converting any &lt;code&gt;TagNode&lt;/code&gt; into
the appropriate HTML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last step is more involved than the first two, and needs to take into
account buffering (for example, taking multiple tag calls and coalescing them
into a single string that&amp;#8217;s pushed into the buffer), escaping of HTML content,
and dealing with other features that P2 provides, such as deferred rendering,
template composition etc.&lt;/p&gt;

&lt;h2 id=&quot;dealing-with-exception-backtraces&quot;&gt;Dealing with Exception Backtraces&lt;/h2&gt;

&lt;p&gt;Another consideration to take into account when compiling templates is that any
exception raised while rendering the template will show an incorrect backtrace.
P2 generates a &lt;em&gt;source map&lt;/em&gt; mapping line numbers in the compiled proc to line
numbers in the original proc source code. This allows us to manipulate the
backtrace for any exception raised while rendering, such that any entry
occurring in the compiled proc will point to the corresponding line in the
original template source code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;p2&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; throws an exception...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the backtrace will show the line where the exception was raised, except that
the code that actually ran is completely different!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;test.rb:5:in &#39;block (2 levels) in &amp;lt;main&amp;gt;&#39;: bar (RuntimeError)
	from /home/sharon/.rbenv/versions/3.4.5/lib/ruby/gems/3.4.0/gems/p2-2.0.1/lib/p2/proc_ext.rb:31:in &#39;Proc#render&#39;
	from test.rb:11:in &#39;&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;some-future-directions&quot;&gt;Some Future Directions&lt;/h2&gt;

&lt;p&gt;The automatic compilation of Ruby DSLs can bring other benefits beyond just
performance improvements. We can really extend the syntax in order to make it
easier to express HTML. Here are some ideas I am currently exploring for
extending the syntax for expressing HTML tags and attributes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# syntax for specifying id&#39;s:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h1 id=&quot;foo&quot;&amp;gt;bar&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# syntax for specifying class:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h1 class=&quot;foo&quot;&amp;gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# syntax for specifying arbitrary attributes:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/about&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;About&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;a href=&quot;/about&quot;&amp;gt;About&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another direction I&amp;#8217;m exploring is being able to extend the DSL by expressing
extensions as Procs, and having P2 automatically inline them:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;P2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;ulist: &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Currently, P2 is perfectly able to emit procs, but the code looks like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# source&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ulist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# compiled template&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;P2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render_emit_call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ulist&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With proc inlining, it would look like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;div&amp;gt;&amp;lt;ul&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CGI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;escape_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;lt;/li&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;__buffer__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/ul&amp;gt;&amp;lt;/div&amp;gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That&amp;#8217;s it for now. If you find P2 useful, please let me know, and feel free to
contribute issues or PR&amp;#8217;s here: https://github.com/digital-fabric/p2&lt;/p&gt;
</description></item><item><title>Introducing UringMachine</title><link>https://noteflakes.com/articles/2025-06-28-introducing-uringmachine</link><guid>https://noteflakes.com/articles/2025-06-28-introducing-uringmachine</guid><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><description>&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/uringmachine&quot;&gt;UringMachine&lt;/a&gt; is a new Ruby gem
for performing concurrent I/O using io_uring and Ruby fibers. In UringMachine
I&amp;#8217;ve implemented all the lessons I&amp;#8217;ve learned from my previous attempts at
bringing io_uring to Ruby:
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;, a comprehensive gem
providing io_uring functionality, structured concurrency, and monkey-patching
for the Ruby standard library; and &lt;a href=&quot;https://github.com/digital-fabric/iou&quot;&gt;IOU&lt;/a&gt;,
a low level async API for using io_uring from Ruby.&lt;/p&gt;

&lt;p&gt;Here are some of those lessons:&lt;/p&gt;

&lt;h2 id=&quot;monkey-patching&quot;&gt;Monkey-patching&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;ve defended monkey-patching before, but my stance now is more nuanced.
Frankly, it&amp;#8217;s a big pain to maintain, because the Ruby stdlib is evolving, and
in some cases it&amp;#8217;s really an excercise in futility. So, UringMachine doesn&amp;#8217;t
really touch the standard IO classes, it just doesn&amp;#8217;t use them. UringMachine
works with plain file descriptors:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;uringmachine&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;foo.bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O_RDONLY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;read &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; bytes: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, at first sight this might look like a really clumsy way to do I/O in Ruby,
but there&amp;#8217;s more than meets the eye in the above example. A second example will
make things a bit clearer:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;foo.bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar.baz&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;baz.foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;content_map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;MAX_READ_LEN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;65536&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# A neat abstraction to do what IO.read(fn) does&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O_RDONLY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# the -1 means append to buffer&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAX_READ_LEN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAX_READ_LEN&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filenames&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;content_map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SystemCallError&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Failed to read &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content_map: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content_map&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So yes it&amp;#8217;s much lower level than IO&amp;#8217;s &lt;code&gt;#read&lt;/code&gt; and &lt;code&gt;#write&lt;/code&gt; methods, but what we
gain here is easy launching of concurrent (a.k.a. asynchronous) operations. In
the above example, three files are read concurrently in three separate fibers by
calling &lt;code&gt;#spin&lt;/code&gt; (just like in Polyphony). Each fiber opens the given filename,
reads the contents until EOF and then puts the content in a content map.
Meanwhile the main fiber calls &lt;code&gt;#join&lt;/code&gt; in order to wait for the three spun
fibers to terminate.&lt;/p&gt;

&lt;h2 id=&quot;the-everything-but-the-kitchen-sink-approach&quot;&gt;The &amp;#8220;Everything but the Kitchen Sink&amp;#8221; Approach&lt;/h2&gt;

&lt;p&gt;Polyphony was a library that was trying to do a lot of things, such as
structured concurrency, on-the-fly file compression (using pipes), HTTP parsing
and chunked transfer encdoing, and an actor API with message passing.&lt;/p&gt;

&lt;p&gt;The result was a big gem (~2.7KLOC of Ruby, plus ~6KLOC of C) with a very big
surface area, lots of classes, lots of money-patches. In comparison,
UringMachine is currently at about 200LOC of Ruby, plus ~2.7KLOC of C.&lt;/p&gt;

&lt;p&gt;UringMachine does not try to do everything that&amp;#8217;s related to concurrency.
Instead, it focuses on providing the basics, which you can then use to build out
the abstractions you want, such as message passing between fibers, structured
concurrency etc.&lt;/p&gt;

&lt;h2 id=&quot;a-simpler-more-robust-implementation&quot;&gt;A Simpler, More Robust Implementation&lt;/h2&gt;

&lt;p&gt;Polyphony has started as a wrapper around libev, and has evolved with time to
provide an alternative backend that uses io_uring instead of libev. UringMachine
was built from the beginning as a Linux-only, io_uring-only library. Its design
is also in many ways simpler than that of the Polyphony backend.&lt;/p&gt;

&lt;p&gt;I still haven&amp;#8217;t run benchmarks comparing UringMachine to Polyphony, but from
using UringMachine for running a webserver, it looks about the same, which means
&lt;em&gt;fast&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-new-ecosystem&quot;&gt;A New Ecosystem&lt;/h2&gt;

&lt;p&gt;Polyphony is but one of a bunch of Ruby gems I&amp;#8217;ve been writing for fun and
profit. Some of those tools were written just to explore what&amp;#8217;s possible in
Ruby, and some of those are being actively used in production on my clients&amp;#8217;
websites.&lt;/p&gt;

&lt;p&gt;But UringMachine does not offer the same (theoretical) level of integration with
the Ruby ecosystem as Polyphony. It just does I/O its own way. It does not
interfere with other gems or the way Ruby does I/O, but any I/O that does not go
through UringMachine means that the parts that do use UringMachine for I/O and
concurrency may be waiting for stock Ruby I/O operations to complete before
being able to complete their own I/O operations.&lt;/p&gt;

&lt;p&gt;Thus, to do anything useful, UringMachine will need to be accompanied by other
gems that create an ecosystem around it.&lt;/p&gt;

&lt;h2 id=&quot;tp2---a-new-webserver-for-ruby&quot;&gt;TP2 - a New Webserver for Ruby&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/noteflakes/tp2&quot;&gt;TP2&lt;/a&gt; is a new webserver based on
UringMachine. It is the continuation of my work on
&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt;. TP2 also has a much simpler
design than Tipi (and a much smaller codebase). TP2 uses UringMachine for
managing I/O and concurrency.&lt;/p&gt;

&lt;h2 id=&quot;syntropy---a-new-web-framework-for-ruby&quot;&gt;Syntropy - a New Web Framework for Ruby&lt;/h2&gt;

&lt;p&gt;A third part of the new ecosystem is
&lt;a href=&quot;https://github.com/noteflakes/syntropy&quot;&gt;Syntropy&lt;/a&gt;, a new web framework I&amp;#8217;ve
been working on. It, too, is a continuation of the work I&amp;#8217;ve been doing in
&lt;a href=&quot;https://github.com/digital-fabric/impression&quot;&gt;Impression&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Like Impression, Syntropy is a filesystem-based router. The routes of the app
are defined by the directory and file structure of your app. So for example,
HTTP requests to &lt;code&gt;/foo/bar&lt;/code&gt; will be routed to &lt;code&gt;site/foo/bar.rb&lt;/code&gt;. Syntropy can
also serve static files and markdown files.&lt;/p&gt;

&lt;p&gt;Dynamic routes are served by Ruby files that are called modules. Each module
exports an request handler, which can be in the form of a custom class, a
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt; template, or a simple
Proc:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# The world&#39;s simplest possible Syntropy module:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Other features in Syntropy:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Define middleware with &lt;code&gt;_hook.rb&lt;/code&gt; modules&lt;/li&gt;
  &lt;li&gt;Define custom error handlers with &lt;code&gt;_error.rb&lt;/code&gt; modules&lt;/li&gt;
  &lt;li&gt;Support for working with collections of markdown files, e.g. a list of blog
articles.&lt;/li&gt;
  &lt;li&gt;Support for clean URLs (i.e. without the file extension) for HTML, markdown
and module files.&lt;/li&gt;
  &lt;li&gt;Development mode with automatic route reloading on file change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future directions for Syntropy include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tools for deploying your web app to any Linux server using Docker Compose&lt;/li&gt;
  &lt;li&gt;Tools for creating a skeleton web app.&lt;/li&gt;
  &lt;li&gt;A data modeling layer based on SQLite databases, for use in: caching, logging,
web analytics and more.&lt;/li&gt;
  &lt;li&gt;The ability to integrate applets in a webapp, so for example an applet can
include subroutes defined in a separate directory (such as a different gem&amp;#8217;s
directory), for ease of integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;future-work-on-uringmachine&quot;&gt;Future Work on UringMachine&lt;/h2&gt;

&lt;p&gt;UringMachine is still missing some features. Here are some of the things I
intend to add in the near future:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Support for more io_uring ops, such as &lt;code&gt;splice&lt;/code&gt;, &lt;code&gt;sendto&lt;/code&gt;, &lt;code&gt;recvfrom&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Support for polling&lt;/li&gt;
  &lt;li&gt;Some abstractions for easily reading and writing entire files.&lt;/li&gt;
  &lt;li&gt;Support for SSL I/O.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;UringMachine, TP2 and Syntropy represent a new direction in my work. My aim is
to build tools that fit my way of writing software, and that are tailor-made for
my workflow and my preferences. As such, I&amp;#8217;m trying to avoid feature creep, and
instead focus on just adding (or changing) the stuff I need in order to execute
for my clients.&lt;/p&gt;

&lt;p&gt;Hopefully, someone else may find these projects interesting enough to use them,
but it&amp;#8217;s really more about scratching my own itch. The proof is right here on
this website, which is made with the above tools.&lt;/p&gt;
</description></item><item><title>Exploring unix pipes with Ruby and Polyphony</title><link>https://noteflakes.com/articles/2022-04-04-exploring-pipes</link><guid>https://noteflakes.com/articles/2022-04-04-exploring-pipes</guid><pubDate>Mon, 04 Apr 2022 00:00:00 GMT</pubDate><description>&lt;p&gt;Most of you are probably familiar with the concept of pipes on Unix-like OSes.
We mostly encounter pipes on the command line, where we can use them to use one
command&amp;#8217;s output as another&amp;#8217;s input. But pipes can also be used programmtically.
On Linux specifically, pipes let us move data between two arbitrary file
descriptors, without the data ever being seen by our user-space program, and in
some cases without it even being copied at all. This is done using the
&lt;a href=&quot;https://man7.org/linux/man-pages/man2/splice.2.html&quot;&gt;&lt;code&gt;splice()&lt;/code&gt;&lt;/a&gt; system call,
which we&amp;#8217;ll explore in more detail below.&lt;/p&gt;

&lt;p&gt;In the last few weeks I&amp;#8217;ve been working on adding some abstractions to
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt; that make it easier to
work with pipes and to use &lt;code&gt;splice()&lt;/code&gt; to move data between arbitrary file
descriptors (which Rubyists know as &lt;code&gt;IO&lt;/code&gt; instances.)&lt;/p&gt;

&lt;p&gt;I have also added a few data compression/decompression APIs that can
significantly improve performance when working with compressed data, and remove
the allocation overhead involved when working with the &lt;code&gt;zlib&lt;/code&gt; APIs in Ruby&amp;#8217;s
standard library. But let&amp;#8217;s start by discussing &lt;code&gt;splice()&lt;/code&gt; and what it allows us
to do.&lt;/p&gt;

&lt;h2 id=&quot;how-to-use-splice-with-polyphony&quot;&gt;How to use splice with Polyphony&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s start with the basics: the &lt;code&gt;splice&lt;/code&gt; system call (available only on Linux)
allows us to move data between two arbitrary file descriptors, without reading
it from the source fd, and then writing it to the target fd, which necessitates
moving data back and forth between the kernel and our user space program. If
you&amp;#8217;ve read the above-linked man page for &lt;code&gt;splice()&lt;/code&gt;, you&amp;#8217;ll have noticed that
&lt;code&gt;splice()&lt;/code&gt; needs at least one of the given file descriptors to refer to a pipe.
As Linus Torvalds himself
&lt;a href=&quot;https://yarchive.net/comp/linux/splice.html&quot;&gt;explains&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The pipe is just the standard in-kernel buffer between two arbitrary points.
Think of it as a scatter-gather list with a wait-queue. That&amp;#8217;s what a pipe
&lt;em&gt;is&lt;/em&gt;. Trying to get rid of the pipe totally misses the whole point of
splice()&amp;#8230;&lt;/p&gt;

  &lt;p&gt;&amp;#8230;it&amp;#8217;s what allows you to do &lt;em&gt;other&lt;/em&gt; things with splice that are simply
impossible to do with sendfile. Notably, splice allows very naturally the
&amp;#8220;readv/writev&amp;#8221; scatter-gather behaviour of &lt;em&gt;mixing&lt;/em&gt; streams. If you&amp;#8217;re a
web-server, with splice you can do&lt;/p&gt;

  &lt;pre&gt;&lt;code&gt;    write(pipefd, header, header_len);
    splice(file, pipefd, file_len);
    splice(pipefd, socket, total_len);
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;splice()&lt;/code&gt; lets us move data between, say, a file and a socket, for example
in an HTTP server, without that data ever being moved back and forth between our
user space program and the kernel. The kernel handles the moving of data
directly, and in some cases without any copying at all - which means less CPU
time, less memory usage and less memory allocations.&lt;/p&gt;

&lt;p&gt;In addition, the use of a pipe also takes care of back pressure. I&amp;#8217;ll let Linus
explain:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The reason you want to have a pipe in the middle is that you have to handle
partial moves &lt;em&gt;some&lt;/em&gt; way. And the pipe being the buffer really does allow
that, and also handles the case of &amp;#8220;what happens when we received more data
than we could write&amp;#8221;&amp;#8230;&lt;/p&gt;

  &lt;p&gt;In particular, what happens when you try to connect two streaming devices, but
the destination stops accepting data? You cannot put the received data &amp;#8220;back&amp;#8221;
into the streaming source any way - so if you actually want to be able to
handle error recovery, you &lt;em&gt;have&lt;/em&gt; to get access to the source buffers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, to sum up, if we want to directly move data between two arbitrary fd&amp;#8217;s, we
need to:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create a pipe, with a read fd and a write fd, which actually encapsulates a
kernel buffer.&lt;/li&gt;
  &lt;li&gt;Use &lt;code&gt;splice()&lt;/code&gt; to move data from the source fd to the pipe.&lt;/li&gt;
  &lt;li&gt;Use &lt;code&gt;splice()&lt;/code&gt; to move data from the pipe to the target fd.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Operations 2 and 3 would repeat until the source fd reaches end-of-file (EOF),
and ideally would run concurrently. Let&amp;#8217;s examine how we can do that using
Polyphony.&lt;/p&gt;

&lt;p&gt;Polyphony introduces the &lt;code&gt;IO.splice&lt;/code&gt; method, which has the following signature:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This method splices a total of &lt;code&gt;len&lt;/code&gt; bytes from &lt;code&gt;src&lt;/code&gt; to &lt;code&gt;dest&lt;/code&gt;, which are both
IO instances. If &lt;code&gt;len&lt;/code&gt; is negative, &lt;code&gt;IO.splice&lt;/code&gt; will keep splicing (with chunks
of up to &lt;code&gt;-len&lt;/code&gt; bytes) until EOF is encountered. What&amp;#8217;s important to note about
this method is that it &lt;em&gt;will&lt;/em&gt; block the current fiber if the source is not
readable or the destination is not writable. And of course, as stated above, at
least one of the IOs involved needs to refer to a pipe.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;move_data_between_ios&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;f2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;move_data_between_ios&lt;/code&gt; method above starts by creating a pipe, then spins
up a fiber (&lt;code&gt;f1&lt;/code&gt;) which splices data continuously from the source IO to the pipe
(until EOF is encountered), then closes the write end of the pipe. A second
fiber (&lt;code&gt;f2&lt;/code&gt;) splices data from the pipe to the target IO, until the pipe has
reached EOF (hence the importance of the call to &lt;code&gt;w.close&lt;/code&gt; in the first fiber).&lt;/p&gt;

&lt;p&gt;So actually, there&amp;#8217;s a quite lot happening here: we split the work between two
fibers: one for moving data from the source to the pipe, another for moving data
from the pipe to the target. What&amp;#8217;s interesting is that these two concurrent
&lt;code&gt;splice&lt;/code&gt; operations are dependent on the speed of both the source and the
target, with the pipe providing a buffer (by default holding &lt;a href=&quot;https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer&quot;&gt;up to
64KB&lt;/a&gt;
of data) that can somewhat dampen any &amp;#8220;stuttering&amp;#8221; of either the source or the
target.&lt;/p&gt;

&lt;p&gt;Finally, the performance of this double splice operation will be limited by the
slowest of the two. In other words, the above solution theoretically provides
the best possible behaviour in terms of responding to back pressure. Under ideal
conditions, where data is always available for reading from the source, and the
target always accepts more data to write, we pay a negligible price for passing
data first from the source to a kernel buffer, then from the buffer to the
target (the kernel performs splicing by incrementing reference counts, in
general &lt;a href=&quot;https://man7.org/linux/man-pages/man2/splice.2.html#NOTES&quot;&gt;there&amp;#8217;s no copying of data
involved&lt;/a&gt;). But when
we have a read-constrained source, or a write-constrained target, our kernel
buffer will allow us to minimize the time wasted waiting for data to become
readable or writable.&lt;/p&gt;

&lt;p&gt;And the best part: we don&amp;#8217;t need to manage a user-space buffer in our program!
We could be streaming GBs of data without worrying about allocating Ruby strings
and stressing the Ruby GC. &lt;em&gt;And&lt;/em&gt; we didn&amp;#8217;t need to copy data from the kernel to
our program and back.&lt;/p&gt;

&lt;p&gt;You will have noticed, however, that there are some drawbacks to this approach:
we need to create a pipe, and we also need to create two fibers in which to run
the two &lt;code&gt;splice&lt;/code&gt; operations concurrently. This overhead means that this approach
to moving data between two file descriptors is probably worth it only under
certain circumstances, for example above a certain quantity of data, but most
importantly when dealing with potentially slow clients, or when latency varies
wildly. Later on in this article I&amp;#8217;ll discuss some new APIs I have introduced
meant to reduce the overhead involved in performing concurrent splicing.&lt;/p&gt;

&lt;h2 id=&quot;some-use-cases-for-splicing&quot;&gt;Some use cases for splicing&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s now look at a few examples that show how &lt;code&gt;splice()&lt;/code&gt; can be used in a
variety of situations. The most obvious place to start is, for me, an echo
server. Since the echo server just sends back whatever you send to it, it seems
logical to use &lt;code&gt;splice&lt;/code&gt; to implement it. Let&amp;#8217;s look at how a &amp;#8220;normal&amp;#8221; echo
server will be implemented using Polyphony:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;127.0.0.1&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Echoing on port 1234...&#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the &lt;code&gt;#handle_client&lt;/code&gt; method simply reads from the client, then
writes the data back to the client. So the data itself does not interest us, we
don&amp;#8217;t do any processing on it, but we still need to create a string every time
we read from the connection, then write this string back.&lt;/p&gt;

&lt;h2 id=&quot;echo-server-using-splice&quot;&gt;Echo server using splice&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;splice&lt;/code&gt;, however, frees us from having to deal with allocating Ruby
strings:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This version of &lt;code&gt;#handle_client&lt;/code&gt; using &lt;code&gt;splice&lt;/code&gt; looks almost identical to the
&lt;code&gt;move_data_between_ios&lt;/code&gt; method discussed above. The only difference is that we
slightly reduce the overhead involved in setting up the concurrent operations by
spinning up only one fiber in addition to the fiber dedicated to the client. We
pass &lt;code&gt;-8192&lt;/code&gt; as the &lt;code&gt;len&lt;/code&gt; argument to &lt;code&gt;IO.splice&lt;/code&gt;, which means that the splice
operation will be repeated until EOF is encountered, in chunks of 8KB.&lt;/p&gt;

&lt;h2 id=&quot;tcp-proxy&quot;&gt;TCP proxy&lt;/h2&gt;

&lt;p&gt;Another use case for &lt;code&gt;splice&lt;/code&gt; that comes to mind is a TCP proxy. Our TCP proxy
will accept incoming connections, will create a connection to some destination
TCP server, and will pass data in both directions between the two connections.&lt;/p&gt;

&lt;p&gt;Here we need to setup two pipes and additional fibers in order to perform the
splicing in both directions:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;DESTINATION&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;127.0.0.1&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DESTINATION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNRESET&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ignore&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Serving TCP proxy on port 4321...&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;TCPServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;127.0.0.1&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4321&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept_loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For each incoming connection to our TCP proxy (serving on port 4321), a new
connection to the target (on port 1234) will be established, and then data will
be moved in both directions. Any data sent by the client will be spliced to the
destination connection, and any data received from the destination will be
spliced back to the client.&lt;/p&gt;

&lt;p&gt;Note how we now need to create two pipes, one for each direction, and we need to
run four splice operations at once. As you can see, the overhead involved is
already substantial - we need to setup at least three more fibers to run our
splice operations, and we need to create two pipes with two IO instances for
each pipe. Surely we can do better&amp;#8230;&lt;/p&gt;

&lt;h2 id=&quot;a-better-pipe-abstraction&quot;&gt;A better pipe abstraction&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s start with pipes. Currently the Ruby core IO API provides the &lt;code&gt;IO.pipe&lt;/code&gt;
method, which returns two IO instances - one for the read end of the pipe, and
one for the write end of the pipe. This way of working with pipes has two
problems: first, as discussed above, whenever we create a pipe we actually need
to allocate and setup two IO instances; and second, we need to name those IO
instances in a way that will signify their usage. Note how in the above TCP
proxy program I just gave generic names to the pipe ends:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;r1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;r2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Those names are not very descriptive and it&amp;#8217;s easy to get them mixed up when
passing them to &lt;code&gt;IO.splice&lt;/code&gt;. What if we introduced a &lt;code&gt;Pipe&lt;/code&gt; class that
encapsulated both ends of the pipe? We can then give each pipe a name that will
mean something:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;client_to_dest_pipe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dest_to_client_pipe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Polyphony.pipe&lt;/code&gt; method, introduced to Polyphony a few days ago does just
that. It offers the same API as a normal IO object, but any read operation will
be performed on the read end of the pipe, and any write operation will be
performed on the write end. This means that &lt;code&gt;Polyphony::Pipe&lt;/code&gt; instances can be
used just like normal &lt;code&gt;IO&lt;/code&gt; instances, and can also be passed to &lt;code&gt;IO.splice&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So if we were to use &lt;code&gt;Polyphony.pipe&lt;/code&gt; instead of &lt;code&gt;IO.pipe&lt;/code&gt;, here&amp;#8217;s how the
&lt;code&gt;handle_client&lt;/code&gt; method would look like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The difference is subtle, but notice how this code looks a lot cleaner, and we
have only a single variable acting as our pipe. We have saved on object
allocations, and we have also made our code more readable and easier to
understand. The TCP proxy example now looks like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DESTINATION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;client_to_dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_to_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_to_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_to_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;dest_to_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest_to_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest_to_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dest_to_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNRESET&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ignore&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;reducing-fiber-usage-for-splicing&quot;&gt;Reducing fiber usage for splicing&lt;/h2&gt;

&lt;p&gt;The second issue we had, where we needed two splice operations to happen at the
same time, was that this necessitates two separate fibers to be running
concurrently. This is due to Polyphony&amp;#8217;s design, where each fiber can only
perform &lt;em&gt;one&lt;/em&gt; blocking operation at a time.&lt;/p&gt;

&lt;p&gt;But since the Polyphony backend is perfectly capable of launching multiple I/O
operations at once, it occurred to me that an abstraction that sets up a pipe,
then performs two concurrent &lt;code&gt;splice&lt;/code&gt; operations could be immensely handy!
Here&amp;#8217;s the basic idea I came up with, implemented on the io_uring backend (the
actual implementation is
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/934be8ee42b0e816db1f4daa9f12c7e1fed5a816/ext/polyphony/backend_io_uring.c#L1009-L1069&quot;&gt;here&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;backend_double_splice_to_eof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_pipe&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prep_splice_op_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prep_splice_op_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;submit_deferred_sqes&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backend_await&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interrupted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;raise_if_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;completed?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctx_src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prep_splice_op_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;completed?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;release&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ctx_dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prep_splice_op_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;submit_deferred_sqes&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;#8217;s examine what&amp;#8217;s going on above.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We start by creating a pipe and resetting a variable for counting the total
bytes transferred.&lt;/li&gt;
  &lt;li&gt;We then setup two operation contexts, one for each &lt;code&gt;splice&lt;/code&gt; (source to pipe,
pipe to destination), and submit the corresponding
&lt;a href=&quot;https://unixism.net/loti/ref-liburing/submission.html&quot;&gt;SQEs&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;We then start a loop:
    &lt;ul&gt;
      &lt;li&gt;We yield control to the next runnable fiber by calling backend_await. Our
fiber will be resumed once any of the two &lt;code&gt;splice&lt;/code&gt; operations completes.&lt;/li&gt;
      &lt;li&gt;We perform checks to see if the operation has been interrupted. If so, we
either raise an error in case the resume value is an exception, or simply
return the value.&lt;/li&gt;
      &lt;li&gt;We check if the &lt;code&gt;splice&lt;/code&gt; moving data from the source to the pipe has
completed.
        &lt;ul&gt;
          &lt;li&gt;If the result of the operation is 0, that means we have hit EOF and we can
stop splicing from the source, and we can close the write-end of the pipe
(in order to signal an EOF to the other &lt;code&gt;splice&lt;/code&gt; operation.)&lt;/li&gt;
          &lt;li&gt;Otherwise, we create a new, identical &lt;code&gt;splice&lt;/code&gt; operation in order to
continue moving data from the source to the pipe.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;We check if the &lt;code&gt;splice&lt;/code&gt; moving data from the pipe to the destination has
completed.
        &lt;ul&gt;
          &lt;li&gt;If the result of the operation is 0, that means we have hit EOF and we can
break out of the loop.&lt;/li&gt;
          &lt;li&gt;Otherwise, we add the bytes transferred to our total bytes counter, and
then create a new, identical &lt;code&gt;splice&lt;/code&gt; operation to continue moving data
from the pipe to the destination.&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;We return the total bytes spliced, and make sure the pipe is closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now that we have our &lt;code&gt;IO.double_splice_to_eof&lt;/code&gt; method, let&amp;#8217;s see how our echo
server now looks:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;double_splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have basically eliminated all of the code dealing with setting up a pipe and
performing two splices concurrently. And we still keep the same cancellation
behaviour (with Polyphony we can cancel any blocking operation at any moment).&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s now see how this new method can be used in the TCP proxy we looked at
above:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DESTINATION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;double_splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;double_splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8192&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Errno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ECONNRESET&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# ignore&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we still need to spin up a second fiber so we could move data in both
directions at the same time, but we have greatly simplified our code, and
minimized the overhead involved. Here again, the actual data transferred between
the two sockets stays in the kernel, we just tell the kernel how to move it.&lt;/p&gt;

&lt;p&gt;If we run both our TCP proxy and our echo server, we can easily test that they
both work correctly. Let&amp;#8217;s start by checking the echo server:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby examples/pipes/echo_server.rb &amp;amp;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foobar&quot;&lt;/span&gt; | nc &lt;span class=&quot;nt&quot;&gt;-N&lt;/span&gt; localhost 1234
foobar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can then run our TCP proxy, which will connect to our echo server:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby examples/pipes/tcp_proxy.rb &amp;amp;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foobar&quot;&lt;/span&gt; | nc &lt;span class=&quot;nt&quot;&gt;-N&lt;/span&gt; localhost 4321
foobar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;pipes-and-compression&quot;&gt;Pipes and compression&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s take a short detour from &lt;code&gt;splice&lt;/code&gt; and look at some other new APIs recently
introduced in Polyphony, all related to data compression. The Ruby stdlib
includes the &lt;code&gt;zlib&lt;/code&gt; gem, which provides a Ruby binding for &lt;code&gt;zlib&lt;/code&gt; - a popular
library for compressing and decompressing data, and it works just fine, but I
wanted to create an API that will allow developers to compress data directly
between two file descriptors, without having to copy it to and from Ruby
strings.&lt;/p&gt;

&lt;p&gt;The new compression/decompression APIs are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;IO.deflate(src, dest)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IO.inflate(src, dest)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IO.gzip(src, dest, info = nil)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IO.gunzip(src, dest, info = nil)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These four methods all take a source and a destination, which would normally be
IO instances, but they also accept Ruby strings as either a source or a
destination. &lt;code&gt;IO.gzip&lt;/code&gt; takes an optional hash that can be used to set gzip meta
data, with the following keys:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;:orig_name&lt;/code&gt; - original file name&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:mtime&lt;/code&gt; - the file&amp;#8217;s time stamp&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:comment&lt;/code&gt; - comment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;IO.gunzip&lt;/code&gt; can also take an optional hash that will be populated with the same
gzip meta data read from the source file descriptor, with the same keys as
&lt;code&gt;IO.gzip&lt;/code&gt;. Here&amp;#8217;s a simple Ruby script that takes uncompressed data on &lt;code&gt;STDIN&lt;/code&gt;,
gzips it and outputs the compressed data to &lt;code&gt;STDOUT&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gzip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDOUT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can very easily test that our script works correctly:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;foobar&quot;&lt;/span&gt; | ruby gzip.rb | &lt;span class=&quot;nb&quot;&gt;gunzip
&lt;/span&gt;foobar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The new compression/decompression APIs introduced in Polyphony have been
designed to minimize allocations when compressing or decompressing data between
two file descriptors (or IO instances.) To achieve this, the new
compression/decompression methods use static stack-allocated buffers. In
addition, the compression/decompression methods are tightly integrated with the
different backend I/O methods, so no Ruby strings are involved (Polyphony&amp;#8217;s
backend APIs such as &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;recv&lt;/code&gt; etc. can read and write data directly
to/from raw buffers.)&lt;/p&gt;

&lt;h2 id=&quot;splicing-and-compression&quot;&gt;Splicing and compression&lt;/h2&gt;

&lt;p&gt;Now, you might be already guessing at what I aim to explore here: can we combine
pipes and those new compression APIs in order to create interesting I/O
pipelines that minimize both allocations and copying of data?&lt;/p&gt;

&lt;p&gt;Suppose we want to be able to send compressed data using some protocol not
unlike HTTP/1.1 with &lt;a href=&quot;https://en.wikipedia.org/wiki/Chunked_transfer_encoding&quot;&gt;chunked transfer
encoding&lt;/a&gt;. What we need
to be able to do is to write a header with the (hexadecimal) chunk length
followed by &lt;code&gt;\r\n&lt;/code&gt;, then splice the data, again followed by &lt;code&gt;\r\n&lt;/code&gt; until we
finally signal EOF by writing &lt;code&gt;0\r\n\r\n&lt;/code&gt; (an empty chunk) to the client. How
can we integrate data compression into this workflow?&lt;/p&gt;

&lt;p&gt;The answer is: Just introduce more pipes. Let&amp;#8217;s look at the simplest case of
doing chunked transfer using &lt;code&gt;splice&lt;/code&gt;, without any compression:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;MAX_CHUNK&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# src is the data source as an IO instance&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# dest is the client&#39;s socket (or maybe an intermediary pipe)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;chunked_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MAX_CHUNK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The following diagram shows how the pipeline we created in &lt;code&gt;#chunked_transfer&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;+------+    +----+    +-----------+
|source| =&amp;gt; |pipe| =&amp;gt; |destination|
+------+    +----+    +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In essence, what &lt;code&gt;splice&lt;/code&gt; allows us to do is to read a chunk of data from the
source into a kernel buffer (the pipe), then use the result (the number of bytes
read) to write a header to the destination, followed by a second &lt;code&gt;splice&lt;/code&gt;
operation moving data from the kernel buffer to the destination. So we get to
control the moving of data between source and desination, without having to copy
data back and forth between the kernel and our program.&lt;/p&gt;

&lt;p&gt;Using the above method is simple:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;chunked_transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_with_static_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;file?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now if we also want to gzip the data before passing it to the client (which
browsers and users love!) we need to introduce an intermediary pipe in
conjunction with &lt;code&gt;IO.gzip&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io_gzipped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gzipped&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gzip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gzipped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gzipped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gzipped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above method, we create an additional &lt;code&gt;gzipped&lt;/code&gt; pipe, and spin up a
separate fiber that will and zip all data from the given &lt;code&gt;io&lt;/code&gt; to the &lt;code&gt;gzipped&lt;/code&gt;
pipe. We then call &lt;code&gt;#respond_from_io&lt;/code&gt;, which will perform the chunked transfer
from the &lt;code&gt;gzipped&lt;/code&gt; pipe.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a diagram representing how data moves in the above pipeline:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;         fiber 1              fiber 2
            +               +---------+
            |               |         |
+------+    v    +-------+  v +----+  v +-----------+
|source| (gzip)&amp;gt; |gzipped| =&amp;gt; |pipe| =&amp;gt; |destination|
+------+         +-------+    +----+    +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, with a pipe, an extra fiber, and a call to &lt;code&gt;IO.gzip&lt;/code&gt; we added a
substantial new feature to our fledgling pseudo-HTTP server, without having to
change our internal interfaces.&lt;/p&gt;

&lt;p&gt;So, we see a pattern begin to emerge here: we have different methods that take a
source file descriptor (or IO instance) and a destination file descriptor, and
we create a pipeline by running them concurrently on separate fibers. Back
pressure is handled automatically (by virtue of using pipes as buffers), and we
reduce CPU and memory usage to the strict minimum, all while still writing Ruby!&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;I don&amp;#8217;t mean to disappoint you, but I still don&amp;#8217;t have any numbers to show you
regarding performance. In the last month I&amp;#8217;ve been juggling work, gardening and
family affairs, and I didn&amp;#8217;t have enough time to do benchmarks. I&amp;#8217;ll try to that
in the comings weeks and publish the results in a subsequent article. Currently
all I have is my intuition, which tells me there&amp;#8217;s a lot to be gained from using
the new APIs and ideas I&amp;#8217;ve exposed in this article. If my intuition proves
right, they will make their way to
&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt;, the web server which I&amp;#8217;ve been
working on for a while now.&lt;/p&gt;

&lt;h2 id=&quot;future-directions&quot;&gt;Future directions&lt;/h2&gt;

&lt;p&gt;A few weeks ago &lt;a href=&quot;/articles/2022-03-05-february-summary&quot;&gt;I wrote&lt;/a&gt; about some
future directions I&amp;#8217;ve been looking at, including a new gem for writing HTTP/2
clients and servers, as well as a new HTTP client library. I hope to be able to
make some progress on these as well in the coming month (or two.) &lt;a href=&quot;/about&quot;&gt;Let me
know&lt;/a&gt; if you have any question about any of this stuff. If you care
about my work, please consider &lt;a href=&quot;https://github.com/sponsors/ciconia&quot;&gt;becoming a sponsor on
Github&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>My Open Source Ruby Gems - February 2022 Report</title><link>https://noteflakes.com/articles/2022-03-05-february-summary</link><guid>https://noteflakes.com/articles/2022-03-05-february-summary</guid><pubDate>Sat, 05 Mar 2022 00:00:00 GMT</pubDate><description>&lt;blockquote&gt;
  &lt;p&gt;I&amp;#8217;m an independent software developer working mostly on Ruby apps. In the line
of my work, I&amp;#8217;ve developing various &lt;a href=&quot;https://github.com/digital-fabric&quot;&gt;open-source
projects&lt;/a&gt; spanning the gamut from low-level
concurrency constructs to database and frontend concerns. If you care about my
work, please consider &lt;a href=&quot;https://github.com/sponsors/ciconia&quot;&gt;becoming a sponsor on
Github&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here&amp;#8217;s a summary of my open-source work in February:&lt;/p&gt;

&lt;h2 id=&quot;polyphony---fiber-based-concurrency-for-ruby&quot;&gt;Polyphony - fiber-based concurrency for Ruby&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt; got &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commits?author=ciconia&amp;amp;since=2022-01-31&amp;amp;until=2022-03-01&quot;&gt;a lot of my
attention&lt;/a&gt;
in the last month, and I spent a lot of time working on mostly low-level
details:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Added supported for IPv6 addresses.&lt;/li&gt;
  &lt;li&gt;Improved handling of process signals.&lt;/li&gt;
  &lt;li&gt;Improved behaviour of SSL sockets.&lt;/li&gt;
  &lt;li&gt;Improved error handling in forked processes.&lt;/li&gt;
  &lt;li&gt;Improved compatibility of drop-in fiber-aware &lt;code&gt;Queue&lt;/code&gt; implementation with
stock Ruby API.&lt;/li&gt;
  &lt;li&gt;Overhauled the tracing subsystem for tracing backend events. This feature lets
developers track what&amp;#8217;s happening at the backend level - scheduling and
switching fibers, polling for I/O completion etc. This tracing subsystem will
eventually be used to build an interface for monitoring the behaviour of
fibers in live Polyphony-based apps, similar to the &lt;a href=&quot;https://www.erlang.org/doc/apps/observer/observer_ug.html&quot;&gt;Erlang
observer&lt;/a&gt;, or the
new &lt;a href=&quot;https://tokio.rs/blog/2021-12-announcing-tokio-console&quot;&gt;tokio-console&lt;/a&gt;
for Rust.&lt;/li&gt;
  &lt;li&gt;Refactored, cleaned up and documented a whole lot of code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The documentation effort is still ongoing, and hopefully I&amp;#8217;ll be able to create
a new website for the project in the coming months. My plan is to be able to put
out a version 1.0 sometime in 2022.&lt;/p&gt;

&lt;h2 id=&quot;extralite---a-fast-ruby-gem-for-working-with-sqlite3-databases&quot;&gt;Extralite - A fast Ruby gem for working with SQLite3 databases&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/extralite&quot;&gt;Extralite&lt;/a&gt; has also been a
principal focus of my attention in February. I worked on two big features:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Prepared statements - now users of Extralite can create prepared statements
which can then be used multiple times for querying, with the same flexible API
that lets you get results in a variety of ways (hash, array, single column,
single row, or single value):&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;extralite&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Extralite&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Database&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;my.db&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stmt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;select count(*) from order_items where order_id = :order_id&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query_single_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;order_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;stmt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query_single_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;order_id: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Prepared statements can provide a nice performance boost (on top of
Extralite&amp;#8217;s already &lt;a href=&quot;https://github.com/digital-fabric/extralite#performance&quot;&gt;very fast
performance&lt;/a&gt;) as they
are compiled only once and can then be reused repeatedly without recompiling
on each invocation.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;A bundled version of Extralite now includes the latest version of SQLite
(currently at version 3.38.0). This provides a double advantage: there&amp;#8217;s no
need to install the sqlite3 lib on your system, and you can use the latest
features provided by SQLite. In order to use the bundled Extralite gem, just
add &lt;code&gt;gem &#39;extralite-bundle&#39;&lt;/code&gt; instead of &lt;code&gt;gem &#39;extralite&#39;&lt;/code&gt; to your Gemfile. The
API stays exactly the same.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;papercraft---composable-templating-for-ruby&quot;&gt;Papercraft - composable templating for Ruby&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt; also got a lot of
attention in the last month:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Added support for XML namespaced tags and attributes.&lt;/li&gt;
  &lt;li&gt;Added bundled SOAP extension for creating SOAP request/response bodies
(contributed by &lt;a href=&quot;https://github.com/aemadrid&quot;&gt;@aemadrid&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Refactored, cleaned up and documented the entire code base.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Papercraft is used to generate the web page you&amp;#8217;re currently reading: the &lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/articles/2022-03-05-february-summary.md&quot;&gt;page
content&lt;/a&gt;
is in [Markdown], the &lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/_layouts/article.rb&quot;&gt;article
layout&lt;/a&gt;
is a Papercraft template, derived from the &lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/_layouts/default.rb&quot;&gt;default
layout&lt;/a&gt;.
The RSS feed for this website is also
&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/feeds/rss.rb&quot;&gt;implemented&lt;/a&gt;
as a Papercraft template.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m looking for contributors willing to integrate Papercraft into Rails. Let me
know if you&amp;#8217;re interested!&lt;/p&gt;

&lt;h2 id=&quot;impression---a-web-framework-wip&quot;&gt;Impression - a web framework (WIP)&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/impression&quot;&gt;Impression&lt;/a&gt; is a relatively
recent project I put together for building the present website (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com&quot;&gt;code
here&lt;/a&gt;). It&amp;#8217;s still more of a rough
sketch than anything serious, but it presents a novel way (I believe) to look at
web apps.&lt;/p&gt;

&lt;p&gt;Instead of the standard MVC pattern (and related patterns as well,) Impression
is based around a single entity - the resource. A resource lives in a tree
structure (rooted in a root resource). Different kinds of resources provide
different functionalities. For example, there&amp;#8217;s a resource for serving static
files, there&amp;#8217;s a resource for running a Rack app, there&amp;#8217;s a resource for serving
Jamstack-like websites, etc.&lt;/p&gt;

&lt;p&gt;An Impression-based application is then simply a tree of resources. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
* /               =&amp;gt; Impression::App
|
+-* /static       =&amp;gt; Impression::FileTree
|
+-* /api
  |
  +-* /api/users  =&amp;gt; Impression::RestfulAPI
  |
  +-* /api/orders =&amp;gt; Impression::RestfulAPI
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each resource above is mounted at a specific location in the URL namespace.
Incoming HTTP requests are first routed to the corresponding resource, which
then handles the request and generates a response.&lt;/p&gt;

&lt;p&gt;So the idea is to be able to build an app out of those different resource types,
and for each resource to be designed to fit the specific functionality required.
This design seems to me to be both simple and flexible.&lt;/p&gt;

&lt;p&gt;The resource used for the present website is called &lt;code&gt;Impression::App&lt;/code&gt;. It
resembles a Jamstack app in that it renders any static assets in the given file
directory, but it can also render markdown files with layouts, &lt;em&gt;and&lt;/em&gt;
dynamically load resource modules written in Ruby. For example, the Noteflakes
website has a &lt;a href=&quot;/ping&quot;&gt;&lt;code&gt;/ping&lt;/code&gt;&lt;/a&gt; endpoint, which simply responds with a &lt;code&gt;pong&lt;/code&gt;.
Here&amp;#8217;s the source code for the endpoint:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# In this case, the endpoint is a basic resource with a custom response block.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;export_default&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Impression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;pong&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That way, an Impression application can be composed of a bunch of dynamically
loaded resources that are &amp;#8220;mounted&amp;#8221; according to their location in the app&amp;#8217;s
file tree. In the future, Impression apps will also be able to automatically
reload resource files that have been updated. I hope to be able to write more
about impression in due time.&lt;/p&gt;

&lt;h2 id=&quot;h1p---http1-parser-for-ruby&quot;&gt;H1P - HTTP/1 parser for Ruby&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/h1p&quot;&gt;H1P&lt;/a&gt; got the ability to &lt;a href=&quot;https://github.com/digital-fabric/h1p/commit/62868679a164af859e8153d5c50e0f4cf567ecad&quot;&gt;parse HTTP/1
responses&lt;/a&gt;
as well as requests, so now you can also use it to implement HTTP clients.
Here&amp;#8217;s a basic example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;socket&#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;h1p&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;ipinfo.io&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H1P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# send request&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GET / HTTP/1.1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Host: ipinfo.io&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# read response&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_headers&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_body&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice how the code above doesn&amp;#8217;t need to deal with reading or buffering
incoming data. It&amp;#8217;s automatically handled by the H1P parser, and what you get in
the end is a hash with the response headers, and of course the response body.
It&amp;#8217;s that simple! For more information, go to the &lt;a href=&quot;https://github.com/digital-fabric/h1p&quot;&gt;H1P project
page&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;perspectives-for-this-month&quot;&gt;Perspectives for this month&lt;/h2&gt;

&lt;p&gt;In the month of March, I&amp;#8217;ll be concentrating on the following areas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Polyphony: I&amp;#8217;ve written in the past about being able to &lt;a href=&quot;./2021-10-05-a-compositional-approach-to-ruby-performance&quot;&gt;express I/O
operations in terms of a composition of
steps.&lt;/a&gt; Lately I&amp;#8217;ve
been thinking about how that relates to being able to do data compression on
the fly. One use case for that would be sending and receving gzip&amp;#8217;ed or
deflated HTTP responses.&lt;/p&gt;

    &lt;p&gt;As I always start with designing the API, I came up with this:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;ResponseExtensions&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serve_io_gzipped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gzip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;serve_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;The idea here is to maximize performance and minimize allocations and GC
pressure by doing as much of the work using pipes, which are basically &lt;a href=&quot;https://yarchive.net/comp/linux/splice.html&quot;&gt;kernel
buffers&lt;/a&gt;. We do need to spin up a
fiber but that&amp;#8217;s the only cost, and thanks to Polyphony&amp;#8217;s innovative design,
we get both optimized I/O scheduling and automatic back-pressure.&lt;/p&gt;

    &lt;p&gt;I&amp;#8217;ll dedicate a future article to how this mechanism once it is implemented.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;H2P: This is a new gem for implementing HTTP/2 servers and clients. The idea
is to provide the same kind blocking API design that underlies
&lt;a href=&quot;https://github.com/digital-fabric/h1p&quot;&gt;H1P&lt;/a&gt; (discussed above). Here&amp;#8217;s an
example of how an HTTP/2 server implementation might look like:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_incoming_http2_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h2p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H2P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Peer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# each stream gets its own fiber&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handle_http2_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_http2_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;stream_adapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H2StreamAdapter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stream_adapter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;H2StreamAdapter&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;done: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@h2p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@stream_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;done: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Inuit - this is another new gem that I&amp;#8217;m currently designing, meant to be a
Polyphony-based HTTP client with the following features:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Support for HTTP/1, HTTP/2, WebSocket.&lt;/li&gt;
      &lt;li&gt;Support for HTTPS, WSS.&lt;/li&gt;
      &lt;li&gt;Persistent connections, connection pooling.&lt;/li&gt;
      &lt;li&gt;Support for sessions and cookies.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please &lt;a href=&quot;/about#contact&quot;&gt;let me know&lt;/a&gt; if my work interests you. You can delve
into my code &lt;a href=&quot;https://github.com/noteflakes&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Papercraft - Composable Templating for Ruby</title><link>https://noteflakes.com/articles/2022-02-04-papercraft</link><guid>https://noteflakes.com/articles/2022-02-04-papercraft</guid><pubDate>Fri, 04 Feb 2022 00:00:00 GMT</pubDate><description>&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt; is a new Ruby gem
I&amp;#8217;ve been working on, that provides a new way to render HTML, XML and JSON using
plain Ruby. Here&amp;#8217;s what it looks like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;papercraft&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Some title&#39;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit_yield&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Page title&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Some title&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, a Ruby DSL for constructing HTML, XML or JSON is nothing new. Here&amp;#8217;s
a list of previous projects that already do that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/activeadmin/arbre&quot;&gt;Arbre&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/erector/erector&quot;&gt;Erector&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ageweke/fortitude&quot;&gt;Fortitude&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/markaby/markaby&quot;&gt;Markaby&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/rails/jbuilder&quot;&gt;Jbuilder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above offer some variation on the same theme: you construct HTML
using nested Ruby blocks, e.g.:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I&amp;#8217;ve been a long time admirer of Ruby procs. To me, procs are perhaps the single
most unique feature of Ruby, especially in their block form:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_something_with&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you call a method with a block, you are in injecting a piece of your own
code into some other code, and this lets you compose different pieces of code in
a variety of ways. The way Ruby blocks are integrated into the language is
perhaps the defining feature of Ruby, and to me it&amp;#8217;s one of the main reasons
Ruby makes developers happy.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve been playing for a while now with all kinds of ideas for constructing DSLs
using blocks. One project I created a while ago was Rubyoshka, a Ruby gem that
lets you write HTML using plain Ruby syntax. The name was a nod to the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Matryoshka_doll&quot;&gt;Matryoshka&lt;/a&gt;, the Russian nesting
doll.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve recently came up with a way to make it even better, and have decided to
give this library a new name, henceforth
&lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft&lt;/a&gt;. Papercraft is unique
in how it embraces Ruby procs, and how it enables developers to express the
different parts of an HTML page (or XML and JSON documents) using procs on one
hand, and to compose those different parts in a variety of ways on the other
hand.&lt;/p&gt;

&lt;h2 id=&quot;ruby-procs-as-templates&quot;&gt;Ruby procs as templates&lt;/h2&gt;

&lt;p&gt;The idea behind Papercraft is simple: a template is a
&lt;a href=&quot;https://rubyapi.org/3.1/o/proc&quot;&gt;Proc&lt;/a&gt; that can be rendered by executing it in
the context of a special-purpose rendering object, using
&lt;a href=&quot;https://rubyapi.org/3.1/o/basicobject#method-i-instance_exec&quot;&gt;&lt;code&gt;#instance_exec&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ruby procs can take positional and/or named parameters, which lets us explicitly
pass dynamic values to templates:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;foobar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;hi&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bye&#39;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above proc (strictly speaking it&amp;#8217;s a
&lt;a href=&quot;https://rubyapi.org/3.1/o/proc#class-Proc-label-Lambda+and+non-lambda+semantics&quot;&gt;lambda&lt;/a&gt;,
which is a special kind of proc) takes two parameters, &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt;, which
affect its rendered output.&lt;/p&gt;

&lt;p&gt;Ruby procs, being first-class objects, can also be composed and derived. For
example, we can take the above &lt;code&gt;foobar&lt;/code&gt; proc and use it in the context of a list
template:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;foobar_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foobar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bar&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We could also create a derivative of &lt;code&gt;foobar&lt;/code&gt; where &lt;code&gt;foo&lt;/code&gt; is applied:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;applied_foobar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foobar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;hi&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Note the use of &lt;code&gt;#emit&lt;/code&gt; in the last two example, more on that below.)&lt;/p&gt;

&lt;p&gt;So, once templates are expressed as procs, they can be composed, combined and
transformed in a variety of ways, including all the ways proc objects can be
manipulated. In fact, the primary template class in Papercraft,
&lt;code&gt;Papercraft::Template&lt;/code&gt;, is a subclass of &lt;code&gt;Proc&lt;/code&gt;, which means it can be
exchanged for a proc at any place.&lt;/p&gt;

&lt;h2 id=&quot;emitting-html&quot;&gt;Emitting HTML&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s start with the basics: Papercraft templates produce HTML by emitting tags.
This is done primarily by making method calls with the tagname, followed by the
tag content and any tag attributes. Here are some examples:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;p&amp;gt;foo&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;https://example.com/&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;a href=&quot;https://example.com&quot;&amp;gt;foo&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Title&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;title&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h1 id=&quot;title&quot;&amp;gt;Title&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tags are nested by using blocks:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;id: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;hi&#39;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;div id=&quot;foo&quot;&amp;gt;&amp;lt;div id=&quot;bar&quot;&amp;gt;&amp;lt;p&amp;gt;hi&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to render text and have it escaped correctly, you can use the &lt;code&gt;#text&lt;/code&gt;
method:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;pre&#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39; foo &amp;amp; bar &#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;post&#39;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;pre&amp;lt;/span&amp;gt; foo &amp;amp;amp; bar &amp;lt;span&amp;gt;post&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Other content (such as raw text, nested templates, markdown etc.) can be
rendered by using the &lt;code&gt;#emit&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;&amp;lt;h1&amp;gt;hi&amp;lt;/h1&amp;gt;&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h1&amp;gt;hi&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;p&amp;gt;foo&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;## Hi&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h2 id=&quot;hi&quot;&amp;gt;Hi&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Papercraft includes built-in support for rendering Markdown. More on that
below.)&lt;/p&gt;

&lt;h2 id=&quot;explicit-template-parameters&quot;&gt;Explicit template parameters&lt;/h2&gt;

&lt;p&gt;The most important difference between Papercraft and all of its predecessors is
the fact that in Papercraft, any variables referenced in the template logic
should be passed explicitly to the template when it is rendered:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;greeter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;world&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we create a template that takes a single named argument,
&lt;code&gt;name:&lt;/code&gt;. When we want to render the template, we need to supply the a &lt;code&gt;name:&lt;/code&gt;
parameter, which is then injected by the template code into the resulting HTML.&lt;/p&gt;

&lt;p&gt;This way of injecting data into templates offers multiple advantages: the data
flow is much clearer - since you&amp;#8217;re not implicitly reyling on variables that
just happen to be in your template&amp;#8217;s binding, and debugging is easier - if you
forget to provide the &lt;code&gt;name&lt;/code&gt; parameter, Ruby will tell you!&lt;/p&gt;

&lt;h2 id=&quot;parameter-application&quot;&gt;Parameter application&lt;/h2&gt;

&lt;p&gt;Papercraft takes this idea even further by letting you create a derivative
template by applying parameters to the source template, using the &lt;code&gt;#apply&lt;/code&gt;
method. You can select to do a full or partial application:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;greeter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;greeting&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;goodbyer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;greeting: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Goodbye&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;goodbyer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;world&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h1&amp;gt;Goodbye, world!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we take our &lt;code&gt;greeter&lt;/code&gt; and make it a bit more general - it
now also takes a &lt;code&gt;greeting:&lt;/code&gt; argument. We then create a derivative template by
partially applying &lt;code&gt;greeter&lt;/code&gt; with just the &lt;code&gt;greeting:&lt;/code&gt; parameter filled in. We
then render the &lt;code&gt;goodbyer&lt;/code&gt; template, passing in the missing &lt;code&gt;name:&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h2 id=&quot;block-application&quot;&gt;Block application&lt;/h2&gt;

&lt;p&gt;The idea of application can be taken further with the use of applied blocks.
Here&amp;#8217;s how you do derivative layouts in Papercraft:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit_yield&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;article_layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markdown_content&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit_markdown&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;markdown_content&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;article_layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;title: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;markdown_content: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;## Bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we first create a layout. This layout creates a generic
HTML structure, with the &lt;code&gt;body&lt;/code&gt; section containing a call to &lt;code&gt;emit_yield&lt;/code&gt;, which
expects a block to be injected. This can be done either using &lt;code&gt;#apply&lt;/code&gt; or
&lt;code&gt;#render&lt;/code&gt; and passing a block. We then create a derivative layout that applies a
block to be emitted inside the &lt;code&gt;body&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Notice how parameters passed to the &lt;code&gt;layout&lt;/code&gt; template are explicitly passed
along to the applied block (in the call to &lt;code&gt;#emit_yield&lt;/code&gt;), and how they are
destructured in the block given to &lt;code&gt;layout.apply&lt;/code&gt;. Finally, we can render the
&lt;code&gt;article_layout&lt;/code&gt; to HTML by calling &lt;code&gt;#render&lt;/code&gt; with the needed parameters.&lt;/p&gt;

&lt;h2 id=&quot;template-composition&quot;&gt;Template composition&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;#emit_yield&lt;/code&gt; is not the only way to pass in arbitrary blocks to a
template. In fact, you can pass any number of template blocks as parameters into
your template, and then use &lt;code&gt;#emit&lt;/code&gt; to emit them. Here&amp;#8217;s another way layout
templates can be created with Papercraft:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;footer&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;footer&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;header:   &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;some header&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;content:  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Some content&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;footer:   &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;footer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;some footer&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;higher-order-templates&quot;&gt;Higher order templates&lt;/h2&gt;

&lt;p&gt;We can create higher order templates by writing methods (or procs) that take a
template as input and return a composed template as output, normally :&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;div_wrap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;templ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;templ&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;div_wrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;hi&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;body&amp;gt;&amp;lt;div&amp;gt;&amp;lt;h1&amp;gt;hi&amp;lt;/h1&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/body&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we create a higher order template called &lt;code&gt;div_wrap&lt;/code&gt;. It
takes as an input a given template, and returns as its output a template
wrapping the original template with a &lt;code&gt;div&lt;/code&gt; element.&lt;/p&gt;

&lt;h2 id=&quot;rendering-markdown&quot;&gt;Rendering Markdown&lt;/h2&gt;

&lt;p&gt;Papercraft has built-in support for rendering Markdown. Markdown can be
converted to HTML by calling the &lt;code&gt;#markdown&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;markdown&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;## Hi&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &quot;&amp;lt;h2 id=\&quot;id\&quot;&amp;gt;Hi&amp;lt;/h2&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In HTML templates you can also use the &lt;code&gt;#emit_markdown&lt;/code&gt; convenience method:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;emit_markdown&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;## Hi&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h2 id=&quot;hi&quot;&amp;gt;Hi&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Papercraft uses &lt;a href=&quot;https://github.com/gettalong/kramdown/&quot;&gt;&lt;code&gt;Kramdown&lt;/code&gt;&lt;/a&gt; and
&lt;code&gt;rouge&lt;/code&gt;(https://github.com/rouge-ruby/rouge) to convert Markdown to HTML with
support for syntax-highlighted code blocks. The &lt;a href=&quot;https://github.com/digital-fabric/papercraft/blob/42fcf74a8c7dbfd28052ee872be8d6d255cac947/lib/papercraft.rb#L93-L100&quot;&gt;default Markdown
options&lt;/a&gt;
can be overriden by passing them to &lt;code&gt;#markdown&lt;/code&gt; or &lt;code&gt;#emit_markdown&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# render markdown without heading ids&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;emit_markdown&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;## Hi&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;auto_ids: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &amp;lt;h2&amp;gt;Hi&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;xml-and-json-templates&quot;&gt;XML and JSON templates&lt;/h2&gt;

&lt;p&gt;Papercraft XML templates work just like HTML templates:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;xml&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;movies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;movies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;movie&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;title&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;year&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;director&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;director&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here&amp;#8217;s how a JSON template can be generated:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;json&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;movies&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;movies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;     &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;title&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;year&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;year&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;director&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;director&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;extending-papercraft&quot;&gt;Extending Papercraft&lt;/h2&gt;

&lt;p&gt;Since the main goal of Papercraft is to allow developers to produce dynamic
HTML, XML or JSON with the least amount of code, it also includes the
possibility of creating extensions that provide a convenient API for creating
complex HTML components. This might be particularly useful when using design
frameworks such as Bootstrap or Tailwind, where some components demand quite
complex markup. Here&amp;#8217;s an example of how a Bootstrap extension might look like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;BootstrapComponents&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;card&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;card&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;card-body&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;emit_yield&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;card_title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h5&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;card-title&#39;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;bootstrap: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BootstrapComponents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;my_card&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;style: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;width: 18rem&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card_title&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Card title&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card_subtitle&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Card subtitle&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card_text&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Some quick example text.&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card_link&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;#&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Card link&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;card_link&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;#&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Another link&#39;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-papercraft-is-used-by-this-website&quot;&gt;How Papercraft is Used by this website&lt;/h2&gt;

&lt;p&gt;Papercraft is used by this website, which is, just so you know, not a static
website. While the source layout of this website largely resembles a
&lt;a href=&quot;https://jamstack.org/&quot;&gt;Jamstack&lt;/a&gt; website, it is rendered dynamically by
&lt;a href=&quot;https://github.com/digital-fabric/impression&quot;&gt;Impression&lt;/a&gt;, a web framework I&amp;#8217;m
currently developing for my own use, which in turn relies heavily on Papercraft
for dealing with layouts.&lt;/p&gt;

&lt;p&gt;So let&amp;#8217;s explore how this website is constructed. The file for the present
article is just a markdown file with some YAML front matter
(&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/articles/2022-02-04-papercraft.md&quot;&gt;permalink&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-md highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Papercraft - Composable Templating for Ruby&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;article&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;

Papercraft is a new Ruby gem I&#39;ve been working on, that provides a new way to
render HTML, XML and JSON using plain Ruby. Here&#39;s what it looks like:

...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here&amp;#8217;s the layout used for rendering articles
(&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/_layouts/article.rb&quot;&gt;permalink&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;./default&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export_default&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;article&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;h3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strftime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;%d·%m·%Y&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;date&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit_yield&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;article&lt;/code&gt; layout above imports the default layout (using
&lt;a href=&quot;https://github.com/digital-fabric/modulation&quot;&gt;Modulation&lt;/a&gt;), and uses &lt;code&gt;#apply&lt;/code&gt;
to create a derivative layout that adds an article element and expects a nested
block to be injected into it.&lt;/p&gt;

&lt;p&gt;And here is the default layout (&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/_layouts/default.rb&quot;&gt;permalink&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;papercraft&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export_default&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;html5&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Noteflakes - &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Noteflakes&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;charset: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;utf-8&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;viewport&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;content: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;width=device-width, initial-scale=1.0&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;style&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;body { display: none }&#39;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# prevent FUOC&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;rel: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;icon&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;image/png&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/assets/nf-icon-black.png&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;rel: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;stylesheet&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;text/css&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/assets/style.css&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;rel: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;alternate&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;application/rss+xml&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/feeds/rss&#39;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;src: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/assets/nf-icon-black.png&#39;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;noteflakes&#39;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ul&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;by Sharon Rosner&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;byline&#39;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;archive&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/archive&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;about&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/about&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;RSS feed&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/feeds/rss&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;code&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;https://github.com/noteflakes&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;target: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;_blank&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit_yield&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;footer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;hr&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Copyright © 2021 Sharon Rosner. This site runs on &#39;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Impression&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;https://github.com/digital-fabric/impression&#39;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39; and &#39;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Tipi&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;href: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;https://github.com/digital-fabric/tipi&#39;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;span&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;.&#39;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, Impression takes the layout referenced in the article&amp;#8217;s front matter,
and renders it by passing a block that renders the markdown:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;render_markdown_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;request: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;resource: &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:html_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mime_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Other templates on this website produce RSS and a &lt;a href=&quot;https://www.jsonfeed.org/&quot;&gt;JSON
feed&lt;/a&gt;. Here&amp;#8217;s the RSS template
(&lt;a href=&quot;https://github.com/noteflakes/noteflakes.com/blob/main/sites/noteflakes.com/feeds/rss.rb&quot;&gt;permalink&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;papercraft&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;export_default&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;xml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;mime_type: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;text/xml; charset=utf-8&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;rss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;version: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;2.0&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;xmlns:atom&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;http://www.w3.org/2005/Atom&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Noteflakes&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;https://noteflakes.com/&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;A website by Sharon Rosner&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;en-us&#39;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pubDate&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;httpdate&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;&amp;lt;atom:link href=&quot;https://noteflakes.com/feeds/rss&quot; rel=&quot;self&quot; type=&quot;application/rss+xml&quot; /&amp;gt;&#39;&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;article_entries&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;page_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;/articles&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reverse&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;article_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://noteflakes.com&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;guid&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://noteflakes.com&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;pubDate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;httpdate&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:html_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-note-on-papercrafts-design&quot;&gt;A note on Papercraft&amp;#8217;s design&lt;/h2&gt;

&lt;p&gt;You will have noticed that Papercraft&amp;#8217;s DSL looks quite terse, and that is
because all of its API consists basically of unqualified method calls that look
like &lt;code&gt;html { body { h1 &#39;foo&#39; } }&lt;/code&gt;. This might look confusing to the young Ruby
padawan, as they might ask &amp;#8220;where do all these calls go, and how are they
intercepted?&amp;#8221;&lt;/p&gt;

&lt;p&gt;The answer is that all template procs are evaulated in the context of a
Papercraft
&lt;a href=&quot;https://github.com/digital-fabric/papercraft/blob/master/lib/papercraft/renderer.rb&quot;&gt;renderer&lt;/a&gt;
instance, which intercepts all calls and turns them into chunks of HTML/XML that
are added to an internal buffer (the &lt;a href=&quot;https://github.com/digital-fabric/papercraft/blob/master/lib/papercraft/json.rb&quot;&gt;JSON
renderer&lt;/a&gt;
works in a slightly different manner).&lt;/p&gt;

&lt;p&gt;On one hand, this allows us to write templates with a minimum of boilerplate,
and have templates that look very clean. On the other hand, it does prevents us
notably from using instance variables, e.g. &lt;code&gt;@foo&lt;/code&gt; in our templates, and doesn&amp;#8217;t
let us use any methods in the scope of the receiver where we create our
template. For example, the following code will fail to produce the desired
result:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Foo&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_html&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Papercraft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bar&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_html&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; not what you might expect...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, when writing Papercraft templates we need to follow a few rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Any variables or data referenced inside a template must be provided to the
template as an explicit argument.&lt;/li&gt;
  &lt;li&gt;Any method calls that are not expected to emit HTML should be qualified, i.e.
the receiver should be referenced explicitly, e.g.: &lt;code&gt;receiver.foo&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;No instance variables (or class variables, for that matter) should be used in
Papercraft templates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Papercraft is a new Ruby gem that lets you dynamically generate HTML, XML and
JSON documents using plain Ruby. Papercraft templates use explicit parameter
passing in order to &amp;#8220;bind&amp;#8221; template variables, and use application and
composition to combine templates in a variety of ways. This website is the first
to use Papercraft &amp;#8220;in production&amp;#8221;, and I hope other people will find it useful.&lt;/p&gt;

&lt;p&gt;To learn more about Papercraft, checkout the &lt;a href=&quot;https://www.rubydoc.info/gems/papercraft&quot;&gt;API
documentation&lt;/a&gt;. Contributions in the
form of issues or pull requests will be gladly accepted on the &lt;a href=&quot;https://github.com/digital-fabric/papercraft&quot;&gt;Papercraft
repository&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Extralite - a new Ruby gem for working with SQLite databases</title><link>https://noteflakes.com/articles/2021-12-15-extralite</link><guid>https://noteflakes.com/articles/2021-12-15-extralite</guid><pubDate>Wed, 15 Dec 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last year I&amp;#8217;ve been working a lot with &lt;a href=&quot;https://sqlite.org/&quot;&gt;SQLite&lt;/a&gt;
databases. I started by using the popular sqlite3-ruby Ruby gem, but quickly
noticed that for &lt;em&gt;my usage&lt;/em&gt; there were a few things missing in the gem&amp;#8217;s
&lt;a href=&quot;https://www.rubydoc.info/gems/sqlite3/&quot;&gt;API&lt;/a&gt;. Being a tinkerer, and having had
some experience writing
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;C-extensions&lt;/a&gt;, I had a look at the
SQLite C API and decided to try to write my own Ruby bindings for SQLite. Thus
Extralite was born.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/extralite&quot;&gt;Extralite&lt;/a&gt; is an extra-lightweight
(less than 460 lines of C-code) SQLite3 wrapper for Ruby. It provides a single
class with a [minimal set of methods] for interacting with an SQLite3 database.
Extralite provides the following improvements over the sqlite3-ruby gem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Improved concurrency for multithreaded apps: the Ruby GVL is released while
preparing SQL statements and while iterating over results.&lt;/li&gt;
  &lt;li&gt;Super fast - up to 13x faster than sqlite3-ruby.&lt;/li&gt;
  &lt;li&gt;Automatically execute SQL strings containing multiple semicolon-separated
queries (handy for creating/modifying schemas).&lt;/li&gt;
  &lt;li&gt;Access data in a variety of ways: rows as hashes, rows as arrays, single row,
single column, single value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;concurrency&quot;&gt;Concurrency&lt;/h2&gt;

&lt;p&gt;One of the most important limitations of the sqlite3-ruby gem is that it doesn&amp;#8217;t
release the GVL while running queries and fetching rows from the database. This
means that if any query takes a significant amount of time to execute, other
threads will also be blocked while the query is running.&lt;/p&gt;

&lt;p&gt;There has been &lt;a href=&quot;https://github.com/sparklemotion/sqlite3-ruby/issues/287&quot;&gt;some
discussion&lt;/a&gt; on the
sqlite3-ruby repository as to why this is. Basically, since developers can
define their own SQLite functions, aggregates and collations using Ruby, the gem
needs to hold on to the GVL while running queries, since those might call back
into Ruby code.&lt;/p&gt;

&lt;p&gt;Extralite does release the GVL when running queries, which makes it much more
friendly to multithreaded code. While Extralite is busy fetching a row, other
threads can continue running. If your program has multiple threads accessing
SQLite databases at the same time, you&amp;#8217;ll get much better usage out of your
multicore machine.&lt;/p&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;Preliminary benchmarks show Extralite to be significantly faster than
sqlite3-ruby. The
&lt;a href=&quot;https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb&quot;&gt;benchmark&lt;/a&gt;
included in the Extralite repository creates a database with varying number of
rows, then meausres the time it takes for sqlite3-ruby and Extralite to fetch
those rows. The performance advantage becomes more pronounced as the number of
rows is increased:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Row count&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;sqlite3-ruby&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Extralite&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Relative&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;56.48K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;91.52K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1.62x&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1K&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;256.3K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1758K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;6.87x&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;100K&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;176.5K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2323.6K rows/s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;13.17x&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;These results surprised me quite a bit, since Extralite &lt;em&gt;does&lt;/em&gt; release the GVL
on each fetched row, but I guess this is more than made up for by the fact that
Extralite makes a minimum of allocations and offers a significantly smaller API
surface area, compared with sqlite3-ruby.&lt;/p&gt;

&lt;h2 id=&quot;other-features&quot;&gt;Other features&lt;/h2&gt;

&lt;p&gt;Extralite provides a variety of ways to get query results: rows as hashes, rows
as arrays, a single column, a single row, or a single value. While sqlite3-ruby
has most of these (except for iterating over a single column), you need to set a
mode (&lt;code&gt;SQLite3::Database#results_as_hash&lt;/code&gt;) if you want to fetch rows as hashes.&lt;/p&gt;

&lt;p&gt;Another important feature of Extralite is that it automatically executes SQL
strings containing multiple SQL statements (separated with a semicolon.) In
sqlite3-ruby you need to use a separate API (&lt;code&gt;#execute_batch&lt;/code&gt;) in order to do
that.&lt;/p&gt;

&lt;p&gt;Other features, such as binding indexed and named parameters, getting the last
inserted rowid, getting the number of changes made in the last query or loading
extensions, are available in both gems.&lt;/p&gt;

&lt;h2 id=&quot;whats-missing&quot;&gt;What&amp;#8217;s missing&lt;/h2&gt;

&lt;p&gt;Extralite is notably missing the ability to define custom functions, aggregates
and collations using Ruby. If you rely on that feature, you&amp;#8217;ll need to use
sqlite3-ruby.&lt;/p&gt;

&lt;h2 id=&quot;usage-with-orms&quot;&gt;Usage with ORMs&lt;/h2&gt;

&lt;p&gt;Extralite includes an adapter for
&lt;a href=&quot;https://github.com/jeremyevans/sequel&quot;&gt;Sequel&lt;/a&gt;. If you wish to switch from
sqlite3-ruby to Extralite, you can just add &lt;code&gt;extralite&lt;/code&gt; to your &lt;code&gt;Gemfile&lt;/code&gt;, and
then change your database URLs to use the &lt;code&gt;extralite&lt;/code&gt; schema instead of
&lt;code&gt;sqlite&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;DB&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Sequel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;extralite://my.db&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What about ActiveRecord? Well, I tried, but after spending a few hours looking
at the ActiveRecord SQLite adapter code and trying to make sense out of that,
all I got was cryptic error messages. I finally decided to abandon the effort.
If you have experience writing ActiveRecord database adapters, I&amp;#8217;ll greatly
appreciate your contribution. Let me know on the &lt;a href=&quot;https://github.com/digital-fabric/extralite&quot;&gt;Extralite
repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;future-directions&quot;&gt;Future directions&lt;/h2&gt;

&lt;p&gt;Extralite is pretty much feature-complete as far as I&amp;#8217;m concerned, apart from
missing an ActiveRecord adapter. I&amp;#8217;m currently thinking about how to adapt the
different abstractions I came up with while working with SQLite databases, but
those will be published in a separate project.&lt;/p&gt;

&lt;p&gt;In the meanwhile, if you have suggestions for improving Extralite, or wish to
contribute, please let me know. I&amp;#8217;ll gladly accept issues and PRs! The
documentation for the Extralite gem can be found
&lt;a href=&quot;https://www.rubydoc.info/gems/extralite&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Qeweney - a feature-rich HTTP request/response API for Ruby</title><link>https://noteflakes.com/articles/2021-12-03-qeweney</link><guid>https://noteflakes.com/articles/2021-12-03-qeweney</guid><pubDate>Fri, 03 Dec 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;As you may know, in the last few months I&amp;#8217;ve been working on
&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt;, a new web server for Ruby, with
innovative features such as support for HTTP/2, automatic SSL certificates, and
streaming request and response bodies. As part of the development process, I
also had to deal with how to represent HTTP requests and responses internally.&lt;/p&gt;

&lt;p&gt;Tipi being a modern web server, with emphasis on concurrency, performance, and
streaming, I felt it was wrong to base it on the
&lt;a href=&quot;https://github.com/rack/rack&quot;&gt;Rack&lt;/a&gt; interface. While Rack is today ubiquitous
in the Ruby ecosystem—it underlies basically all Ruby web frameworks and all
Ruby app servers—it has some important limitations, especially as regards being
able to perform &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Upgrade&quot;&gt;HTTP
upgrades&lt;/a&gt; and
the streaming of request and response bodies.&lt;/p&gt;

&lt;p&gt;Instead, the interface I came up with is a single &lt;code&gt;Request&lt;/code&gt; class, with an
imperative API design. I have extracted this interface into a separate gem, for
reasons that I&amp;#8217;ll discuss below. I&amp;#8217;m calling this interface
&lt;a href=&quot;https://github.com/digital-fabric/qeweney&quot;&gt;Qeweney&lt;/a&gt; (pronounced &amp;#8220;Q &lt;em&gt;&amp;#8216;n&amp;#8217;&lt;/em&gt; A&amp;#8221;). As
we&amp;#8217;ll see, Qeweney can be used inside of a Rack app, and can also be used to
drive a Rack app.&lt;/p&gt;

&lt;h2 id=&quot;the-request-interface&quot;&gt;The Request interface&lt;/h2&gt;

&lt;p&gt;Qeweney exposes a single class,
&lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/main/lib/qeweney/request.rb&quot;&gt;&lt;code&gt;Request&lt;/code&gt;&lt;/a&gt;.
An instance of &lt;code&gt;Request&lt;/code&gt; is initialized with headers and an adapter. The adapter is the
party actually responsible for the client connection. Tipi implements separate
adapters for
&lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/lib/tipi/http1_adapter.rb&quot;&gt;HTTP/1&lt;/a&gt;
and
&lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/lib/tipi/http2_stream.rb&quot;&gt;HTTP/2&lt;/a&gt;
connections. In the case of HTTP/2, the actual request adapter represents a
single HTTP/2 stream, backed by an HTTP/2 connection adapter, as shown in the
following diagram:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    +------+    +-------+                   +-------+    +-----+
    |Client|---&amp;gt;|HTTP/1 |------------------&amp;gt;|Request|---&amp;gt;|     |
    |      |&amp;lt;---|adapter|&amp;lt;------------------|       |&amp;lt;---|     |
    +------+    +-------+                   +-------+    |     |
                                                         |     |
    +------+    +-------+    +---------+    +-------+    |     |
    |Client|---&amp;gt;|       |---&amp;gt;|H2 stream|---&amp;gt;|Request|---&amp;gt;| Web |
    |      |&amp;lt;---|       |&amp;lt;---|adapter  |&amp;lt;---|       |&amp;lt;---| app |
    +------+    |HTTP/2 |    +---------+    +-------+    |     |
                |adapter|        ...                     |     |
                |       |    +---------+    +-------+    |     |
                |       |---&amp;gt;|H2 stream|---&amp;gt;|Request|---&amp;gt;|     |
                |       |----|adapter  |&amp;lt;---|       |&amp;lt;---|     |
                +-------+    +---------+    +-------+    +-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This design provides separation between the HTTP semantic layer (the
representation of different parts of HTTP requests and responses,) and the HTTP
transport layer, which changes according to the HTTP protocol version. In
addition, this design facilitates the testing of HTTP requests and responses,
for example with a &lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/main/test/helper.rb#L14-L24&quot;&gt;mock request
adapter&lt;/a&gt;.
It also allows the implementation of other transport methods, such as
&lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/3&quot;&gt;HTTP/3&lt;/a&gt;, or other custom transport
protocols for implementing custom HTTP proxies (I already have some ideas I&amp;#8217;ll
write about in the future.)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Request&lt;/code&gt; API includes methods for inspecting the request headers, reading
the request body, and finally responding to the request. The most important
differentiator between the Qeweney API and the Rack interface is that with
Qeweney the response is generated imperatively, by using one of the response
APIs, while in Rack the response is the return value of an app function that
takes a request as argument. Let&amp;#8217;s compare the two approaches:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Rack&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Qeweney&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello, world!&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;text/plain&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As shown above, a Rack app is a &lt;code&gt;Proc&lt;/code&gt; that takes a &lt;code&gt;Hash&lt;/code&gt; as its &lt;code&gt;env&lt;/code&gt; argument
(containing information about the request and the server) and returns an &lt;code&gt;Array&lt;/code&gt;
consisting of the HTTP status code, response headers and the response body
reprersented as an array.&lt;/p&gt;

&lt;p&gt;In contrast, a Qeweney-based app takes a &lt;code&gt;Qeweney::Request&lt;/code&gt; as its argument, and
it is the app&amp;#8217;s responsibility to respond to the request by &lt;em&gt;explicitly&lt;/em&gt; calling
one of the response methods, such as &lt;code&gt;#respond&lt;/code&gt;, &lt;code&gt;#send_headers&lt;/code&gt;, &lt;code&gt;#send_chunk&lt;/code&gt;
and &lt;code&gt;#finish&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;request-information&quot;&gt;Request information&lt;/h2&gt;

&lt;p&gt;One of the most crucial design decisions I made early on with Tipi was to allow
handling incoming requests without first reading and parsing the request body.
In Tipi, as soon as all request headers have been received, the request is
created and passed to the app. This allows an app to reject invalid or malicious
requests &lt;em&gt;before&lt;/em&gt; the request body is read. It also allows Ruby apps to apply
backpressure, as they can control reading the request body.&lt;/p&gt;

&lt;p&gt;With that in mind, let&amp;#8217;s examine how the request information is actually
represented in &lt;code&gt;Qeweney::Request&lt;/code&gt;. As we saw, a request is instantiated by
providing it with an adapter instance, and a hash containing the request
headers. The header keys are represented by convention using lower case, and
also include the following meta-headers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;:method&lt;/code&gt; - the HTTP method used (represented as an upper-case string.)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:path&lt;/code&gt; - the path specified in the HTTP request line (including query
parameters.)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:scheme&lt;/code&gt; - the protocol scheme used, i.e. &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to note that these conventions concerning request headers should
be adhered to by any request adapter. Finally those are the conventions that
allow apps to behave identically for both HTTP/1 and HTTP/2 connections.&lt;/p&gt;

&lt;p&gt;In order to facilitate the processing of request headers, Qeweney includes a
module named
&lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/main/lib/qeweney/request_info.rb&quot;&gt;&lt;code&gt;RequestInfoMethods&lt;/code&gt;&lt;/a&gt;
into the &lt;code&gt;Request&lt;/code&gt; class. These method help us deal with such things as &lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/10116eb5371157c968e224a8b58b6df331be9f23/lib/qeweney/request_info.rb#L53-L67&quot;&gt;URL
queries&lt;/a&gt;
and
&lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/10116eb5371157c968e224a8b58b6df331be9f23/lib/qeweney/request_info.rb#L86-L102&quot;&gt;cookies&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s an example of how these methods can be used:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;find_session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;session-id&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;validate_csrf_token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;x-csrf_token&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;page_no&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;page&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;serve_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page_no&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;request-body&quot;&gt;Request body&lt;/h2&gt;

&lt;p&gt;Once the app is ready to read the request body, it can use &lt;code&gt;Request#read&lt;/code&gt; for
reading the entire request body, or &lt;code&gt;Request#next_chunk&lt;/code&gt; and
&lt;code&gt;Request#each_chunk&lt;/code&gt; to read the request body in chunks.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s an example of how &lt;code&gt;#each_chunk&lt;/code&gt; can be used to create a server that
responds with the request body converted to upper case:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;upper_caser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_headers&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_chunk&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upcase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finish&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In order to facilitate the handling of forms, Qeweney also includes the
&lt;code&gt;Request.parse_form_data&lt;/code&gt; method which can be used to handle &lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/10116eb5371157c968e224a8b58b6df331be9f23/lib/qeweney/request_info.rb#L106-L182&quot;&gt;form submission&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;form_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;form&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_form_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;submit_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;:status&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CREATED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;responding&quot;&gt;Responding&lt;/h2&gt;

&lt;p&gt;When it&amp;#8217;s time to send out a response, &lt;code&gt;Qeweney::Request&lt;/code&gt; provides the following
methods:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;#respond(body, headers)&lt;/code&gt; - send out a complete response&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;#send_headers(headers)&lt;/code&gt; - send out headers, without finishing the response&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;#send_chunk(chunk, done: false)&lt;/code&gt; - send a body chunk, possibly finishing the
response&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;#finish()&lt;/code&gt; - finish the response (when not using &lt;code&gt;respond&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These APIs allow the app to either send the body all at one, or as shown in the
&lt;code&gt;upper_caser&lt;/code&gt; example above, chunk by chunk. Note that Tipi&amp;#8217;s HTTP/1 adapter
will use &lt;a href=&quot;https://en.wikipedia.org/wiki/Chunked_transfer_encoding&quot;&gt;chunked
encoding&lt;/a&gt; by default,
which lets apps send out streaming responses easily. By being able to send a
response chunk by chunk, apps are able to generate large responses without
having to buffer them in memory, thus lowering pressure on the Ruby GC, and the
app&amp;#8217;s memory footprint in general.&lt;/p&gt;

&lt;p&gt;In a similar manner to the way Qeweney provides additional methods for
processing request headers, it also provides response methods (defined in the
&lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/main/lib/qeweney/response.rb&quot;&gt;&lt;code&gt;Qeweney::ResponseMethods&lt;/code&gt;
module&lt;/a&gt;)
for generating responses more easily. Here are some of them:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# redirect to another URL&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alternative_url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# redirect to HTTPS&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;redirect_to_https&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# serve a static file&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;serve_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# serve from an IO&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;serve_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# serve from a Rack app&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;serve_rack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Upgrade to arbitrary protocol&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now these methods may seem almost banal (and they mostly are,) but &lt;code&gt;#serve_file&lt;/code&gt;
method merits closer scrutiny, as it actually packs a lot of power behind its
modest method signature. It sets cache headers, validates &lt;code&gt;If-None-Match&lt;/code&gt; and
&lt;code&gt;If-Modified-Since&lt;/code&gt; headers included in the request, and responds with a &lt;code&gt;304
Not Modified&lt;/code&gt; status code if the client&amp;#8217;s cache is valid. It also serves the
file compressed according to the &lt;code&gt;Accept-Encoding&lt;/code&gt; request header, using either
the &lt;code&gt;deflate&lt;/code&gt; or &lt;code&gt;gzip&lt;/code&gt; algorithms.&lt;/p&gt;

&lt;p&gt;Tipi further turbocharges the functionality provided by Qeweney using optimized,
protocol-specific methods for serving arbitrary responses from an IO object
(&lt;code&gt;Request#serve_file&lt;/code&gt; uses &lt;code&gt;#serve_io&lt;/code&gt; under the hood). Tipi&amp;#8217;s HTTP/1 adapter is
capable of sending requests from an IO by
&lt;a href=&quot;https://noteflakes.com/articles/2021-06-25-polyphony-june-edition#a-new-api-for-splicing-tofrom-pipes&quot;&gt;splicing&lt;/a&gt;
them to the client&amp;#8217;s socket connection, achieving up to 64% better throughput
for large files.&lt;/p&gt;

&lt;h2 id=&quot;request-routing&quot;&gt;Request routing&lt;/h2&gt;

&lt;p&gt;Qeweney also includes a basic &lt;a href=&quot;https://github.com/digital-fabric/qeweney/blob/main/lib/qeweney/routing.rb&quot;&gt;API for routing
requests&lt;/a&gt;
without needing a web framework, or rolling your own. The Qeweney routing is
largely inspired by &lt;a href=&quot;http://roda.jeremyevans.net/&quot;&gt;Roda&lt;/a&gt;, and in fact uses the
same mechanism (using &lt;code&gt;catch/throw&lt;/code&gt;) behind the scenes. Here&amp;#8217;s a simple
demonstration of what Qeweney&amp;#8217;s router is capable of:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;route&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_root&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;redirect&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;/hello&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;hello&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;world&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello world&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_get&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_post&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;redirect&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;/&#39;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;:status&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;404&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Qeweney router is by no way comprehensive (it probably doesn&amp;#8217;t have half the
features of Roda,) nor does it have the best routing DSL, at least as far as I&amp;#8217;m
concerned, but it offers a solid starting point for implementing web apps on top
of Qeweney, without needing to use any web framework.&lt;/p&gt;

&lt;h2 id=&quot;extending-qeweney&quot;&gt;Extending Qeweney&lt;/h2&gt;

&lt;p&gt;Qeweney&amp;#8217;s API largely consists of a single class, &lt;code&gt;Qeweney::Request&lt;/code&gt;, which
deals with all aspects of processing HTTP requests and responses, from the point
of view of the server. The different functionalities—request information,
responding, and routing—are implemented in separate modules that are then
&lt;code&gt;include&lt;/code&gt;d into the &lt;code&gt;Request&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;This design choice means that you don&amp;#8217;t need to deal with different objects and
different classes, everything related to requests and responses available
directly on the &lt;code&gt;Request&lt;/code&gt; instance given to your web app. The downside is that
the &lt;code&gt;Request&lt;/code&gt; class has a big surface area with dozens of methods, and will most
probably not satisfy OOP purists who demand no more than 10 methods per class,
and a single area of responsibility for each class. YMMV.&lt;/p&gt;

&lt;p&gt;The point I want to make here, is that to me, adding request/response
functionality in this manner, by extending the &lt;code&gt;Request&lt;/code&gt; class, makes a lot more
sense, especially when you want to add middleware to your app, which brings us
to:&lt;/p&gt;

&lt;h2 id=&quot;qeweney-and-middleware&quot;&gt;Qeweney and Middleware&lt;/h2&gt;

&lt;p&gt;One of the defining features of Rack is the use of middleware - small pieces of
specific HTTP functionality that you can plug into your application, without
making any change to your app&amp;#8217;s logic. This is due to Rack being a functional
interface (you feed a request in, and you get a response out.) All you need to
do to plug a middleware into your app is to call the middleware instead of your
app, and have the middleware call your app before or after it has done its
business. That way, a middleware can manipulate both the &lt;code&gt;env&lt;/code&gt; parameter passed
to your app, as well as the response your app has generated, before it is being
returned to the app server.&lt;/p&gt;

&lt;p&gt;In fact, you can put a whole bunch of middleware components in a pipe line, with
each of them performing specific tasks before and after your app has dealt with
the request.&lt;/p&gt;

&lt;p&gt;As we saw above, Qeweney does not use a functional approach, but rather an
imperative one. Once the app has sent its response (by calling methods on the
given request,) there&amp;#8217;s no way to change it &lt;em&gt;post factum&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So how can we use middleware under such circumstances? We do not necessarily
want to monkey-patch &lt;code&gt;Qeweney::Request&lt;/code&gt; in order to provide specific
functionality, such as logging, validating request headers or setting various
response headers. In fact, it would be undesirable to change the behaviour of
the class, when all we need is to apply custom behaviour for specific instances
of the class. For example, we might want to apply &lt;a href=&quot;https://en.wikipedia.org/wiki/Cross-site_request_forgery&quot;&gt;CSRF
protection&lt;/a&gt; only to
some requests but not to others. We might want to measure response latency for
some requests but not others.&lt;/p&gt;

&lt;p&gt;Luckily, Ruby lets us do just that by letting us &lt;code&gt;extend&lt;/code&gt; object instances with
methods from arbitray modules. Let&amp;#8217;s see how this technique can be applied to
creating middleware in Qeweney:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;JSONContentTypeExtensions&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# overrides Qeweney::Request#respond&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;with_json_content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JSONContentTypeExtensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello world!&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# plug middleware in front of app&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with_json_content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above example shows how a module modifying the stock behaviour of
&lt;code&gt;Qeweney::Request#respond&lt;/code&gt; can be used to extend a specific &lt;em&gt;instance&lt;/em&gt; of the
class. Note how we call &lt;code&gt;super&lt;/code&gt; in the patched method. The
&lt;code&gt;with_json_content_type&lt;/code&gt; builds the middleware by taking an app and returning a
modified &lt;code&gt;Proc&lt;/code&gt; that first extends the request with the custom behaviour, then
passes it on to the app.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s another example that shows how we can implement a logging middleware:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;with_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;define_method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;elapsed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Got &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;, respond with &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elapsed&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;s)&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example is quite dense, so let&amp;#8217;s analyze what it does. Since we want to be
able to inject a &lt;code&gt;log&lt;/code&gt; instance into the middleware, we need to dynamically
override the &lt;code&gt;#repsond&lt;/code&gt; method with a closure, in order to be able to access
&lt;code&gt;log&lt;/code&gt; from within the method. Once that has been done, &lt;code&gt;with_logger&lt;/code&gt; returns a
&lt;code&gt;Proc&lt;/code&gt; that first extends the request with the dynamically defined module, then
calls the app.&lt;/p&gt;

&lt;p&gt;Finally, in order to make it easier to write middleware for Qeweney, it will
eventually include a simple DSL for writing middleware. It will probably look
something like the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;with_logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;middleware&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;respond&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;elapsed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Responded with &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;elapsed&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;s)&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;with_json_content_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;middleware&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;respond&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;message&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hello world!&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# plug middleware&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;with_logger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;with_json_content_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-about-rack&quot;&gt;What about Rack?&lt;/h2&gt;

&lt;p&gt;Last, but not least, I&amp;#8217;d like to discuss how Qeweney fits in with a world where
practically all Ruby web apps are based on Rack. Qeweney can be used both as a
driver for Rack apps (e.g. if you want to run your Rack app on Tipi,) and to run
Qeweney apps on top of Rack servers such as
&lt;a href=&quot;https://github.com/puma/puma/&quot;&gt;Puma&lt;/a&gt; or
&lt;a href=&quot;https://github.com/socketry/falcon&quot;&gt;Falcon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to run your Rack app using Tipi, you don&amp;#8217;t need to do anything. Just
provide run Tipi with the path of your rack app, e.g.: &lt;code&gt;tipi myapp.ru&lt;/code&gt;, and Tipi
will take care of doing the
&lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/lib/tipi/rack_adapter.rb&quot;&gt;translation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you prefer to develop your next web app using the Qeweney interface, it&amp;#8217;s
easy to convert it to a Rack app. Here&amp;#8217;s how:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;qeweney&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;my_qeweney_app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;Hello world!&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Qeweney&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my_qeweney_app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;This has been an introduction to
&lt;a href=&quot;https://github.com/digital-fabric/qeweney&quot;&gt;Qeweney&lt;/a&gt;, a new HTTP request/reponse
interface for Ruby web apps and servers. Its design was driven by the need for
concurrency, performance, and streaming capabilities that are currrently lacking
in Rack. While most developers using
&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt; and the suite of tools &lt;a href=&quot;https://github.com/digital-fabric&quot;&gt;I&amp;#8217;m
currently developing&lt;/a&gt;, will not interact
directly with Qeweney, I wanted to give you an overview of its API and show some
of its capabilities. If this work interests you, please let me know by
&lt;a href=&quot;https://noteflakes.com/about#contact&quot;&gt;contacting me&lt;/a&gt;. We can also hook up on
&lt;a href=&quot;https://github.com/noteflakes&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>Signal handling in concurrent apps with Ruby and Polyphony</title><link>https://noteflakes.com/articles/2021-11-23-signal-handling</link><guid>https://noteflakes.com/articles/2021-11-23-signal-handling</guid><pubDate>Tue, 23 Nov 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;In the last few weeks I&amp;#8217;ve been writing about different aspects of
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;, a library for writing
fiber-based concurrent apps in Ruby. Polyphony makes it easy for developers to
use stock Ruby core and stdlib classes and APIs in a highly-concurrent
environment in order to create scalable, high-performance apps.&lt;/p&gt;

&lt;p&gt;In order for provide a solid developer experience, Polyphony reimplements
different parts of the Ruby runtime functionality, which are adjusted so
developers will see a consistent and reliable behaviour. In this article I&amp;#8217;ll
discuss how Polyphony implements signal handling. For the sake of brevity, I&amp;#8217;ll
assume the reader is familiar with POSIX signals and has some knowledge of how
signals are handled in Ruby.&lt;/p&gt;

&lt;h2 id=&quot;what-happens-when-a-signal-is-trapped&quot;&gt;What happens when a signal is trapped&lt;/h2&gt;

&lt;p&gt;In order to get a clear picture of how signal traps work, here&amp;#8217;s the relevant
passage from the Linux &lt;code&gt;sigreturn&lt;/code&gt; &lt;a href=&quot;https://www.man7.org/linux/man-pages/man2/sigreturn.2.html&quot;&gt;manpage&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If the Linux kernel determines that an unblocked signal is pending for a
process, then, at the next transition back to user mode in that process (e.g.,
upon return from a system call or when the process is rescheduled onto the
CPU), it creates a new frame on the user-space stack where it saves various
pieces of process context (processor status word, registers, signal mask, and
signal stack settings).&lt;/p&gt;

  &lt;p&gt;The kernel also arranges that, during the transition back to user mode, the
signal handler is called, and that, upon return from the handler, control
passes to a piece of user-space code commonly called the &amp;#8220;signal trampoline&amp;#8221;.
The signal trampoline code in turn calls sigreturn().&lt;/p&gt;

  &lt;p&gt;&amp;#8230; Using the information that was earlier saved on the user-space stack
sigreturn() restores the process&amp;#8217;s signal mask, switches stacks, and restores
the process&amp;#8217;s context (processor flags and registers, including the stack
pointer and instruction pointer), so that the process resumes execution at the
point where it was interrupted by the signal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From the point of view of the developer, when a signal occurs, it&amp;#8217;s as if
control of your program has been momentarily hijacked and a special bit of
code－the signal handler block－is executed. Once the block has finished
running, control is returned to your program, which continues running normally
as if nothing happened, &lt;em&gt;unless&lt;/em&gt; the signal handler has raised an exception. In
that case, the exception will be raised by the Ruby runtime in the signal
trampoline.&lt;/p&gt;

&lt;p&gt;By default, Ruby traps the &lt;code&gt;INT&lt;/code&gt; and &lt;code&gt;TERM&lt;/code&gt; signals by raising &lt;code&gt;Interrupt&lt;/code&gt; and
&lt;code&gt;SystemExit&lt;/code&gt; exceptions, respectively. Other signals cause a &lt;code&gt;SignalException&lt;/code&gt;
to be raised. Normally, such exceptions will cause the program to exit, unless
they are rescued. The following block will gracefully handle pressing ctrl-C in
the terminal:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;going to sleep...&#39;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Interrupt&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;got Interrupt, waking up.&#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In addition there are some operations that are not allowed in Ruby signal
handlers, namely acquiring mutexes (which is needed for doing buffered I/O) and
joining threads. If you need to do that in your program, you&amp;#8217;ll need to
implement your own mechanisms for handling signals asynchronously, that is,
outside of the signal handler block. Sidekiq, for example, implements a
mechanism for handling signals asynchronously by &lt;a href=&quot;https://github.com/mperham/sidekiq/pull/761&quot;&gt;writing to a
pipe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, when your program switches constantly between multiple fibers, a signal
may occur in the context of any fiber, and if a signal exception is raised, it
might be raised in the context of any fiber. This might lead to a situation
where the exception terminates some worker fiber, and this may prevent the
graceful handling of the signal.&lt;/p&gt;

&lt;h2 id=&quot;making-signals-work-with-structured-concurrency&quot;&gt;Making signals work with structured concurrency&lt;/h2&gt;

&lt;p&gt;Polyphony&amp;#8217;s implementation of structured concurrency assures developers that any
exception occuring in any fiber will bubble up the fiber hierarchy if it is not
rescued locally. How can we make signal exceptions work in an multi-fiber
environment, and furthermore how can we make &lt;em&gt;any&lt;/em&gt; signal handler work when we
don&amp;#8217;t know in what fiber the signal will occur?&lt;/p&gt;

&lt;p&gt;Polyphony&amp;#8217;s answer to that is actually quite simple: it runs the signal handler
block on a new raw fiber (&amp;#8220;raw&amp;#8221; meaning it&amp;#8217;s not bound by Polyphony&amp;#8217;s rules of
structured concurrency.) This
&lt;a href=&quot;https://en.wikipedia.org/wiki/Out-of-band_data&quot;&gt;out-of-band&lt;/a&gt; fiber is
priority-scheduled by putting it at the head of the runqueue, causing it to be
run immediately once the currently running fiber has yielded control. Any
uncaught exception raised in the signal handler will be propagated to the main
fiber, which will also be priority-scheduled:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;FiberControlClassMethods&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;schedule_priority_oob_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Setup raw fiber&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;oob_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setup_raw&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Transfer uncaught exception to the main fiber by scheduling with the&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# exception as the resume value.&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule_and_wakeup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Thread#schedule_and_wakeup schedules the fiber at the head of the runqueue.&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule_and_wakeup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oob_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Thread#schedule_and_wakeup&lt;/code&gt; method schedules the given fiber with priority:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Thread_fiber_schedule_and_wakeup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Qnil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Thread_schedule_fiber_with_priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Backend_wakeup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rb_ivar_get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ID_ivar_backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Qnil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Thread_switch_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Backend_wakeup&lt;/code&gt; will return true if the Polyphony backend is currently polling
for completions (or for events if using libev instead of io_uring). If that was
the case, there&amp;#8217;s no need to take any further action, the signal handling fiber
will be switched to as soon as the polling is done (that is, right after the
signal trap has returned). If not, we call &lt;code&gt;Thread_switch_fiber&lt;/code&gt; which will
immediately switch to the signal handling fiber.&lt;/p&gt;

&lt;p&gt;In this manner, signal handling becomes asynchronous, but any received signal is
handled as soon as possible, without interfering with the work of any fiber in
your Ruby process. As a consequence, there&amp;#8217;s no more limits on what you can do
in the signal handler block provided to &lt;code&gt;Kernel#trap&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, in order to transparently handle the setting of signal traps, Polyphony
&lt;a href=&quot;/articles/2021-11-04-monkey-patching&quot;&gt;monkey-patches&lt;/a&gt; &lt;code&gt;Kernel#trap&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Kernel&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The actual code for this method is a bit more involved&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;trap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;orig_trap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule_priority_oob_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;signal-handling-patterns&quot;&gt;Signal handling patterns&lt;/h2&gt;

&lt;p&gt;There are some useful patterns you can employ when dealing with signals. In
general, Polyphony shies away from callbacks. Almost the entire Polyphony API is
exclusively synchronous and blocking. But when installing a signal trap, you
actually provide a callback that will be called asynchronously sometime in the
future. With Polyphony, it&amp;#8217;s easy to turn this into a blocking API.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;this_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;trap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;suspend&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# We do work in a separate fiber&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_some_work&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# On the main fiber we wait for a signal&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;await_signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;INT&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Got INT, quitting.&#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the signal handling logic is something that can happen multiple times, we can
use &lt;a href=&quot;/articles/2021-11-13-real-world-polyphony-chat#fiber-messaging&quot;&gt;fiber
messaging&lt;/a&gt; to
serialize the receipt of signals:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_some_work&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;signal_watcher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin_loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;USR1&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restart&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;TERM&#39;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;INT&#39;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;exit!&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;sx&quot;&gt;%w{USR1 TERM INT}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;trap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;signal_watcher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sig&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this article we have explored how POSIX signals can be handled in a safe and
consistent manner in a fiber-based concurrent environment such as
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;. Other interesting
aspects of the behaviour of multi-fiber Ruby programs, such as forking and
exception handling, will be addressed in future articles. Please feel free to
&lt;a href=&quot;https://noteflakes.com/about#contact&quot;&gt;contact me&lt;/a&gt; if you have any questions
about this article or Polyphony in general.&lt;/p&gt;
</description></item><item><title>Real-world Concurrency with Ruby and Polyphony: a Telnet Chat App</title><link>https://noteflakes.com/articles/2021-11-13-real-world-polyphony-chat</link><guid>https://noteflakes.com/articles/2021-11-13-real-world-polyphony-chat</guid><pubDate>Sat, 13 Nov 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;Recently there has been a lot of renewed interest in
&lt;a href=&quot;https://rubyapi.org/3.0/o/fiber&quot;&gt;fibers&lt;/a&gt; as the building blocks for writing
concurrent Ruby apps. Most of the articles written lately (my own included) have
tried to explain what are fibers, how they can be used for writing concurrent
apps, and how fiber scheduling works. While this obviously is great, I feel
there&amp;#8217;s also a need for developers to get a feel for how a real-world
fiber-based app looks, and how writing such an app differs from using, say,
&lt;a href=&quot;https://github.com/eventmachine/eventmachine/&quot;&gt;EventMachine&lt;/a&gt; or some other Ruby
library providing a different concurrency model.&lt;/p&gt;

&lt;p&gt;In this article I&amp;#8217;ll walk you through implementing a bare-bones Telnet chat app
using &lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;. Along the way,
I&amp;#8217;ll demonstrate how Polyphony lets us write concurrent programs in a natural,
idiomatic style, and show how fiber messaging, one of Polyphony&amp;#8217;s unique
features, allows us to design a concurrent app as a collection of simple,
autonomous entities, each having a single responsibility.&lt;/p&gt;

&lt;p&gt;The source code for the chat app is available as a
&lt;a href=&quot;https://gist.github.com/noteflakes/9e2ffbc39d75a8b7433ea6df7c5ddea8&quot;&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;designing-our-chat-app&quot;&gt;Designing our chat app&lt;/h2&gt;

&lt;p&gt;The chat app we&amp;#8217;re going to implement today will have the following
requirements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Telnet-based (that is, using plain text over TCP sockets.)&lt;/li&gt;
  &lt;li&gt;Support any number of concurrent users.&lt;/li&gt;
  &lt;li&gt;Support any number of rooms.&lt;/li&gt;
  &lt;li&gt;Each user can be in a single room at a time.&lt;/li&gt;
  &lt;li&gt;Rooms are ephemeral, so no need to keep a history of messages.&lt;/li&gt;
  &lt;li&gt;Users are also ephemeral, no need to keep a user&amp;#8217;s state.&lt;/li&gt;
  &lt;li&gt;User actions are: join a room, leave a room, send a message to a room.&lt;/li&gt;
  &lt;li&gt;When a user sends a message to a room, all users in the same room get it.&lt;/li&gt;
  &lt;li&gt;A user joins or leaves a room by sending &lt;code&gt;:enter &amp;lt;room&amp;gt;&lt;/code&gt; or &lt;code&gt;:leave&lt;/code&gt;,
respectively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have our requirements, let&amp;#8217;s concentrate on the design of our
program: what are the different moving parts and how do they connect? One of the
biggest advantages of using fibers is that, fibers being so cheap to create (in
terms of computer resources,) we can implement any entity in our program as a
fiber. If we take the problem of a chat app, we have rooms, we have users, and
we have TCP connections. As I&amp;#8217;ll show below, each of these can be modeled as an
independent fiber.&lt;/p&gt;

&lt;h2 id=&quot;fiber-messaging&quot;&gt;Fiber messaging&lt;/h2&gt;

&lt;p&gt;In order for all those different fibers to communicate with each other, we can
use fiber messaging, a feature that is unique to Polyphony, and is greatly
inspired by &lt;a href=&quot;https://www.erlang.org/doc/getting_started/conc_prog.html#message-passing&quot;&gt;message-passing in
Erlang&lt;/a&gt;, which essentially permits
Erlang processes to behave as concurrent
&lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;actors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In Polyphony, each fiber has a mailbox, and can receive messages by calling
&lt;code&gt;Kernel#receive&lt;/code&gt;. A message can be any Ruby object. To send a message to a
fiber, we call &lt;code&gt;Fiber#send&lt;/code&gt; or &lt;code&gt;Fiber#&amp;lt;&amp;lt;&lt;/code&gt;. Receiving a message is a blocking
operation, if the fiber&amp;#8217;s mailbox is empty, the call to &lt;code&gt;#receive&lt;/code&gt; will block
until a message is sent to the fiber. The call to &lt;code&gt;#send&lt;/code&gt;, however, is not
blocking (except if the fiber&amp;#8217;s mailbox is capped and filled to capacity. By
default fiber mailboxes are not capped.)&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a simple example to show how fiber messaging works:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;receiver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;hello&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Waiting for message...&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Got &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we spin up a fiber that will sleep for 1 second, then send
a message to the main fiber, which meanwhile waits for a message to be received.
This apparently simple mechanism for &lt;em&gt;asynchronous&lt;/em&gt; communication between fibers
has profound implications for how we can structure our concurrent programs.
Since fibers can behave as actors (just like Erlang processes,) they can
basically have the same capabilities as custom Ruby objects. Think about it:
when we call a method on a Ruby object, we basically send it a message. If
fibers can send and receive messages, we can use them instead of plain Ruby
objects. And just like custom Ruby objects which hold state (stored in instance
variables,) fibers can hold state in local variables.&lt;/p&gt;

&lt;p&gt;In order to see how a fiber can hold state and receive &amp;#8220;method calls&amp;#8221;, let&amp;#8217;s
take the simple example of a calculator with a memory. Our calculator can do
arythmetic operations on the last retained value. Here&amp;#8217;s how we&amp;#8217;ll implement
such a calculator using a normal Ruby class definition:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Calculator&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let&amp;#8217;s see how we can do the same thing with a fiber:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:add&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mul&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The calculator fiber loops infinitely, waiting for messages to be received in
its mailbox. Each message, having been destructured, is processed by updating
the state and sending the updated state to the peer fiber which originated the
message. Notice that in the fiber-based version, in order to get the result of
the arythmetic operation, we need to provide the &lt;code&gt;calculator&lt;/code&gt; fiber with the
current fiber, to which it will send the result of the operation. In effect, our
calculator fiber can be said to be a sort of server: it receives requests,
handles them, and sends back a reply.&lt;/p&gt;

&lt;p&gt;This might seem like a a much more complicated way of doing things, but in fact
look at the stuff we don&amp;#8217;t need to worry about: we don&amp;#8217;t need to define a custom
class, and our state is safely stored as a local variable and cannot be accessed
or tampered with from the outside. Finally, since our calculator fiber is doing
one thing at a time we are basically guaranteed to not have any race conditions
when making &amp;#8220;calls&amp;#8221; to our calculator. Compare this to the &amp;#8220;normal&amp;#8221;
implementation above, which will fail miserably once we try to call methods from
multiple threads at once.&lt;/p&gt;

&lt;p&gt;If we want to make the fiber&amp;#8217;s interface a bit more like what we&amp;#8217;re used to with
our normal Ruby method calls, we can wrap our calculator fiber implementation
with something akin to Erlang&amp;#8217;s
&lt;a href=&quot;https://www.erlang.org/doc/man/gen_server.html&quot;&gt;GenServer&lt;/a&gt; (generic server)
behavior, as &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/examples/core/erlang-style-genserver.rb&quot;&gt;shown in the Polyphony
repository&lt;/a&gt;.
Our fiber-based calculator would then look something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Calculator&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;module_function&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initial_state&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# The first value is the return value, the second is the mutated state. In&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# our case, they are the same.&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# start server with initial state&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;GenServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mul&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One important detail to understand about fiber messaging is that like with any
API, the actual messages sent and received between fibers (which, if you recall,
can be any Ruby object) need to be well defined. An abstraction such as the
&lt;code&gt;GenServer&lt;/code&gt; example shown above, can help with making those interfaces more
convenient to use, but it is in no way obligatory. We can get by explicitly
sending and receiving fiber messages.&lt;/p&gt;

&lt;p&gt;Using fibers to encapsulate state - and fiber messaging to communicate between
fibers - has an additional ramification: it guides the developer towards a more
functional style of programming (the example above is a case in point.) You stop
thinking in classes and objects, and think more in terms of methods and message
passing. While Ruby is pretty good at doing both, in the last few years I&amp;#8217;ve
been personally gravitating towards a more functional programming style, and
Polyphony does facilitate moving in that direction.&lt;/p&gt;

&lt;p&gt;But let&amp;#8217;s go back to our chat app. We&amp;#8217;d like to implement the different entities
in our program as fibers, and make them interact using fiber messaging. As noted
above, if we want to use fiber messaging, we&amp;#8217;ll need to have defined the
different messages that are going to be sent between the different fibers, in
other words the different interfaces those fibers will have. Before starting to
write our implementation, let&amp;#8217;s first define those.&lt;/p&gt;

&lt;h2 id=&quot;defining-fiber-interfaces&quot;&gt;Defining fiber interfaces&lt;/h2&gt;

&lt;p&gt;As we said, we have three kinds of entities: Telnet session, user, and room.
Let&amp;#8217;s figure out the responsibilities of each entity, and how those entities
interact:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A room has zero or more users in it.&lt;/li&gt;
  &lt;li&gt;A user can enter a room, and can leave a room.&lt;/li&gt;
  &lt;li&gt;Once a user enters a room, she&amp;#8217;ll receive any messages broadcast by other
users in the same room.&lt;/li&gt;
  &lt;li&gt;When a Telnet session starts (the client connection is accepted,) a user is
created.&lt;/li&gt;
  &lt;li&gt;When a Telnet session ends (the socket is closed,) the corresponding user
leaves the room she&amp;#8217;s currently in, and then terminates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let&amp;#8217;s now define the shape of the different messages our chat entities should be
able to handle. A &lt;em&gt;room&lt;/em&gt; fiber needs to be able to handle the following events:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A user entered the room: &lt;code&gt;[:enter, name, fiber]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;A user left the room: &lt;code&gt;[:leave, name, fiber]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;A user wrote something: &lt;code&gt;[:message, message]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;em&gt;user&lt;/em&gt; fiber should handle the following events:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A line was received from the corresponding socket: &lt;code&gt;[:input, message]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;A message was broadcast in the user&amp;#8217;s room: &lt;code&gt;[:message, message]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Telnet session fiber does not need to handle incoming messages, as its job
is only to wait for lines of text to arrive on the socket, and send them to the
corresponding user. The distinction between session and user is important, since
those two entities have different responsibilities. The user fiber implements
the business logic from the point of view of the user, dealing with
notifications coming either from the room or the Telnet session. The Telnet
session deals exclusively with receiving data on the corresponding TCP socket.&lt;/p&gt;

&lt;p&gt;Now that we have defined the interactions and messages sent between the
different parts of our app, let&amp;#8217;s start writing code!&lt;/p&gt;

&lt;h3 id=&quot;the-telnet-session&quot;&gt;The Telnet session&lt;/h3&gt;

&lt;p&gt;We start writing our code with a straightforward implementation of a TCP server:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;server_socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;0.0.0.0&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;server_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept_loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We start by spinning up a server fiber that will run the TCP server. The server
fiber creates a &lt;code&gt;TCPServer&lt;/code&gt; instance for accepting connections. The
&lt;code&gt;#accept_loop&lt;/code&gt; method runs an infinite loop, waiting for connections to be
accepted. For each accepted connection, we spin a separate fiber, calling
&lt;code&gt;#handle_session&lt;/code&gt; with the accepted connection. Let&amp;#8217;s look at how
&lt;code&gt;#handle_session&lt;/code&gt; is implemented:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Please enter your name: &#39;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chomp&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;!&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gets&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chomp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We start by asking the user for their name, then setup a fiber for the user,
calling &lt;code&gt;#run_user&lt;/code&gt;. Finally, we run a loop waiting for lines to arrive on our
socket, and send each line to the user fiber.&lt;/p&gt;

&lt;h2 id=&quot;the-user-fiber&quot;&gt;The user fiber&lt;/h2&gt;

&lt;p&gt;Our user fiber will run a loop, waiting for and processing incoming messages:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:close&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:input&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/\:enter\s+(.+)/&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;leave_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enter_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;:leave&#39;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;leave_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;leave_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_room&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We destructure incoming messages (received as an Array of the form &lt;code&gt;[event,
message]&lt;/code&gt;), then take the correct action according to the message received. Here
are the rest of the user&amp;#8217;s business logic, which consist of sending messages to
the room the user has entered or left:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;leave_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:leave&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;enter_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;find_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:enter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;room_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:say&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-room&quot;&gt;The room&lt;/h2&gt;

&lt;p&gt;Finally, we get to the room entity, which manages a list of users and takes care
of broadcasting messages received from individual users in the room. Let&amp;#8217;s start
with the &lt;code&gt;#find_room&lt;/code&gt; method, which is used by users to find the fiber for the
room they want to enter:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@room_fibers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@main_fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;find_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@room_fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@main_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since &lt;code&gt;#find_room&lt;/code&gt; is called in the context of the user fiber, we need to be
careful about how we spin up the room fiber. We want our room fiber to not be
limited to the lifetime of the user fiber (which will terminate when the user&amp;#8217;s
Telnet session closes,) and that means we cannot spin it directly from the user
fiber. Instead, we spin it from the main fiber. Notice that the user fiber
itself is spun from the Telnet session fiber, but since the user fiber should
not outlive its Telnet session that is just fine.&lt;/p&gt;

&lt;p&gt;(In a future article I&amp;#8217;ll show a better way to manage fibers by organizing them
into supervision trees, but for the sake of the present discussion the above
solution is good enough).&lt;/p&gt;

&lt;p&gt;Lets continue with the room implementation:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run_room&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:leave&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;broadcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; has left the room.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:enter&lt;/span&gt;
      &lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;broadcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; has entered the room.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:say&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;broadcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@room_fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;broadcast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fibers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The room fiber is very similar to the user fiber, in that it runs a loop waiting
for events to be received. The different events are processed by updating the
list of users and broadcasting the corresponding messages to all users.&lt;/p&gt;

&lt;h2 id=&quot;tying-it-all-together&quot;&gt;Tying it all together&lt;/h2&gt;

&lt;p&gt;Now that we have implemented the different parts of the application, all that&amp;#8217;s
left is for the main fiber to wait for the server fiber to terminate (which will
never arrive). We do that by calling &lt;code&gt;Fiber#await&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now that our program is complete, let&amp;#8217;s run it (we can run two separate Telnet
sessions from separate terminal windows):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sharon@nf1:~$ Telnet localhost 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is &#39;^]&#39;.
Please enter your name: sharon
Hello, sharon!
:enter foo
sharon has entered the room.
hi
sharon: hi
sylvain has entered the room.
sylvain: hi there!
hello
sharon: hello
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We now have a fully functioning bare-bones chat app able to handle hundreds or
even thousands of concurrent users, implemented in about 85 lines of code, and
including a total of 8 methods: 1 for Telnet sessions, 4 for users, 3 for rooms.
Our code is compact, easy to understand, and does not include any class
definitions.&lt;/p&gt;

&lt;p&gt;Furthermore, any state we need to keep track of (the current room for the user,
and the list of users for each room) is conveniently held as local variables
inside the relevant methods. As discussed above, we could have encapsulated our
different entities (namely, users and rooms) as &lt;code&gt;GenServer&lt;/code&gt; interfaces, but I&amp;#8217;ll
leave that as an exercise to the reader.&lt;/p&gt;

&lt;p&gt;Also, note how fluid and idiomatic our code looks. Spinning up fibers takes no
effort, and neither does fiber messaging. We just sprinkle our code with a bunch
of &lt;code&gt;spin&lt;/code&gt; &lt;code&gt;receive&lt;/code&gt; and &lt;code&gt;fiber &amp;lt;&amp;lt; message&lt;/code&gt; and everything works concurrently.&lt;/p&gt;

&lt;p&gt;There&amp;#8217;s a lot to be said for designing concurrent programs as collections of
autonomous actors, interacting using messages. Programming in this way requires
a shift in how we think about the different entities in our program, and in how
we get them to interact. I&amp;#8217;ll continue exploring this subject in more detail in
future articles.&lt;/p&gt;

&lt;p&gt;You can find the complete code to the chat app
&lt;a href=&quot;https://gist.github.com/noteflakes/9e2ffbc39d75a8b7433ea6df7c5ddea8&quot;&gt;here&lt;/a&gt;. Please
feel free to &lt;a href=&quot;https://noteflakes.com/about#contact&quot;&gt;contact me&lt;/a&gt; if you have any
questions about this article or Polyphony in general.&lt;/p&gt;
</description></item><item><title>About that monkey-patching business...</title><link>https://noteflakes.com/articles/2021-11-04-monkey-patching</link><guid>https://noteflakes.com/articles/2021-11-04-monkey-patching</guid><pubDate>Thu, 04 Nov 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;A few days ago, a
&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/qj4s94/async_ruby/hio5k1o/?utm_source=share&amp;amp;utm_medium=web2x&amp;amp;context=3&quot;&gt;comment&lt;/a&gt;
was made on the internet about
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;, an open source project
of mine, mentioning the fact that Polyphony patches some core Ruby APIs. The
context was the difference between Polyphony and
&lt;a href=&quot;https://github.com/socketry/async&quot;&gt;Async&lt;/a&gt; (another fiber-based concurrency gem
for Ruby):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Last time I checked, polyphony monkey patched Ruby core methods to do its work
:-/. This has deterred from learning more about polyphony&amp;#8230; On the other
hand, &lt;code&gt;async&lt;/code&gt; gem has always had incredibly clean code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I&amp;#8217;m sure Bruno Sutic, the author of the above comment, was writing in good
faith, but his comment implies that Polyphony&amp;#8217;s code is &amp;#8220;dirty&amp;#8221;. It also implies
that monkey-patching is somehow illegitimate. While normally I don&amp;#8217;t get too
excited about people calling my code names, I do take pride in my work, and I
feel a rebuttal is in order.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;med&quot; src=&quot;https://imgs.xkcd.com/comics/duty_calls.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Moreover, the mere fact that Polyphony employs a (somewhat) controversial
technique to do its thing should not &lt;em&gt;deter&lt;/em&gt; people like Bruno from examining
it. I&amp;#8217;m sure all of us would benefit from approaching other people&amp;#8217;s code with
an open and inquisitive mind.&lt;/p&gt;

&lt;p&gt;In this article I&amp;#8217;ll explain in detail the strategy I chose for developing
Polyphony and the role monkey-patching plays within it. I&amp;#8217;ll also discuss the
potential problems raised by the practice of monkey-patching, and how they can
be minimized.&lt;/p&gt;

&lt;h2 id=&quot;what-is-monkey-patching&quot;&gt;What is monkey-patching?&lt;/h2&gt;

&lt;p&gt;But first, for those that are confused about what monkey-patching actually is,
here&amp;#8217;s the &lt;a href=&quot;https://en.wikipedia.org/wiki/Monkey_patch&quot;&gt;Wikipedia entry&lt;/a&gt; (go
ahead, read it!) For the sake of the present discussion, I&amp;#8217;ll define
monkey-patching as the practice of changing or extending the behavior of
pre-existing Ruby classes or modules by overriding their instance or class
methods. This can be done in a variety of ways, depending on what you want to
achieve. The most obvious way would be to open the class, then &lt;em&gt;redefine&lt;/em&gt; some
methods:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;s1&quot;&gt;&#39;123&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also put your patched methods in a separate module, then
&lt;a href=&quot;https://rubyapi.org/3.0/o/module#method-i-prepend&quot;&gt;&lt;code&gt;prepend&lt;/code&gt;&lt;/a&gt; it to the target
class (that way it will take precedence over existing method definitions):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;StringPatches&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prepend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StringPatches&lt;/span&gt;

&lt;span class=&quot;s1&quot;&gt;&#39;123&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you need to target specific object instances, you can patch their singleton
class:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;123&#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also limit the monkey-patching to a single file, class, or module, by
using
&lt;a href=&quot;https://docs.ruby-lang.org/en/3.0.0/doc/syntax/refinements_rdoc.html&quot;&gt;refinements&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;StringPatches&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;refine&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt;
      &lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StringPatches&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# activate refinement&lt;/span&gt;

&lt;span class=&quot;s1&quot;&gt;&#39;123&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So monkey-patching can be done in a variety of ways in Ruby, depending on how
specific you want the patched behaviour to be: from the level of a single object
instance, through specific scopes, all the way to patching classes globally.&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s also worth noting that there are other techniques that could be used
instead of monkey-patching: subclassing is ubiquitous in Ruby, and can even work
for extending core Ruby classes. Rails&amp;#8217;s
&lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html&quot;&gt;&lt;code&gt;HashWithIndifferentAccess&lt;/code&gt;&lt;/a&gt;
is a case in point. I could probably come up with a bunch of other alternatives,
but I&amp;#8217;ll leave it at that. The point is, it really depends on the circumstances.&lt;/p&gt;

&lt;h2 id=&quot;is-monkey-patching-inherently-bad&quot;&gt;Is monkey-patching inherently bad?&lt;/h2&gt;

&lt;p&gt;I&amp;#8217;m sure many people have written before about monkey-patching and whether it&amp;#8217;s
good or bad for you, but in my most humble opinion, there&amp;#8217;s no right or wrong
when it comes to programming. Monkey-patching is just a technique that has its
place like everything else under the heavens.&lt;/p&gt;

&lt;p&gt;Of course, monkey-patching can lead to problems - it can cause compatibility
issues and strange bugs, for example when your monkey-patching gem is combined
with other gems. It can break behaviour across different versions of Ruby, or in
conjunction with specific versions of specific dependencies. It can cause all
kinds of havoc. But it can also provide a very elegant solution in specific
circumstances, and can be amazingly effective.&lt;/p&gt;

&lt;h2 id=&quot;when-is-monkey-patching-useful&quot;&gt;When is monkey-patching useful?&lt;/h2&gt;

&lt;p&gt;Monkey-patching is useful when you need to alter or extend the way pre-existing
classes behave. Ruby&amp;#8217;s open nature lets you change almost everything about Ruby,
even core classes such as &lt;code&gt;Array&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt; can be modified (as shown in the
above examples.) Why would we want to do this? Here are some cases where
monkey-patching can be useful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ensuring compatibility between different versions of Ruby. This is especially
useful when you need to backport some new method introduced in a later version
of Ruby to an earlier version of Ruby. This is commonly called &amp;#8220;polyfill&amp;#8221;
(there&amp;#8217;s a whole bunch of them on
&lt;a href=&quot;https://rubygems.org/search?query=polyfill&quot;&gt;rubygems.org&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;&amp;#8220;Fixing&amp;#8221; some gem to work with your code. Suppose you have encountered a bug
in some gem your project depends on. In some cases, despite everybody&amp;#8217;s best
intentions, fixes to those problems can sometimes take months find their way
into a new version. In those cases, a monkey-patch can solve the problem
immediately, even if only temporarily, the new version containing the fix is
put out by the gem&amp;#8217;s author.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Debugging an application&amp;#8217;s behaviour by overriding methods and adding tracing,
for example:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;socket&#39;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TCPServer&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;alias_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:orig_initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:initialize&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Connecting to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;orig_initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Connected to &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Otherwise extending or replacing behaviours provided by the Ruby core or
stdlib, or by Ruby gems. For example, the &lt;a href=&quot;http://www.ohler.com/oj/&quot;&gt;&lt;code&gt;oj&lt;/code&gt;&lt;/a&gt;
gem, which provides fast JSON processing, has a &lt;a href=&quot;http://www.ohler.com/oj/doc/file.JsonGem.html&quot;&gt;compatibility
mode&lt;/a&gt; that effectively patches
the &lt;code&gt;json&lt;/code&gt; gem to provided as part of Ruby&amp;#8217;s stdlib. This feature lets Ruby
apps take advantage of faster JSON processing without any change to their
code.&lt;/p&gt;

    &lt;p&gt;It&amp;#8217;s important to note that the advantage monkey-patching provides over other
techniques, such as subclassing, is that those patches are in fact going to
impact all the other dependencies of your app. In the case of the &lt;code&gt;oj&lt;/code&gt; gem,
any other dependencies that make use of the &lt;code&gt;JSON&lt;/code&gt; API are also going to show
improved performance!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;designing-polyphony&quot;&gt;Designing Polyphony&lt;/h2&gt;

&lt;p&gt;When I first started working on Polyphony, I didn&amp;#8217;t know where it would take me.
Polyphony began as an experiment in designing an API for writing concurrent Ruby
programs. My starting point was the &lt;a href=&quot;https://github.com/socketry/nio4r&quot;&gt;nio4r&lt;/a&gt;
gem, which implements an event loop based on
&lt;a href=&quot;http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod&quot;&gt;libev&lt;/a&gt;. I really liked
what nio4r was able to do, and wanted to experiment with different concurrency
models, so I took its C-extension code and start fiddling with it. I went
through a whole bunch of different designs: callbacks, promises, futures,
async/await, and finally fibers.&lt;/p&gt;

&lt;p&gt;As Polyphony slowly took form, the following principles manifested themselves:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Polyphony should extend the Ruby runtime and feel like an integral part of it.&lt;/li&gt;
  &lt;li&gt;Polyphony&amp;#8217;s API should allow expressing concurrent operations in a concise
manner, with a minimum of abstractions or boilerplate.&lt;/li&gt;
  &lt;li&gt;Polyphony should allow developers to continue working with core and stdlib
classes and APIs such as &lt;code&gt;IO&lt;/code&gt;, &lt;code&gt;Socket&lt;/code&gt; and &lt;code&gt;Net::HTTP&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to be able to apply the above principles to Polyphony&amp;#8217;s design, I
needed a way to make Ruby&amp;#8217;s core classes, especially those having to do with
I/O, usable under Polyphony&amp;#8217;s concurrency model. The only solution that would
have allowed me to do that was  monkey-patching all those classes, including the
&lt;code&gt;IO&lt;/code&gt; class, the different &lt;code&gt;Socket&lt;/code&gt; classes, even the &lt;code&gt;OpenSSL&lt;/code&gt; classes dealing
with I/O. Without monkey-patching, Polyphony as it currently is would have been
impossible to implement!&lt;/p&gt;

&lt;h2 id=&quot;polyphony-and-rubys-new-fiber-scheduler-interface&quot;&gt;Polyphony and Ruby&amp;#8217;s new fiber scheduler interface&lt;/h2&gt;

&lt;p&gt;At this point people might ask: what about using the new
&lt;a href=&quot;https://rubyapi.org/3.0/o/fiber/schedulerinterface&quot;&gt;&lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt;&lt;/a&gt;
introduced in Ruby 3.0?  Presumably, with the &lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt; I
would be able to keep the same design based on the same principles, without
resorting to monkey-patching Ruby core classes. That&amp;#8217;s because the fiber
scheduler is baked right into Ruby&amp;#8217;s core.&lt;/p&gt;

&lt;p&gt;I have long thought about this problem, and have always come to the same
conclusion: if I were to base Polyphony on the &lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt;, it
would have limited what Polyphony could do. In fact, some of the features
Polyphony currently offers would have been impossible to achieve:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;I want Polyphony to work on older versions of Ruby. In fact, one of my
original constraints for developing Polyphony was to have it work on Ruby &amp;gt;=
2.6 (I started work in Polyphony in August 2018.)&lt;/li&gt;
  &lt;li&gt;Polyphony&amp;#8217;s design is highly-integrated - from the io_uring- or libev-based
backend through the fiber-scheduling code, all the way to the developer-facing
APIs for spinning up fibers and controlling them. Polyphony&amp;#8217;s io_uring backend
in particular offers unique capabilities, such as chaining of I/O operations,
which might have been much more difficult to achieve had it been based on the
fiber scheduler interface.&lt;/li&gt;
  &lt;li&gt;The &lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt; itself is still in a state of flux, and is
still missing hooks for socket operations (according to &lt;a href=&quot;https://github.com/ioquatix&quot;&gt;Samuel
Williams&lt;/a&gt;, the developer behind the fiber
scheduler interface, the &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; hooks are considered experimental
at the moment.)&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The &lt;code&gt;Fiber::SchedulerInterface&lt;/code&gt; will not magically bring fiber-awareness to
all Ruby gems, especially not those implemented as C-extensions. Take for
example the &lt;a href=&quot;https://github.com/ged/ruby-pg&quot;&gt;pg&lt;/a&gt; gem, which has recently added
&lt;a href=&quot;https://github.com/ged/ruby-pg/pull/397&quot;&gt;support for fiber schedulers&lt;/a&gt;.
Compare that with Polyphony&amp;#8217;s &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/lib/polyphony/adapters/postgres.rb&quot;&gt;monkey-patching
approach&lt;/a&gt;,
which is much more minimal. Another example is Polyphony&amp;#8217;s &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/lib/polyphony/adapters/redis.rb&quot;&gt;patch for
the &lt;code&gt;redis&lt;/code&gt; gem&lt;/a&gt;.&lt;/p&gt;

    &lt;p&gt;Even gems that do not rely on C-extensions might be problematic. Such is the
case with ActiveRecord, which does connection pooling &lt;em&gt;per thread&lt;/em&gt; and is thus
apparently incompatible with both Async and Polyphony. Here again, it seems to
me that monkey-patching might be the more effective solution, and perhaps also
simpler to implement, at least in the short term. That&amp;#8217;s how Polyphony
implements &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/lib/polyphony/adapters/sequel.rb&quot;&gt;fiber-aware connection
pooling&lt;/a&gt;
for Sequel (thanks &lt;a href=&quot;https://github.com/wjordan&quot;&gt;wjordan&lt;/a&gt;!)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating fiber scheduling into Ruby&amp;#8217;s core is not a trivial undertaking (and
I applaud Samuel for his resolve and determination in the face of substantial
pushback.) The problem is not only technological - making fiber scheduling work
with the complex code of Ruby&amp;#8217;s IO core - but also getting other Ruby core
developers and gem authors to understand the merits of this effort, and finally
to put out new fiber-aware versions of their code.&lt;/p&gt;

&lt;p&gt;As the fiber scheduler interface matures, I guess I will have to reconsider my
position regarding Polyphony. One interesting
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/issues/46&quot;&gt;suggestion&lt;/a&gt; was to
implement Polyphony as a fiber scheduler for Ruby &amp;gt;= 3.0, and as a &amp;#8220;polyfill&amp;#8221;
for earlier Ruby versions.&lt;/p&gt;

&lt;h2 id=&quot;what-about-compatibility&quot;&gt;What about compatibility?&lt;/h2&gt;

&lt;p&gt;Monkey-patching does introduce the problem of compatibility, and this &lt;em&gt;should&lt;/em&gt;
be taken seriously. Polyphony aims to reduce compatibility issues in two ways.
firstly, Polyphony aims to mimic the same behaviour as much as possible across
all monkey-patched APIs from the point of view of the application. Secondly,
Polyphony aims to monkey-patch mostly stable APIs that have little chance of
changing between versions.&lt;/p&gt;

&lt;p&gt;This approach is not without problems. For example, the changes to &lt;code&gt;irb&lt;/code&gt;
introduced in Ruby 2.7 have broken Polyphony&amp;#8217;s patch, and there&amp;#8217;s an
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/issues/5&quot;&gt;outstanding issue&lt;/a&gt; for it
(I&amp;#8217;ll get to it eventually.)&lt;/p&gt;

&lt;p&gt;Polyphony also provides, as described above, mokey-patches for third-party gems,
such as &lt;code&gt;pg&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt; and others. Those are are bundled as part of Polyphony,
but in the futre might be extracted to separate gems, in order to be able to
respond more quickly to local issues that arise in integrating those gems with
Polyphony.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;d also like to note that I do not expect people to just add Polypony to their
&lt;code&gt;Gemfile&lt;/code&gt; and start spinning up fibers all over the place. In fact, using
Polyphony is to me such a radical shift from previous approaches to Ruby
concurrency that I find it improbable that one day it will &lt;em&gt;simply work&lt;/em&gt; with
any Ruby on Rails app. Using Polyphony to its full potential will require much
more careful consideration on the part of developers using it.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;d also like to add that my goal is not for Polyphony to become &lt;em&gt;the&lt;/em&gt; solution
for fiber-based concurrency in Ruby. It&amp;#8217;s just a project that I find useful for
my own work and I feel could be useful for others as well. There&amp;#8217;s nothing wrong
with having multiple solutions to the same problem. On the contrary, I find it
beneficial and stimulating to have competing projects based on different
approaches.&lt;/p&gt;

&lt;h2 id=&quot;so-what-does-polyphony-patch&quot;&gt;So what does Polyphony patch?&lt;/h2&gt;

&lt;p&gt;Polyphony replaces whole parts of the Ruby core API with fiber-aware code that
provides the same functionality, but integrated with Polyphony&amp;#8217;s code. I took
great care to make method signatures are the same and behave identically as much
as possible.&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s worth noting that running Ruby programs with multiple fibers
present challenges that go beyond merely reading and writing to &lt;code&gt;IO&lt;/code&gt; instances:
there&amp;#8217;s all kinds of subtleties around forking, signal handling, waiting for
child processes and thread control. Much of the monkey-patching that Polyphony
performs is around that.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a (probably incomplete) list of APIs monkey-patched by Polyphony:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;IO&lt;/code&gt; - all read/write instance methods and all read/write class methods&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Socket&lt;/code&gt;/&lt;code&gt;TCPSocket&lt;/code&gt;/&lt;code&gt;TCPServer&lt;/code&gt; et al - all I/O functionality including &lt;code&gt;accept&lt;/code&gt; and &lt;code&gt;connect&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;OpenSSL::SSL::SSLSocket&lt;/code&gt;/&lt;code&gt;OpenSSL::SSL::SSLSocket&lt;/code&gt; - all &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt;/&lt;code&gt;accept&lt;/code&gt;/&lt;code&gt;connect&lt;/code&gt; methods&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Kernel&lt;/code&gt; - methods such as &lt;code&gt;sleep&lt;/code&gt;, ` (backtick) , &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;trap&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Process#detach&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Timeout#timeout&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Thread#new&lt;/code&gt; and &lt;code&gt;Thread#join&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Polyphony also provides monkey-patches for gems such as &lt;code&gt;pg&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;, &lt;code&gt;mysql2&lt;/code&gt;
and &lt;code&gt;sequel&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Polyphony uses monkey-patching extensively because it&amp;#8217;s the best way to
achieving the goals I set to myself in developing it. Yes, monkey-patching has
its disadvantages, but it also has advantages (as I showed above). Finally, I
believe Polyphony should be rather judged by what it can do, and by the value it
provides to developers.&lt;/p&gt;
</description></item><item><title>Explaining Ruby Fibers</title><link>https://noteflakes.com/articles/2021-10-20-explaining-ruby-fibers</link><guid>https://noteflakes.com/articles/2021-10-20-explaining-ruby-fibers</guid><pubDate>Wed, 20 Oct 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;Fibers have long been a neglected corner of the Ruby core API. Introduced in
Ruby version 1.9 as a coroutine abstraction, fibers have never really seemed to
realize their promise of lightweight concurrency, and remain relatively little
explored. In spite of attempts to employ them for achieving concurrency, most
notably &lt;a href=&quot;https://github.com/igrigorik/em-synchrony&quot;&gt;em-synchrony&lt;/a&gt;, fibers have
not caught on. Hopefully, with the advent of the
&lt;a href=&quot;https://rubyapi.org/3.0/o/fiber/schedulerinterface&quot;&gt;FiberScheduler&lt;/a&gt; interface
introduced in Ruby 3.0, and libraries such as
&lt;a href=&quot;https://github.com/socketry/async&quot;&gt;Async&lt;/a&gt; and
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;, this situation will
change and fibers will become better known and understood, and Ruby developers
will be able to use them to their full potential.&lt;/p&gt;

&lt;p&gt;My aim in this article is to explain how fibers work from the point of view of a
concurrent Ruby application written using Polyphony. I&amp;#8217;ll give an overview of
fibers as concurrency constructs, and discuss how Polyphony harnesses Ruby
fibers in order to provide an idiomatic and performant solution for writing
highly-concurrent Ruby apps. For the sake of simplicity, I&amp;#8217;ll omit some details,
which will be mentioned in footnotes towards the end of this article.&lt;/p&gt;

&lt;h2 id=&quot;whats-a-fiber&quot;&gt;What&amp;#8217;s a Fiber?&lt;/h2&gt;

&lt;p&gt;Most developers (hopefully) know about threads, but just what are fibers?
Linguistically we can already tell they have some relation to threads, but what
is the nature of that relation? Is it one of aggregation (as in &amp;#8220;a thread is
made of one or more fibers&amp;#8221;,) or is it one of similitude (as in &amp;#8220;a fiber is sort
of like a thread but lighter/smaller/whatever&amp;#8221;?) As we shall see, it&amp;#8217;s
a little bit of both.&lt;/p&gt;

&lt;p&gt;A fiber is simply an independent execution context that can be paused and
resumed programmatically. We can think of fibers as story lines in a book or a
movie: there are multiple happenings involving different persons at different
places all occurring at the same time, but we can only follow a single story
line at a time: the one we&amp;#8217;re currently reading or watching.&lt;/p&gt;

&lt;p&gt;This is one of the most important insights I had personally when I started
working with Ruby fibers: there&amp;#8217;s always a currently active fiber. In fact, even
if you don&amp;#8217;t use fibers and don&amp;#8217;t do any fiber-related work, your code is still
running in the context of the main fiber for the current thread, which is
created automatically by the Ruby runtime &lt;em&gt;for each thread&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So a fiber is an execution context, and in that regard it&amp;#8217;s kind of like a
thread: it keeps track of a sequence of operations, and as such it has its own
stack and instruction pointer. Where fibers differ from threads is that they are
not managed by the operating system, but instead are paused, resumed, and
switched between by the userspace program itself. This programmatic way to
switch between different execution contexts is also called &amp;#8220;cooperative
multitasking&amp;#8221;, in contrast to the way threads are being switched by the
operating system, called &amp;#8220;preemptive multitasking&amp;#8221;.&lt;/p&gt;

&lt;h2 id=&quot;switching-between-fibers&quot;&gt;Switching between Fibers&lt;/h2&gt;

&lt;p&gt;Here&amp;#8217;s a short example of how switching between fibers is done&lt;a href=&quot;#f1&quot;&gt;¹&lt;/a&gt;. In this
example we&amp;#8217;re doing the fiber switching manually:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;fiber&#39;&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hi from f1&#39;&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hi again from f1&#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@f2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hi from f2&#39;&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Hi from main fiber&#39;&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;Bye from main fiber&#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we create two fibers. The main fiber then transfers
control (&amp;#8220;switches&amp;#8221;) to fiber &lt;code&gt;@f1&lt;/code&gt;, which then transfers control to &lt;code&gt;@f2&lt;/code&gt;,
which then returns control to &lt;code&gt;@f1&lt;/code&gt;. When &lt;code&gt;@f1&lt;/code&gt; has finished running, control is
returned automatically to the main fiber, and the program terminates. The
program&amp;#8217;s output will be:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Hi from main fiber
Hi from f1
Hi from f2
Hi again from f1
Bye from main fiber
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One peculiarity of the way switching between fibers is done, is that the context
switch is always initiated from the currently active fiber. In other words, the
currently active fiber must voluntarily yield control to another fiber. This is
also the reason why this model of concurrency is called &amp;#8220;cooperative
concurrency.&amp;#8221;&lt;/p&gt;

&lt;p&gt;Another important detail to remember is that a fiber created using &lt;code&gt;Fiber.new&lt;/code&gt;
always starts in a suspended state. It will not run unless you first switch to
it using &lt;code&gt;Fiber#transfer&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;controlling-fiber-state-via-the-context-switch&quot;&gt;Controlling fiber state via the context switch&lt;/h2&gt;

&lt;p&gt;What I find really interesting about the design of Ruby fibers is that the act
of pausing a fiber, then resuming it later, is seen as a normal method call from
the point of view of the fiber being resumed: we make a call to
&lt;code&gt;Fiber#transfer&lt;/code&gt;, and that call will return only when the fiber who made that
call is itself resumed using a reciprocal call to &lt;code&gt;Fiber#transfer&lt;/code&gt;. The return
value will be the value given as an argument to the reciprocal call, as is
demonstrated in the following example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ping&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;ping&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pong&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;pong&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The block provided to &lt;code&gt;Fiber.new&lt;/code&gt; can take a block argument which will be set to
the value given to &lt;code&gt;Fiber#transfer&lt;/code&gt; the first time the fiber is being resumed.
We use this to set &lt;code&gt;ping&lt;/code&gt;&amp;#8217;s peer, and then use &lt;code&gt;Fiber#transfer&lt;/code&gt; to pass messages
between the &lt;code&gt;ping&lt;/code&gt; and &lt;code&gt;pong&lt;/code&gt; fibers.&lt;/p&gt;

&lt;p&gt;This characteristic of &lt;code&gt;Fiber#transfer&lt;/code&gt; has profound implications: it means that
we can control a fiber&amp;#8217;s state using the value we pass to it as we resume it.
Here&amp;#8217;s an example for how this could be done:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:reset&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:increment&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;count = &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# count = 3&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:reset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;                     &lt;span class=&quot;c1&quot;&gt;# count = 0&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:increment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# count = 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, the fiber &lt;code&gt;f&lt;/code&gt; gets its state updated each time it is
resumed. The main fiber can control &lt;code&gt;f&lt;/code&gt;&amp;#8217;s state by passing a special value when
calling &lt;code&gt;Fiber#transfer&lt;/code&gt;. As long as we have in place a well-defined convention
for how the transferred value is interpreted by fibers in a given program, we
can implement arbitrarily complex semantics for controlling our fibers&amp;#8217; state
and life-cycle. Later on in this article we&amp;#8217;ll see how Polyphony couples this
mechanism with return values from blocking operations, as well as exceptions
that permit us to cancel any long-running operation at any time.&lt;/p&gt;

&lt;h2 id=&quot;switching-fibers-on-long-running-operations&quot;&gt;Switching fibers on long-running operations&lt;/h2&gt;

&lt;p&gt;Now that we have a feel for how fibers can be paused and resumed, we can discuss
how this can be used in conjunction with blocking operations. For the sake of
our discussion, a blocking operation is any operation that potentially needs to
wait for some external event to occur, such as a socket becoming readable, or
waiting for a timer to elapse. We want to be able to have multiple concurrent
tasks, each proceeding at its own pace, and each yielding control whenever it
needs to wait for some external event to occur.&lt;/p&gt;

&lt;p&gt;In order to demonstrate how this might work, let&amp;#8217;s imagine a program where we
have two fibers: one waits for data to arrive on STDIN, then echoes it; and a
second fiber that prints the time once a second:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@echo_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readable?&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readpartial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@time_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@time_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;elapsed?&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reset&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@echo_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In each of the above fibers, we have have a condition that tells us if the fiber
can proceed: for &lt;code&gt;@echo_printer&lt;/code&gt; the condition is &lt;code&gt;STDIN.readable?&lt;/code&gt;; for
&lt;code&gt;@time_printer&lt;/code&gt; it&amp;#8217;s &lt;code&gt;timer.elapsed?&lt;/code&gt;. What&amp;#8217;s notable though about this example
that the switching between fibers is done &lt;em&gt;explicitly&lt;/em&gt;, and that each fiber
needs to check a condition continually. Of course, this is not ideal, since the
two fibers will just pass control between them until one of the conditions is
met and actual work is done. If you run such a program, you&amp;#8217;ll see one of your
CPU cores saturated. But the main insight to draw here is that each fiber can
yield control to another fiber if it cannot go on doing actual work.&lt;/p&gt;

&lt;h2 id=&quot;automatic-fiber-switching-using-an-event-reactor&quot;&gt;Automatic fiber switching using an event reactor&lt;/h2&gt;

&lt;p&gt;Let&amp;#8217;s see if we can avoid endlessly checking for readiness conditions, by using
an event reactor - a piece of software that lets you subscribe to specific
events, most importantly I/O readiness, and timers. In this case we&amp;#8217;ll be using
&lt;a href=&quot;https://github.com/digital-fabric/ever&quot;&gt;ever&lt;/a&gt;, a tiny Ruby gem that I wrote a
few months ago, implementing an event reactor for Ruby apps based on
&lt;a href=&quot;http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod&quot;&gt;libev&lt;/a&gt;. Here&amp;#8217;s our
rewritten example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;ever&#39;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;fiber&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;evloop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Ever&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@reactor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@echo_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readpartial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@reactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@time_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@reactor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# register interest in events&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;evloop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;watch_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@echo_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;evloop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;watch_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@time_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# run loop&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;evloop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, our fibers no longer need to check for readiness. The reactor,
after registering interest in the respective event for each fiber, runs the
event loop, and when an event becomes available, the corresponding fiber is
resumed. Once resumed, each fiber does its work, then yields control back to the
reactor.&lt;/p&gt;

&lt;p&gt;This is already a great improvement, since our program does not need to
endlessly check for readiness, and the code for each of the fibers doing actual
work looks almost normal. The only sign we have of anything &amp;#8220;weird&amp;#8221; going on is
that each fiber needs to yield control back to the reactor by calling
&lt;code&gt;@reactor.transfer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we look more closly at the above program, we can describe what&amp;#8217;s actually
happening as follows:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@echo_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readpartial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;wait_for_events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queued_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;queued_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;vi&quot;&gt;@time_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;wait_for_events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queued_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;empty?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;queued_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transfer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For each of our fiber, at any point where the fiber needs to wait, we first look
at our list of queued events. If there are none, we wait for events to occur.
Finally we proceed to handle those events by transferring control to each
respective fiber. This needs to be done for each blocking or long-running
operation: reading from a file, reading from a socket, writing to a socket,
waiting for a time period to elapsed, waiting for a process to terminate, etc.
What if we had a tool that could automate this for us? This is where Polyphony
enters the stage.&lt;/p&gt;

&lt;h2 id=&quot;using-polyphony-for-fiber-based-concurrency&quot;&gt;Using Polyphony for fiber-based concurrency&lt;/h2&gt;

&lt;p&gt;Polyphony automates all the different aspects of fiber switching, which boils
down to knowing which fiber should be running at any given moment. Let&amp;#8217;s see how
Polyphony solves the problem of fiber switching by using it to rewrite the
example above:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;polyphony&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;echo_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;STDIN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;time_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Time: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;echo_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As our rewritten example shows, we got completely rid of calls to
&lt;code&gt;Fiber#transfer&lt;/code&gt;. The fiber switching is handled automatically and implicitly by
Polyphony&lt;a href=&quot;#f2&quot;&gt;²&lt;/a&gt;. Each fiber is an autonomous &lt;a href=&quot;/articles/2021-10-14-embracing-infinite-loops&quot;&gt;infinite
loop&lt;/a&gt; made of a sequence of
operations that&amp;#8217;s simple to write and simple to read. All the details of knowing
when &lt;code&gt;STDIN&lt;/code&gt; is ready or when a second has elapsed, and which fiber to run at
any moment, are conveniently taken care of by Polyphony.&lt;/p&gt;

&lt;p&gt;Even more importantly, Polyphony offers a fluent, idiomatic API that mostly gets
out of your way and feels like an integral part of the Ruby core API. Polyphony
introduces the &lt;code&gt;#spin&lt;/code&gt; global method, which creates a new fiber and schedules it
for running as soon as possible (remember: fibers start their life in a
suspended state.) The call to &lt;code&gt;Fiber.await&lt;/code&gt; means that the main fiber, from
which the two other fibers were spun, will wait for those two fibers to
terminate. Because both &lt;code&gt;echo_printer&lt;/code&gt; and &lt;code&gt;time_printer&lt;/code&gt; are infinite loops,
the main fiber will wait forever.&lt;/p&gt;

&lt;p&gt;Now that we saw how Polyphony handles behind the scenes all the details of
switching between fibers, let&amp;#8217;s examine how Polyphony does that.&lt;/p&gt;

&lt;h2 id=&quot;the-fiber-switching-dance&quot;&gt;The fiber switching dance&lt;/h2&gt;

&lt;p&gt;A fiber will spend its life in one of three states: &lt;code&gt;:running&lt;/code&gt;, &lt;code&gt;:waiting&lt;/code&gt; or
&lt;code&gt;:runnable&lt;/code&gt;&lt;a href=&quot;#f3&quot;&gt;³&lt;/a&gt;. As discussed above, only a single fiber can be &lt;code&gt;:running&lt;/code&gt; at a
given moment (for each thread). When a fiber needs to perform a blocking
operation, such as reading from a file descriptor, it makes a call to the
Polyphony backend associated with its thread, which performs the actual I/O
operation. The backend will submit the I/O operation to the OS (using the
&lt;a href=&quot;https://unixism.net/loti/what_is_io_uring.html&quot;&gt;io_uring&lt;/a&gt; interface&lt;a href=&quot;#f4&quot;&gt;⁴&lt;/a&gt;,) and will
switch to the first fiber pulled from the &lt;em&gt;runqueue&lt;/em&gt;, which will run until it
too needs to perform a blocking operation, at which point another fiber switch
will occur to the next fiber pulled from the runqueue.&lt;/p&gt;

&lt;p&gt;Meanwhile, our original fiber has been put in the &lt;code&gt;:waiting&lt;/code&gt; state. Eventually,
the runqueue will be exhausted, which means that all fibers are waiting for some
event to occur. At this point, the Polyphony backend will check whether any of
the currently ongoing I/O operation have completed. For each completed
operation, the corresponding fiber is &amp;#8220;scheduled&amp;#8221; by putting it on the runqueue,
and the fiber transitions to the &lt;code&gt;:runnable&lt;/code&gt; state. The runnable state means
that the operation the fiber was waiting for has been completed (or cancelled),
and the fiber can be resumed.&lt;/p&gt;

&lt;p&gt;Once all completed I/O operations have been processed, the backend performs a
fiber switch to the first fiber available on the runqueue, the fiber transitions
back to the &lt;code&gt;:running&lt;/code&gt; state, and the whole fiber-switching dance recommences.&lt;/p&gt;

&lt;p&gt;What&amp;#8217;s notable about this way of scheduling concurrent tasks is that Polyphony
does not really have an event loop that wraps around your code. Your code does
not run inside of a loop. Instead, whenever your code needs to perform some
blocking operation, the Polyphony backend starts the operation, then switches to
the next fiber that is &lt;code&gt;:runnable&lt;/code&gt;, or ready to run.&lt;/p&gt;

&lt;h2 id=&quot;the-runqueue&quot;&gt;The runqueue&lt;/h2&gt;

&lt;p&gt;The runqueue is simply a FIFO queue that contains all the currently runnable
fibers, that is fibers that can be resumed. Let&amp;#8217;s take our last example and
examine how the contents of the runqueue changes as the program executes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; []&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;echo_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [echo_printer]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;time_printer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [echo_printer, time_printer]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# at this point the two fibers have been put on the runqueue and will be resumed&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# once the current (main) fiber yields control:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;echo_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time_printer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# while the main fiber awaits, the two fibers are resumed. echo_printer will&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# wait for STDIN to become readable. time_printer will wait for 1 second to&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# elapse.&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The runqueue is empty.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; []&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Since there&#39;s no runnable fiber left, the Polyphony backend will wait for&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# io_uring to generate at least one completion entry. A second has elapsed, and&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# time_printer&#39;s completion has arrived. The fiber becomes runnable and is put&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# back on the runqueue.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [time_printer]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Polyphony pulls the fiber from the runqueue and switches to it. The time is&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# printed, and time_printer goes back to sleeping for 1 second. The runqueue is&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# empty again:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; []&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The Polyphony backend waits again for completions to occur. The user types a&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# line and hits RETURN. The completion for echo_printer is received, and&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# echo_printer is put back on the runqueue:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; [echo_printer]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Polyphony pulls the fiber from the runqueue and switches to it.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;runqueue&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; []&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While the runqueue was represented above as a simple array of runnable fibers,
its design is actually much more sophisticated than that. First of all, the
runqueue is implemented as a &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/ext/polyphony/runqueue_ring_buffer.c&quot;&gt;ring
buffer&lt;/a&gt;
in order to achieve optimal performance. The use of a ring buffer algorithm
results in predictable performance characteristics for both adding and removing
of entries from the runqueue. When adding an entry to a runqueue that&amp;#8217;s already
full, the underlying ring buffer is reallocated to twice its previous size. For
long running apps, the runqueue size will eventually stabilize around a value
that reflects the maximum number of currently runnable fibers in the process.&lt;/p&gt;

&lt;p&gt;In addition, each entry in the runqueue contains not only the fiber, but also
the value with which it will be resumed. If you recall, earlier we talked about
the fact that we can control a fiber&amp;#8217;s state whenever we resume it by passing it
a value using &lt;code&gt;Fiber#transfer&lt;/code&gt;. The fiber will receive this value as the return
value of its own previous call to &lt;code&gt;Fiber#transfer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Polyphony, each time a fiber is resumed, the return value is checked to see
if it&amp;#8217;s an exception. In case of an exception, it will be raised in the context
of the resumed fiber. Here&amp;#8217;s an excerpt from Polyphony&amp;#8217;s &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/ext/polyphony/backend_io_uring.c&quot;&gt;io_uring
backend&lt;/a&gt;
that implements the global &lt;code&gt;#sleep&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Backend_sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Backend_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;GetBackend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;VALUE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Qnil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;io_uring_backend_submit_timeout_and_await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NUM2DBL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;RAISE_IF_EXCEPTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;RB_GC_GUARD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the code above, we first submit an iouring timeout entry, then yield
control (by calling &lt;code&gt;Fiber#transfer&lt;/code&gt; on the next runnable fiber) and await its
completion. When the call to &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/89b18ce3626e253e1731e6489d59845f0a3e50cb/ext/polyphony/backend_io_uring.c#L980-L989&quot;&gt;&lt;code&gt;io_uring_backend_submit_timeout_and_await&lt;/code&gt;&lt;/a&gt;
returns, our fiber has alreadey been resumed, with &lt;code&gt;resume_value&lt;/code&gt; holding the
value returned from our call to &lt;code&gt;Fiber#transfer&lt;/code&gt;. We use &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/89b18ce3626e253e1731e6489d59845f0a3e50cb/ext/polyphony/polyphony.h#L23-L26&quot;&gt;&lt;code&gt;RAISE_IF_EXCEPTION&lt;/code&gt;&lt;/a&gt; to
check if &lt;code&gt;resume_value&lt;/code&gt; is an exception, and raise it in case it is.&lt;/p&gt;

&lt;p&gt;It&amp;#8217;s also important to note the resume values stored alongside fibers in the
runqueue can be used in effect to control a fiber&amp;#8217;s state, just like in bare
bones calls to &lt;code&gt;Fiber#transfer&lt;/code&gt;. This can be done using the Polyphony-provided
&lt;code&gt;Fiber#schedule&lt;/code&gt; method, which puts the fiber on the runqueue, along with the
provided resume value:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;lazy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;suspend&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Got &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;every&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lazy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;O hi!&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the example above, our &lt;code&gt;lazy&lt;/code&gt; fiber suspends itself (using &lt;code&gt;#suspend&lt;/code&gt;, which
puts it in a &lt;code&gt;:waiting&lt;/code&gt; state), and the main fiber schedules it once every
second along with a message. The lazy fiber receives the message as the return
value of the call to &lt;code&gt;#suspend&lt;/code&gt;. One important difference between
&lt;code&gt;Fiber#schedule&lt;/code&gt; and &lt;code&gt;Fiber#transfer&lt;/code&gt; is that &lt;code&gt;Fiber#schedule&lt;/code&gt; does not perform
a context switch. It simply puts the fiber on the runqueue along with its resume
value. The fiber will be resumed as soon as all previous runnable fibers have
been resumed and have yielded control.&lt;/p&gt;

&lt;h2 id=&quot;yielding-control&quot;&gt;Yielding control&lt;/h2&gt;

&lt;p&gt;As explained above, blocking operations involve submitting the operation to the
io_uring interface, and then yielding (or &lt;code&gt;#transfer&lt;/code&gt;ring) control to the next
runnable fiber. A useful metaphor here is the relay race: only a single person
runs at any given time (she holds the baton,) and eventually the runner will
pass the baton to the next person who&amp;#8217;s ready to run. Let&amp;#8217;s examine what happens
during this &amp;#8220;passing of the baton&amp;#8221; in a little more detail.&lt;/p&gt;

&lt;p&gt;In effect, once the I/O operation has been submitted, the Polyphony backend
calls the
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/c7f670f12c386f7ae607c0a6cce5427acefd59d8/ext/polyphony/backend_common.c#L59-L105&quot;&gt;&lt;code&gt;backend_base_switch_fiber&lt;/code&gt;&lt;/a&gt;
function, which is responsible for this little ceremony, which consists of the
following steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Shift the first runqueue entry from the runqueue.&lt;/li&gt;
  &lt;li&gt;If the entry is not &lt;code&gt;nil&lt;/code&gt;:
    &lt;ul&gt;
      &lt;li&gt;Check if it&amp;#8217;s time to do a non-blocking poll (in order to prevent starvation
 of I/O completions. See discussion below.)&lt;/li&gt;
      &lt;li&gt;proceed to step 4.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Otherwise:
    &lt;ul&gt;
      &lt;li&gt;Perform a blocking poll.&lt;/li&gt;
      &lt;li&gt;Go back to step 1.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Transfer control to the entry&amp;#8217;s fiber with the entry&amp;#8217;s resume value using
&lt;code&gt;Fiber#transfer&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All this happens in the context of the fiber that yields control, until a
context switch is performed in step 4. To reuse our &amp;#8220;relay race&amp;#8221; metaphor, each
time the current runner wishes to pass the baton to the next one, it&amp;#8217;s as if she
has a little gadget in her hand that holds the baton, performs all kinds of
checks, finds out who the next runner is, and finally hands it over to the next
runner.&lt;/p&gt;

&lt;h2 id=&quot;polling-for-io-completions&quot;&gt;Polling for I/O completions&lt;/h2&gt;

&lt;p&gt;When there are no runnable fibers left, Polyphony polls for at least one
io_uring completion to arrive. For each received completion, the corresponding
fiber is scheduled by putting it on the runqueue. Once the polling is done, the
first fiber is pulled off the runqueue and is then resumed.&lt;/p&gt;

&lt;p&gt;As we saw, polling for completions is only perofrmed done when the runqueue is
empty. But what about situations where the runqueue is never empty? Consider the
following example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;f1&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@f2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;f2&#39;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@f1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@f2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each of the fibers above will run in an infinite loop, scheduling its peer and
then printing a message. As shown above, the call to &lt;code&gt;#puts&lt;/code&gt;, being an I/O
operation, causes the Polyphony backend to submit the &lt;code&gt;write&lt;/code&gt; operation to the
io_uring interface, and then perform a context switch to the next runnable
fiber. In order for the call to &lt;code&gt;#puts&lt;/code&gt; to return, the Polyphony backend needs
to poll for completions from the io_uring interface. But, since the runqueue is
never empty (both fibers are scheduling each other, effectively adding each
other to the runqueue,) the runqueue will never be empty!&lt;/p&gt;

&lt;p&gt;In order to be able to deal with such circumstances, and prevent the
&amp;#8220;starvation&amp;#8221; of completion processing, the Polyphony backend periodically
performs a non-blocking check for any received completions. This mechanism
assures that even in situations where our application becomes CPU-bound (since
there&amp;#8217;s always some fiber running!) we&amp;#8217;ll continue to process io_uring
completions, and our entire process will continue to behave normally.&lt;/p&gt;

&lt;h2 id=&quot;implications-for-performance&quot;&gt;Implications for performance&lt;/h2&gt;

&lt;p&gt;The asynchronous nature of the io_uring interface has some interesting
implications for the performance of Polyphony&amp;#8217;s io_uring backend. As mentioned
above, the submission of SQE&amp;#8217;s is deferred, and performed either when a certain
number of submissions have acumulated, or before polling for completions.&lt;/p&gt;

&lt;p&gt;In Polyphony, runnable fibers are always prioritized, and polling for events is
done mostly when there are no runnable fibers. Theoretically, this &lt;em&gt;might&lt;/em&gt; have
a negative impact on latency, but I have seen the io_uring backend achieve more
than twice the throughput achieved by the libev backend. Tipi, the
Polyphony-based web server I&amp;#8217;m currently developing, boasts a request rate of
&lt;a href=&quot;https://github.com/digital-fabric/tipi/#benchmarks&quot;&gt;more than 138K
requests/second&lt;/a&gt; at the time
of this writing.&lt;/p&gt;

&lt;p&gt;In fact, Polyphony provides multiple advantages over other concurrency
solutions: The number of I/O-related syscalls is minimized (by using the
io_uring interface.) In addition, the use &lt;code&gt;Fiber#transfer&lt;/code&gt; to transfer control
&lt;em&gt;directly&lt;/em&gt; to the next runnable fiber halves the number of context-switches when
compared to using &lt;code&gt;Fiber#resume&lt;/code&gt;/&lt;code&gt;Fiber.yield&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Most importantly, by prioritizing runnable fibers over processing of I/O
completions (with an anti-starvation mechanism as described above,) Polyphony
lets a Ruby app switch easily between I/O-bound work and CPU-bound work. For
example, when a Polyphony-based web server receives 1000 connections in the
space of 1ms, it needs to perform a lot of work, setting up a fiber, a parser
and associated state for each connection. The design of Polyphony&amp;#8217;s scheduling
system allows the web server to do this burst of hard work while deferring any
I/O work for later submission. When this CPU-bound burst has completed, the web
server fires all of its pending I/O submissions at once, and can proceed to
check for completions.&lt;/p&gt;

&lt;h2 id=&quot;cancellation&quot;&gt;Cancellation&lt;/h2&gt;

&lt;p&gt;Now that we have a general idea of how Polyphony performs fiber switching, let&amp;#8217;s
examine how cancellation works in Polyphony. We want to be able to cancel any
ongoing operation at any time. This can be done for any reason: a timeout has
elapsed, an exception has occurred in a related fiber, or business logic that
requires cancelling some specific operation in specific circumstances. The
mechanism for doing this is simple, as mentioned above: we use an exception as
the resume value that will be transferred to the fiber when the context switch
occurs.&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s a simple example that shows how cancellation looks from the app&amp;#8217;s point
of view:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gets_with_timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;move_on_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gets&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;#move_on_after&lt;/code&gt; global API sets up a timer, then runs the given block. If
the timer has elapsed before the block has finished executing, the fiber is
scheduled with a &lt;code&gt;Polyphony::MoveOn&lt;/code&gt; exception, any ongoing I/O operation is
cancelled by the backend, the exception is caught by &lt;code&gt;#move_on_after&lt;/code&gt; and an
optional cancellation value is returned.&lt;/p&gt;

&lt;p&gt;If we want to generate an exception on timeout, we can instead use the
&lt;code&gt;#cancel_after&lt;/code&gt; API, which will schedule the fiber with a &lt;code&gt;Polyphony::Cancel&lt;/code&gt;
exception, and the exception will have to be caught by the app code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gets_with_timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cancel_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gets&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Cancel&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;gets was cancelled!&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;more-ways-to-control-fiber-execution&quot;&gt;More ways to control fiber execution&lt;/h2&gt;

&lt;p&gt;In addition to the various APIs discussed above, here&amp;#8217;s a list of some of the
various APIs used for controlling fiber execution:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;#suspend&lt;/code&gt; - transition from &lt;code&gt;:running&lt;/code&gt; to &lt;code&gt;:waiting&lt;/code&gt; state. A fiber that was
suspended will not be resumed until it is manually scheduled.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;#snooze&lt;/code&gt; - transition from &lt;code&gt;:running&lt;/code&gt; to &lt;code&gt;:runnable&lt;/code&gt; state. The fiber is put
on the runqueue, and will eventually get its turn again. This method is useful
when a fiber performs a long-running CPU-bound operation and needs to
periodically let other fibers have their turn.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Fiber#schedule&lt;/code&gt; - transition from &lt;code&gt;:waiting&lt;/code&gt; to &lt;code&gt;:runnable&lt;/code&gt; state, with an
optional resume value.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Fiber#terminate&lt;/code&gt; - &lt;a href=&quot;/articles/2021-10-14-embracing-infinite-loops#more-ways-to-stop-a-fiber&quot;&gt;terminate a
fiber&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Polyphony has been built to harness the full power of Ruby fibers, and provide a
solid and joyful experience for those wishing to write highly-concurrent Ruby
apps. There are many subtleties to designing a robust, well-behaving fiber-based
concurrent environment. For example, there&amp;#8217;s the problem of
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/47ca56812a7b4dab7e0f0d9b13f98ce70e522ff8/lib/polyphony.rb#L24-L39&quot;&gt;forking&lt;/a&gt;
from arbitrary fibers, or the problem of correctly handling
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/47ca56812a7b4dab7e0f0d9b13f98ce70e522ff8/lib/polyphony/extensions/core.rb#L145-L161&quot;&gt;signals&lt;/a&gt;.
Polyphony aims to take care of all these details, in order to be able to handle
a broad range of applications and requirements.&lt;/p&gt;

&lt;p&gt;I hope this article has helped clear up some of the mystery and misconceptions
about Ruby fibers. Please &lt;a href=&quot;/about&quot;&gt;let me know&lt;/a&gt; if you have specific questions
about fibers in general or Polyphony in particular, and feel free to browse
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&amp;#8217;s source code&lt;/a&gt;.
Contributions will be gladly accepted!&lt;/p&gt;

&lt;h3 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h3&gt;

&lt;p&gt;&lt;a id=&quot;f1&quot;&gt;1.&lt;/a&gt; In this article we&amp;#8217;ll confine ourselves to using the
&lt;code&gt;Fiber#transfer&lt;/code&gt; API for fiber switching, which is better suited for usage with
symmetric coroutines. Using the &lt;code&gt;Fiber.yield&lt;/code&gt;/&lt;code&gt;Fiber#resume&lt;/code&gt; API implies an
asymmetric usage which is better suited for fibers used as generators or
iterators. Sadly, most articles dealing with Ruby fibers only discuss the
latter, and make no mention of the former. Please note that in order to use
&lt;code&gt;Fiber#transfer&lt;/code&gt; we first need to &lt;code&gt;require &#39;fiber&#39;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;f2&quot;&gt;2.&lt;/a&gt; I&amp;#8217;ve mentioned above the &lt;a href=&quot;https://rubyapi.org/3.0/o/fiber/schedulerinterface&quot;&gt;Fiber Scheduler
interface&lt;/a&gt; introduced in
Ruby 3.0, based on the work of &lt;a href=&quot;https://github.com/ioquatix&quot;&gt;Samuel Williams&lt;/a&gt;.
This new feature, baked into the Ruby core, has roughly the same capabilities as
Polyphony when it comes to automatically switching between fibers based on I/O
readiness. As its name suggests, this is just a well-defined interface. In order
to be able to employ it in your Ruby app, you&amp;#8217;ll need to use an actual fiber
scheduler. At the moment, the following fiber schedulers are available, in
different states of production-readiness: &lt;a href=&quot;https://github.com/dsh0416/evt&quot;&gt;evt&lt;/a&gt;,
&lt;a href=&quot;https://github.com/socketry/event&quot;&gt;event&lt;/a&gt;, and my own
&lt;a href=&quot;https://github.com/digital-fabric/libev_scheduler&quot;&gt;libev-scheduler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;f3&quot;&gt;3.&lt;/a&gt; A fourth state, &lt;code&gt;:dead&lt;/code&gt; is used for fibers that have terminated. A fiber&amp;#8217;s
state can be interrogated using &lt;code&gt;Fiber#state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a id=&quot;f4&quot;&gt;4.&lt;/a&gt; Polyphony also includes an alternative backend used on non-Linux OSes or
older Linux kernels. Both backends have the same capabilities. In this article
we&amp;#8217;ll discuss only the io_uring backend.&lt;/p&gt;
</description></item><item><title>Embracing Infinite Loops with Ruby and Polyphony</title><link>https://noteflakes.com/articles/2021-10-14-embracing-infinite-loops</link><guid>https://noteflakes.com/articles/2021-10-14-embracing-infinite-loops</guid><pubDate>Thu, 14 Oct 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;In this article I&amp;#8217;ll discuss the use of infinite loops as a major construct when
writing concurrent apps in Ruby using Polyphony. I&amp;#8217;ll show how infinite loops
differ from normal, finite ones; how they can be used to express long-running
tasks in a concurrent environment; and how they can be stopped.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt; is a library for
writing highly concurrent Ruby apps. Polyphony harnesses Ruby fibers and a
powerful &lt;a href=&quot;https://unixism.net/loti/what_is_io_uring.html&quot;&gt;io_uring&lt;/a&gt;-based I/O
runtime to provide a solid foundation for building high-performance concurrent
Ruby apps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the last few months I&amp;#8217;ve been slowly transitioning from working on
Polyphony-designing APIs and adding functionality-to using to develop actual
applications, some of them &lt;a href=&quot;https://github.com/digital-fabric&quot;&gt;open source&lt;/a&gt;, and
others closed source production apps for my clients.&lt;/p&gt;

&lt;p&gt;In the process of actually using Polyphony as the basis for writing concurrent
apps, I&amp;#8217;ve discovered some patterns that I&amp;#8217;d like to share. It&amp;#8217;s really
fascinating how the design of an API can impact the patterns that emerge in the
application code. Take for example loops.&lt;/p&gt;

&lt;p&gt;Developers that are used to asynchronous APIs will probably find the idea of
writing loops in your app code anathema to asynchronous design: there&amp;#8217;s only one
loop - the main event loop - and it is that loop which drives your code. You
just provide callbacks to be called at the right moment.&lt;/p&gt;

&lt;p&gt;By contrast, with Polyphony the app code is written in a sequential style, and
it is the app code that is in control. There is &lt;em&gt;no&lt;/em&gt; event loop. Instead, you
can create any number of fibers, all executing concurrently, with each of those
fibers proceeding independently of the others.&lt;/p&gt;

&lt;p&gt;But loops come into play when you want to launch autonomous long-running tasks,
like listening for incoming connections on a TCP socket, pulling items from a
queue and processing them, or periodically running some background task.
Infinite loops are what makes it possible to &amp;#8220;fire-and-forget&amp;#8221; those concurrent
processes.&lt;/p&gt;

&lt;h2 id=&quot;loops-are-everywhere&quot;&gt;Loops are everywhere!&lt;/h2&gt;

&lt;p&gt;Loops are one of the most useful ways to control execution. Loops are used
anywhere you need to repeat an operation, and can be expressed in a variety of
ways, from the lowly &lt;code&gt;GOTO&lt;/code&gt;, through plain &lt;code&gt;for&lt;/code&gt; and &lt;code&gt;while&lt;/code&gt; loops, all the way
to Ruby&amp;#8217;s elegant &lt;code&gt;#each&lt;/code&gt; and related methods, which take a block and apply it
to items from some &lt;em&gt;iterable&lt;/em&gt; object. While those don&amp;#8217;t necessarily look like
loops, they are, in fact, loops:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# this is a loop&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# this is also a loop&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;infinite-loops&quot;&gt;Infinite loops&lt;/h2&gt;

&lt;p&gt;Inifinite loops are loops that run indefinitely. A loop can be inadvertently
infinite if the loop logic is faulty, but loops can also be infinite by design.
Infinite loops are made for running &lt;em&gt;autonomous&lt;/em&gt;, &lt;em&gt;long-lived&lt;/em&gt; tasks that can run
any number of iterations, and are not meant to be stopped &lt;em&gt;conditionally&lt;/em&gt;. Here
are some examples:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Accept incoming connections:&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;handle_client_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Process items from a queue:&lt;/span&gt;
&lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the example above shows, Ruby provides the very useful &lt;code&gt;#loop&lt;/code&gt; method which
lets us express infinite loops in a clear and concise manner. Looking at the
code we can immediately tell that we&amp;#8217;re dealing with an infinite loop.&lt;/p&gt;

&lt;p&gt;What&amp;#8217;s important to note about infinite loops is that they &lt;em&gt;can&lt;/em&gt; include a
mechanism for breaking out of the loop if a certain condition is met. In fact,
sometimes the distinction between a finite loop and an infinite one is not that
clear.&lt;/p&gt;

&lt;p&gt;Take for example a loop for handling an HTTP client connection. It needs to run
for the life time of the connection, which can last for any duration and for any
number of HTTP requests. In this case, this might look like an infinite loop,
but it will include a conditional &lt;code&gt;break&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# using h1p for parsing HTTP/1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H1P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_headers&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# returns nil when socket is closed&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_body&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handle_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another way to express the same logic, which makes it look like a normal finite
loop, is like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handle_client_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H1P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_body&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handle_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;concurrent-infinite-loops&quot;&gt;Concurrent infinite loops&lt;/h2&gt;

&lt;p&gt;What&amp;#8217;s interesting about infinite loops is that once they start, theoretically
they will go on forever! In Polyphony you can start any number of infinite
loops, each running in its own fiber. Polyphony does the hard work of switching
between all those fibers, letting each fiber proceed at its own pace once the
operation it was waiting for has completed: reading from or writing to a socket,
waiting for an item to become available on a queue, etc. To do this, we use the
&lt;code&gt;#spin&lt;/code&gt; global method provided by Polyphony, which spins up new fibers:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;http_server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;0.0.0.0&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# each client runs in its own fiber&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_http_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we start a fiber for processing items from a queue, and
along side it an HTTP server. Each of those is implemented using an infinite
loop running on a separate fiber. Finally, the main fiber waits for those two
fibers to terminate. While the main fiber waits, Polyphony takes care of running
the item processor and the HTTP server, with each fiber proceeding at its own
pace as items are pushed into the queue, and as incoming HTTP connections are
being accepted.&lt;/p&gt;

&lt;h2 id=&quot;interrupting-an-infinite-loop&quot;&gt;Interrupting an infinite loop&lt;/h2&gt;

&lt;p&gt;As we saw above, starting an inifinite loop on a separate fiber is really easy,
but how do you interrupt one? Polyphony provides us with some tools for
interrupting fibers at any time. We can do that by scheduling the specific fiber
with an exception, which might be a normal exception, or one of the special
exceptions that Polyphony provides for controlling fibers.&lt;/p&gt;

&lt;p&gt;In order to stop a fiber running an infinite loop, we can call
&lt;code&gt;Fiber#stop&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# tell the item_processor to stop&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# then wait for it to terminate&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Under the hood, &lt;code&gt;Fiber#stop&lt;/code&gt; schedules the fiber with a &lt;code&gt;Polyphony::MoveOn&lt;/code&gt;
exception, which means that the fiber should just terminate at the earliest
occasion, without the exception bubbling further up the fiber hierarchy.&lt;/p&gt;

&lt;p&gt;As the example above shows, telling a fiber to stop does not mean it will do so
immediately. We also need to properly wait for it to terminate, which we do by
calling &lt;code&gt;item_processor.await&lt;/code&gt; or &lt;code&gt;Fiber.await(item_processor)&lt;/code&gt;. As discussed
above, stopping a fiber is done by scheduling it with a special exception that
tells it to terminate. The terminated fiber will then proceed to terminate any
child fibers it has, and perform other cleanup. This also means that you can use
normal &lt;code&gt;ensure&lt;/code&gt; blocks in order to perform cleanup. Let&amp;#8217;s rewrite our item
processor to process items by sending them in JSON format over a TCP connection:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;soket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PROCESSOR_HOSTNAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROCESSOR_PORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# the socket is now guaranteed to be closed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the &lt;code&gt;item_processor&lt;/code&gt; fiber is terminated, the ensure block guarantees that
the socket used for sending items is closed before the call to
&lt;code&gt;item_processor.await&lt;/code&gt; returns.&lt;/p&gt;

&lt;h2 id=&quot;more-ways-to-stop-a-fiber&quot;&gt;More ways to stop a fiber&lt;/h2&gt;

&lt;p&gt;In addition to the &lt;code&gt;Fiber#stop&lt;/code&gt; method, Polyphony has other APIs that can be
used to stop a fiber in a variety of ways, including by raising an exception in
the fiber&amp;#8217;s context, and gracefully terminating a fiber. Termnating a fiber with
an exception is done using &lt;code&gt;Fiber#raise&lt;/code&gt;. This is especially useful when you
need to implement your own error states:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;my_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;raise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FooError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A graceful termination can be done using &lt;code&gt;Fiber#terminate&lt;/code&gt; which takes an
optional boolean flag. This requires a bit more logic in the fiber itself:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;soket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TCPSocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PROCESSOR_HOSTNAME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PROCESSOR_PORT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_json&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;graceful_shutdown?&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;move_on_after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;wait_for_inflight_items&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# terminate gracefully&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;terminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the example above, we added logic in the ensure block that waits up to 10
seconds for all inflight items to be processed, then proceeds with closing the
TCP socket.&lt;/p&gt;

&lt;p&gt;(We&amp;#8217;ll take a closer look at exception handling and fiber termination in a
future article.)&lt;/p&gt;

&lt;h2 id=&quot;polyphony--loops&quot;&gt;Polyphony 💛 Loops&lt;/h2&gt;

&lt;p&gt;Polyphony not only makes it easy to start and stop concurrent infinite loops,
but it also further embraces loops by providing a bunch of loop APIs, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code&gt;#spin_loop&lt;/code&gt; - used for spinning up fibers that just run a loop. That way you
can be even more concise when expressing infinite loops:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;item_processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin_loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code&gt;IO#read_loop&lt;/code&gt;/&lt;code&gt;IO#recv_loop&lt;/code&gt; - used for reading from an IO instance (and
provides even better performance, since it reads in a tighter loop):&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_loop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code&gt;IO#feed_loop&lt;/code&gt; - used for feeding data read from an IO instance into a parser.
Take for example a connection that uses &lt;a href=&quot;https://msgpack.org/&quot;&gt;MessagePack&lt;/a&gt; to
pass messages:&lt;/p&gt;

    &lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;msgpack&#39;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;unpacker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MessagePack&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Unpacker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# #feed_loop takes a receiver and the method to call on each chunk of data&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;feed_loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unpacker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:feed_each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the future Polyphony will include even more &lt;code&gt;#xxx_loop&lt;/code&gt; APIs that will
provide more concise ways to express loops along with better performance.&lt;/p&gt;

&lt;h2 id=&quot;polyphony-is-just-plain-ruby&quot;&gt;Polyphony is just plain Ruby&lt;/h2&gt;

&lt;p&gt;Looking at all the above examples, you will have noticed how the Polyphony API
really looks baked into the Ruby language, as if it was part of the Ruby core.
One of my principal design goals was to minimize boilerplate code when
expressing concurrent operations. There&amp;#8217;s no instantiating of special objects,
no weird mechanisms for controlling fibers or rescuing exceptions. It just looks
like plain Ruby! This makes it easier to both write &lt;em&gt;and&lt;/em&gt; read concurrent code.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In this article I&amp;#8217;ve showed you how infinite loops can be used to express
long-running concurrent tasks using
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;. Polyphony provides all
the tools needed for controlling the execution of concurrent fibers. For more
information about Polyphony you can go to the &lt;a href=&quot;https://digital-fabric.github.io/polyphony/&quot;&gt;Polyphony
website&lt;/a&gt;. You can also browse the
&lt;a href=&quot;https://github.com/digital-fabric/polyphony/tree/master/examples&quot;&gt;examples&lt;/a&gt; in
the Polyphony repository.&lt;/p&gt;
</description></item><item><title>A Compositional Approach to Optimizing the Performance of Ruby Apps</title><link>https://noteflakes.com/articles/2021-10-05-a-compositional-approach-to-ruby-performance</link><guid>https://noteflakes.com/articles/2021-10-05-a-compositional-approach-to-ruby-performance</guid><pubDate>Tue, 05 Oct 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;Ruby has long been derided as a slow programming language. While this accusation
has some element of truth to it, successive Ruby versions, released yearly, have
made great strides in improving Ruby&amp;#8217;s performance characteristics.&lt;/p&gt;

&lt;p&gt;In addition to all the iterative performance improvements - Ruby 2.6 and 2.7
were especially impressive in terms of performance gains - recent versions have
introduced bigger features aimed at improving performance, namely: a &lt;a href=&quot;https://k0kubun.medium.com/the-method-jit-compiler-for-ruby-2-6-388ee0989c13&quot;&gt;JIT
compiler&lt;/a&gt;,
the &lt;a href=&quot;https://rubyapi.org/3.0/o/ractor&quot;&gt;Ractor&lt;/a&gt; API for achieving parallelism,
and the &lt;a href=&quot;https://rubyapi.org/3.0/o/fiber/schedulerinterface&quot;&gt;Fiber scheduler&lt;/a&gt;
interface aimed at improving concurrency for I/O bound applications.&lt;/p&gt;

&lt;p&gt;While those three big developments have yet to prove themselves in real-life
Ruby apps, they represent great opportunities for improving the performance of
Ruby-based apps. The next few years will tell if any of those new technologies
will deliver on its promise for Ruby developers.&lt;/p&gt;

&lt;p&gt;While Ruby developers (especially those working in and around Ruby on Rails) are
still looking for the holy grail of Ruby fastness, I&amp;#8217;d like to explore a
different way of thinking about developing Ruby apps (and gems) that can be
employed to achieve optimal performance.&lt;/p&gt;

&lt;h2 id=&quot;ruby-makes-developers-happy-until-it-comes-to-performance&quot;&gt;Ruby Makes Developers Happy, Until it Comes to Performance&amp;#8230;&lt;/h2&gt;

&lt;p&gt;Why do I love programming in Ruby? First of all, because it&amp;#8217;s optimized for
developer happiness. Ruby is famous for allowing you to express your ideas in
any number of ways. You can write functional programs, or go all in on Java-like
enterprise OOP patterns. You can build DSLs, and you can replace or extend whole
chunks of the Ruby core functionality by redefining core class methods. And
metaprogramming in Ruby lets you do nuclear stuff!&lt;/p&gt;

&lt;p&gt;All this comes, of course, with a price tag in the form of reduced performance
when compared to other, less magical, programming languages, such as Go, C or
C++. But experienced Ruby developers will have already learned how to get the
most &amp;#8220;bang for the buck&amp;#8221; out of Ruby, by carefully designing their code so as to
minimize object allocations (and subsequent GC cycles), and picking core Ruby
and Stdlib APIs that provide better performance.&lt;/p&gt;

&lt;p&gt;Another significant way Ruby developers have been dealing with problematic
performance is by using Ruby C-extensions, which implement specific
functionalities in native compiled code that can be invoked from plain Ruby
code.&lt;/p&gt;

&lt;h2 id=&quot;compositional-programming-in-ruby&quot;&gt;Compositional Programming in Ruby&lt;/h2&gt;

&lt;p&gt;It has occurred to me during my work on
&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt;, a concurrency library
for Ruby, that a C-extension API can be designed in such a way as to provide a
small, task-specific execution layer for small programs composed of multiple
steps that can be exspressed as data structures using plain Ruby objects. Let me
explain using an example.&lt;/p&gt;

&lt;p&gt;Let&amp;#8217;s say we are implementing an HTTP server, and we would like to implement
sending a large response using &lt;a href=&quot;https://en.wikipedia.org/wiki/Chunked_transfer_encoding&quot;&gt;chunked
encoding&lt;/a&gt;. Here&amp;#8217;s how
we can do this in Ruby:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_chunked_encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytesize&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytesize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chunk&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# send empty chunk&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@socket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is pretty short and sweet, but look how we&amp;#8217;re allocating a string for each
chunk and doing index arythmetic in Ruby. This kind of code surely could be made
more efficient by reimplementing it as a C-extension. But if we already go to
the trouble of writing a C-extension, we might want to generalize this approach,
so we might be able to implement sending chunked data over other protocols as
well.&lt;/p&gt;

&lt;p&gt;What if we could come up with a method implemented in C, that takes a
&lt;em&gt;description&lt;/em&gt; of what we&amp;#8217;re trying to do? Suppose we have a method with the
following interface:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_data_in_chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_head&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_tail&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We could then implement HTTP/1 chunked encoding by doing the following:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_chunked_encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send_data_in_chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# chunk size + CRLF&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;                             &lt;span class=&quot;c1&quot;&gt;# trailing CRLF&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the &lt;code&gt;#send_data_in_chunks&lt;/code&gt; method is implemented in C, this means that Ruby
code is not involved at all in the actual sending of the data. The C-extension
code is responsible for looping and writing the data to the socket, and the Ruby
code just provides instructions for what to send before and after each chunk.&lt;/p&gt;

&lt;h2 id=&quot;polyphonys-chunked-splicing-api&quot;&gt;Polyphony&amp;#8217;s chunked splicing API&lt;/h2&gt;

&lt;p&gt;The above approach is actually how static file responses are generated in
&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt;, the web server for Ruby I&amp;#8217;m
currently developing. One of Tipi&amp;#8217;s distinguishing features is that it can send
large files without ever loading them into memory, by using Polyphony&amp;#8217;s
&lt;code&gt;Backend#splice_chunks&lt;/code&gt; API (Polyphony emulates splicing on non-Linux OSes).
Here&amp;#8217;s an excerpt from Tipi&amp;#8217;s HTTP/1 adapter code:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tx_incr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytesize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice_chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Backend#splice_chunks&lt;/code&gt; method is slightly more sophisticated than the
previous example, as it also takes a string to send &lt;em&gt;before all chunks&lt;/em&gt; (here
it&amp;#8217;s the HTTP headers), and a string to send &lt;em&gt;after all chunks&lt;/em&gt; (the empty chunk
string &lt;code&gt;&quot;0\r\n\r\n&quot;&lt;/code&gt;). My &lt;a href=&quot;/articles/2021-06-25-polyphony-june-edition#a-new-api-for-splicing-tofrom-pipes&quot;&gt;non-scientific
benchmarks&lt;/a&gt;
have shown speed gains of up to 64% for multi-megabyte HTTP responses!&lt;/p&gt;

&lt;p&gt;The main idea behind the &lt;code&gt;#splice_chunks&lt;/code&gt; API is that the application provides a
&lt;em&gt;plan&lt;/em&gt;, or a &lt;em&gt;program&lt;/em&gt; for what to do, and the underlying system &amp;#8220;runs&amp;#8221; that
program.&lt;/p&gt;

&lt;h2 id=&quot;chaining-multiple-io-operations-in-a-single-ruby-method-call&quot;&gt;Chaining multiple I/O operations in a single Ruby method call&lt;/h2&gt;

&lt;p&gt;A similar approach was also used to implement chaining of multiple I/O
operations, a feature particularly useful when running on recent Linux kernels
with io_uring (Polyphony automatically uses io_uring starting from Linux version
5.6.) Here again, the same idea is employed - the application provides a
&amp;#8220;program&amp;#8221; expressed using plain Ruby objects. Here&amp;#8217;s how chunked transfer
encoding can be implemented using &lt;code&gt;Backend#chain&lt;/code&gt; (when splicing a single chunk
from an IO instance):&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;send_chunk_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;#8217;s take a closer look at the call to &lt;code&gt;#chain&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Backend#chain&lt;/code&gt; API takes one or more Ruby arrays each an I/O operation. The
currently supported operations are &lt;code&gt;:write&lt;/code&gt;, &lt;code&gt;:send&lt;/code&gt; and &lt;code&gt;:splice&lt;/code&gt;. For each
operation we provide the operation type followed by its arguments. The most
interesting aspect of this API is that it allows us to reap the full benefits of
using io_uring, as the given operations are
&lt;a href=&quot;https://unixism.net/loti/tutorial/link_liburing.html&quot;&gt;linked&lt;/a&gt; so that they will
be performed by the kernel one after the other without the Ruby code ever being
involved! The &lt;code&gt;#chain&lt;/code&gt; method will return control to the Ruby layer once all
operations have been performed by the kernel.&lt;/p&gt;

&lt;h2 id=&quot;designing-compositional-apis&quot;&gt;Designing Compositional APIs&lt;/h2&gt;

&lt;p&gt;This approach to API design might be called compositional APIs - the idea here
is that the API provides a way to &lt;em&gt;compose&lt;/em&gt; multiple tasks or operations by
describing them using native data structures.&lt;/p&gt;

&lt;p&gt;Interestingly enough, io_uring itself takes this approach: you describe I/O
operations using &lt;a href=&quot;https://unixism.net/loti/ref-liburing/submission.html&quot;&gt;SQEs (submission queue
entries)&lt;/a&gt;, which are
nothing more than C data structures conforming to a standard interface. In
addition, as mentioned above, with io_uring you can chain multiple operations to
be performed one after another.&lt;/p&gt;

&lt;p&gt;Future plans for io_uring include making it possible to submit
&lt;a href=&quot;https://en.wikipedia.org/wiki/Berkeley_Packet_Filter&quot;&gt;eBPF&lt;/a&gt; programs for
running arbitrary eBPF code kernel side. That way, we might be able to implement
chunked encoding in eBPF code, and submit it to the kernel using io_uring.&lt;/p&gt;

&lt;h2 id=&quot;a-more-general-approach-to-chaining-io-operations&quot;&gt;A More General Approach to Chaining I/O operations&lt;/h2&gt;

&lt;p&gt;It has recently occurred to me that the compositional approach to designing APIs
can be further enhanced and generalized, for example by providing the ability to
express flow control. Here&amp;#8217;s how the chunk splicing functionality might be
expressed using such an API:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:break_if_ret_eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:store_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# store the return code in @len&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# use stored @len value&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now there are clearly a few problems here: this kind of API can quickly run into
the problem of Turing-completeness - will developers be able to express &lt;em&gt;any&lt;/em&gt;
kind of program using this API? Where are the boundaries and how do we define them?&lt;/p&gt;

&lt;p&gt;Also, how can we avoid having to allocate all those arrays every time we call
the &lt;code&gt;#respond_from_io&lt;/code&gt; method? All those allocations can put more pressure on
the Ruby GC, and themselves can be costly in terms of performance. And that proc
we provide - it&amp;#8217;s still Ruby code that needs to be called for every iteration of
the loop. That too can be costly to performance.&lt;/p&gt;

&lt;p&gt;The answers to all those questions are still not clear to me, but one solution I
thought about was to provide a &amp;#8220;library&amp;#8221; of operation types that is a bit
higher-level than a simple write or splice. For example, we can come up with an
operation to write the chunk header, which can look something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:break_if_ret_eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:store_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write_cte_chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;adding-io-references&quot;&gt;Adding IO References&lt;/h2&gt;

&lt;p&gt;Another improvement we can make is to provide a way to reference io instances
and dynamic strings from our &lt;code&gt;respond_from_io&lt;/code&gt; &amp;#8220;program&amp;#8221; using indexes. This
will allow us to avoid allocating all those arrays on each invocation:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# program references:&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 0 - headers&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 1 - io&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 2 - @conn&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 3 - pipe r&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 4 - pipe w&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# 5 - chunk_size&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RESPOND_FROM_IO_PROGRAM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:break_if_ret_eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:store_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write_cte_chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;RESPOND_FROM_IO_PROGRAM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;creating-io-programs-using-a-dsl&quot;&gt;Creating IO Programs Using a DSL&lt;/h2&gt;

&lt;p&gt;Eventually, we could provide a way for developers to express IO programs with a
DSL, instead of with arrays. We could then also use symbols for representing IO indexes:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RESPOND_FROM_IO_PROGRAM&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;io_program&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;ss&quot;&gt;:headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pipe_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pipe_w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:chunk_size&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:headers&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;io_loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;splice&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pipe_w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:chunk_size&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;break_if_ret_eq&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;store_ret&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;write_cte_chunk_size&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;splice&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:pipe_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:len&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Does this look better? I&amp;#8217;m not sure. Anyways, there are some rough edges here
that will need to be smoothed out for this approach to work.&lt;/p&gt;

&lt;h2 id=&quot;implementing-a-protocol-parser-using-the-compositional-approach&quot;&gt;Implementing a Protocol Parser Using the Compositional Approach&lt;/h2&gt;

&lt;p&gt;It as occurred to me that this kind of approach, expressing a &amp;#8220;program&amp;#8221; using
plain Ruby objects, to be executed by a C-extension, could also be applied to
protocol parsing. I&amp;#8217;ve recently released a blocking HTTP/1 parser for Ruby,
called &lt;a href=&quot;https://github.com/digital-fabric/h1p&quot;&gt;h1p&lt;/a&gt;, implemented as a Ruby C
extension, and I had some ideas about how this could be done.&lt;/p&gt;

&lt;p&gt;We introduce a &lt;code&gt;IO#parse&lt;/code&gt; method that accepts a program for parsing
characters. The program expressed includes a set of steps, each one reading
consecutive characters from the IO instance:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# for each part of the line we can express the valid range of lengths,&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;REQUEST_LINE_RULES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;delimiter: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;invalid: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:consume_whitespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;delimiter: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;invalid: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:consume_whitespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_to_eol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_eol: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;HEADER_RULES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_or_eol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;delimiter: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;:&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:return_if_nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:consume_whitespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:read_to_eol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_eol: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parse_http1_headers&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;REQUEST_LINE_RULES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&#39;:method&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&#39;:path&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&#39;:protocol&#39;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protocol&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HEADER_RULES&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here too, we can imagine being able to express these parsing rules using a DSL:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;REQUEST_LINE_RULES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_program&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;delimiter: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;invalid: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;consume_whitespace&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;delimiter: &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;invalid: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_delimiter: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;consume_whitespace&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;read_to_eol&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;consume_eol: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;length: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It remains to be seen where are the limits to what we can achieve with this
approach: can we really express everything that we need in order to parse &lt;em&gt;any&lt;/em&gt;
conceivable protocol. In addition, it is not clear whether this kind of solution
provides performance benefits.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;In this article I have presented an approach to optimizing the performance of
Ruby apps by separating the program into two layers: a top layer that written in
Ruby, expressing low-level operations using Ruby data structures; and an
implementation layer written in C for executing those operations in an optimized
manner. This approach is particularly interesting when dealing with long running
or complex operations: sending an HTTP response with chunked encoding, parsing
incoming data, running I/O operations in loops etc.&lt;/p&gt;

&lt;p&gt;As I have mentioned above, this this is similar to that employed by io_uring
on Linux. The idea is the same: we express (I/O) operations using data
structures, then offload the execution to an lower-level optimized layer - in
io_uring&amp;#8217;s case it&amp;#8217;s the kernel, in Ruby&amp;#8217;s case it&amp;#8217;s a C-extension.&lt;/p&gt;

&lt;p&gt;It seems to me that&lt;/p&gt;

&lt;p&gt;This is definitely an avenue I intend on further exploring, and I invite other
Ruby developers to join me in this exploration. While we wait for all those
exciting Ruby developments I mentioned at the beginning of this article to
materialize (the new &lt;a href=&quot;https://github.com/Shopify/yjit&quot;&gt;YJIT&lt;/a&gt; effort from Shopify
looks especially promising), we can investigate other approaches that take
advantage of Ruby&amp;#8217;s expressivity while relying on native C code to execute lower
level code.&lt;/p&gt;
</description></item><item><title>How I Write Code: Pen &amp; Paper</title><link>https://noteflakes.com/articles/2021-09-02-how-i-write-code-pen-paper</link><guid>https://noteflakes.com/articles/2021-09-02-how-i-write-code-pen-paper</guid><pubDate>Thu, 02 Sep 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;I am a self taught programmer. I first started programming as a kid, and have
never bothered to formally study this discipline. To me, programming is first of
all a pleasant creative pursuit. It&amp;#8217;s a bit like putting together and taking
apart all kinds of machines, except these machines happen to be &lt;em&gt;virtual&lt;/em&gt;
constructions, and you get to keep your hands clean (well, mostly&amp;#8230;)
Incidentally, this pleasant activity is also how I support my family.&lt;/p&gt;

&lt;p&gt;But even though I love programming, I try not to sit in front of a computer
screen too much. I do not find staring at a screen all day beneficial, not for
my physical health, nor for my mental health. In the last few years, I&amp;#8217;ve
started a habit of sketching my programming ideas using pen and paper. I&amp;#8217;m not
talking here about todo lists, or making diagrams. I&amp;#8217;m talking about actually
writing code using pen and paper. Let me explain.&lt;/p&gt;

&lt;h3 id=&quot;why-pen--paper&quot;&gt;Why pen &amp;amp; paper&lt;/h3&gt;

&lt;p&gt;A lot has been written about the &lt;a href=&quot;https://www.theguardian.com/science/2014/dec/16/cognitive-benefits-handwriting-decline-typing&quot;&gt;advantages of handwriting vs
typing&lt;/a&gt;.
I will not enumerate all of them here, but I will tell you that since I started
this practice I find I&amp;#8217;m more productive, and I seem to produce better-quality
code.&lt;/p&gt;

&lt;p&gt;The mere fact that I can concentrate on a single problem without any
distractions is already a big advantage, and it seems to me that through writing
pages upon pages with a pen, scratching bad ideas, rewriting small bits of code,
I gain (as if by magic) a deeper understanding of my code.&lt;/p&gt;

&lt;h3 id=&quot;what-about-debugging-what-about-testing&quot;&gt;What about debugging? What about testing?&lt;/h3&gt;

&lt;p&gt;When you write code on paper, you have no way to see if your code works. Maybe
someday I&amp;#8217;ll be able to handwrite code on an e-Ink device and then run it. Until
that day, all that I have is my ideas, my intuition, my knowledge, and my
&amp;#8220;mental runtime&amp;#8221; - a mental thought process that simulates some kind of computer
runtime that goes through the code, evaluating it.&lt;/p&gt;

&lt;p&gt;Of course, my mental process is not perfect. It will let slip through all kinds
of bugs, things I&amp;#8217;m not smart enough to detect before actually feeding the code
to a computer. That&amp;#8217;s why I try to concentrate on one small problem at a time.
Nothing bigger than a page or two, and mostly short snippets that implement a
specific algorithm.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/pen-and-paper-1.jpg&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;iterate-iterate-iterate&quot;&gt;Iterate, iterate, iterate&lt;/h3&gt;

&lt;p&gt;So once I start exploring a problem space with pen &amp;amp; paper, I just iterate on it
until I feel I&amp;#8217;ve found the best solution to the problem. Once I&amp;#8217;ve achieved
that, I can finally open my laptop and type the code into my favorite editor.&lt;/p&gt;

&lt;p&gt;Sometimes it works like magic, I just type in the code and everything works.
Sometimes it needs some additional effort in editing and elaborating. But still,
even if my handwritten code turned out to be only partially correct, or needed
some reworking for it to fit in with other components, I feel like spending time
sketching code and reflecting on it before turning to the computer has given me
a deeper understanding of the code.&lt;/p&gt;

&lt;h3 id=&quot;a-recent-example&quot;&gt;A recent example&lt;/h3&gt;

&lt;p&gt;Here&amp;#8217;s an example of coding I did completely with pen &amp;amp; paper: a few days ago I
published a new open-source project called
&lt;a href=&quot;https://github.com/digital-fabric/ever&quot;&gt;Ever&lt;/a&gt; - a libev-based event reactor for
Ruby. The entire design was done with pen &amp;amp; paper. I did it over three evenings,
taking a couple hours each evening to work through the design and different
aspects of the implementation.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/pen-and-paper-2.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, when I felt the design was solid and that I had resolved all the issues
I could see, I opened my laptop, created a new Github repository, typed in the
code, added some tests and wrote the README. It was probably all done in 4 or 5
hours of concentrated work.&lt;/p&gt;

&lt;p&gt;At the end of the process, in addition to being able to create a small Ruby gem
in a single day without too much fussing around looking for solutions to
problems encountered during development, I feel like I have a deeper knowledge
of the code, a deeper understanding of the implications of different choices,
and a greater appreciation for writing the least amount of code.&lt;/p&gt;

&lt;p&gt;When I hold a pen in my hand and set it to paper, I find it much easier to be
&amp;#8220;in the zone&amp;#8221;, and I feel much closer to the ideas I&amp;#8217;m exploring. Somehow, I
never get this feeling of &lt;em&gt;connectedness&lt;/em&gt; when laying my hands on a computer
keyboard. It also empowers me to know that I don&amp;#8217;t need a computer in order to
create code. I&amp;#8217;m not dependent in my creative pursuits on a machine that
sometimes seems to disturb my creative process rather than facilitate it.&lt;/p&gt;

&lt;p&gt;Steve Jobs once talked about computers being like a &lt;a href=&quot;https://www.youtube.com/watch?v=ob_GX50Za6c&quot;&gt;bicycle for our
minds&lt;/a&gt;. To me it feels like a lot
of times we expend lots of energy on
&lt;a href=&quot;https://en.wiktionary.org/wiki/bikeshedding&quot;&gt;bikeshedding&lt;/a&gt; rather than actually
riding our bikes. And maybe we should also get off our bikes every once in a
while, and just
&lt;a href=&quot;https://lithub.com/on-the-link-between-great-thinking-and-obsessive-walking/&quot;&gt;walk&lt;/a&gt;.&lt;/p&gt;
</description></item><item><title>What&#39;s new in Polyphony and Tipi - August 2021 edition</title><link>https://noteflakes.com/articles/2021-08-26-polyphony-august-edition</link><guid>https://noteflakes.com/articles/2021-08-26-polyphony-august-edition</guid><pubDate>Thu, 26 Aug 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;The summer is drawing to an end, and with it I bring another edition of Polyphony (and Tipi) news, this time on my own website, where I&amp;#8217;ll be publishing periodically from now on.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt; is a library for writing highly concurrent Ruby apps. Polyphony harnesses Ruby fibers and a powerful &lt;a href=&quot;https://unixism.net/loti/what_is_io_uring.html&quot;&gt;io_uring&lt;/a&gt;-based I/O runtime to provide a solid foundation for building high-performance concurrent Ruby apps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt; is a new Polyphony-based web server for Ruby. Tipi provides out of the box support for HTTP/1, HTTP/2, and WebSocket. Tipi also provides SSL termination (support for HTTPS) with &lt;strong&gt;automatic certificate provisioning&lt;/strong&gt; and automatic ALPN protocol selection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;from-counterpoint-to-composition&quot;&gt;From counterpoint to composition&lt;/h2&gt;

&lt;p&gt;In the last month I&amp;#8217;ve been doing a lot more work on &lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt; than on Polyphony, and most of my work on Polyphony has been just fixing bugs. For me this is a major milestone, as I&amp;#8217;m transitioning from working on the low-level stuff, to an actual application that can do something useful. To me this feels a bit like transitioning from writing &lt;a href=&quot;https://en.wikipedia.org/wiki/Counterpoint&quot;&gt;counterpoint&lt;/a&gt; exercises to composing an actual piece of music.&lt;/p&gt;

&lt;p&gt;The Polyphony API is maturing nicely and I hope to be able to make a 1.0 release in the coming weeks. As for Tipi, there&amp;#8217;s still a lot of work to do in order for it to be ready for public release, and I&amp;#8217;ll discuss that towards the end of this post.&lt;/p&gt;

&lt;h2 id=&quot;changes-in-polyphony&quot;&gt;Changes in Polyphony&lt;/h2&gt;

&lt;h3 id=&quot;support-for-splicing-on-non-linux-platforms&quot;&gt;Support for splicing on non-Linux platforms&lt;/h3&gt;

&lt;p&gt;The &lt;a href=&quot;http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod&quot;&gt;libev&lt;/a&gt; backend now supports all splicing APIs by emulating the Linux &lt;code&gt;splice&lt;/code&gt; system call. I&amp;#8217;ve &lt;a href=&quot;/articles/2021-06-25-polyphony-june-edition#a-new-api-for-splicing-tofrom-pipes&quot;&gt;already written&lt;/a&gt; about splicing and the amazing things that can be done with these APIs. So now they can be used on cross-platform, even if the performance gains are only achievable on Linux.&lt;/p&gt;

&lt;h3 id=&quot;fiber-supervision&quot;&gt;Fiber supervision&lt;/h3&gt;

&lt;p&gt;One of the major advantages of using Polyphony as the basis for concurrent programs is that it implements &lt;a href=&quot;https://en.wikipedia.org/wiki/Structured_concurrency&quot;&gt;structured concurrency&lt;/a&gt;, a programming paradigm that makes it easier to control fiber execution in a highly-concurrent environment. Just imagine writing a program that performs thousands of long-running tasks concurrently. How do you manage that complexity? How do you deal with failures? How can you control any of those concurrent tasks?&lt;/p&gt;

&lt;p&gt;Polyphony deals with this problem by adhering to three principles:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fibers are arranged in a hierarchy: a fiber is considered the child of the fiber from which it was spun.&lt;/li&gt;
  &lt;li&gt;A fiber&amp;#8217;s lifetime is limited to that of its immediate parent. In other words, a fiber is guaranteed to terminate before its parent does.&lt;/li&gt;
  &lt;li&gt;Any uncaught exception raised in a fiber will &amp;#8220;bubble up&amp;#8221; to its immediate parent, and potentially all the way up to the main fiber (which will cause the program to terminate with an exception, if not handled.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here&amp;#8217;s an example to demonstrate these three principles in action:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Kernel#spin starts a new fiber&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# Each fiber has a mailbox for receiving messages&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# The result is sent back to the &quot;client&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The controller fiber will block until the worker is done (but notice that&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# the worker runs an infinite loop.)&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Send the job to the worker fiber...&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ... and wait for the result&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# wait for the controller to terminate&lt;/span&gt;
&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, we spin a &lt;em&gt;controller&lt;/em&gt; fiber, which then spins a &lt;em&gt;worker&lt;/em&gt; fiber. This creates the following hierarchy:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;main
 |
 +- controller
      |
      +-- worker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now we can just call &lt;code&gt;#calc&lt;/code&gt; to perform calculations inside the worker:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# from the main fiber&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 5&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# or from another fiber:&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:**&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But notice what happens when we send an operation that results in an exception:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;Traceback (most recent call last):
        5: from examples/core/calc.rb:7:in `&amp;lt;main&amp;gt;&#39;
        4: from examples/core/calc.rb:8:in `block in &amp;lt;main&amp;gt;&#39;
        3: from examples/core/calc.rb:9:in `block (2 levels) in &amp;lt;main&amp;gt;&#39;
        2: from examples/core/calc.rb:9:in `loop&#39;
        1: from examples/core/calc.rb:12:in `block (3 levels) in &amp;lt;main&amp;gt;&#39;
examples/core/calc.rb:12:in `+&#39;: nil can&#39;t be coerced into Integer (TypeError)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Actually, the exception that was raised inside of the &lt;em&gt;worker&lt;/em&gt; fiber, has bubbled up to the &lt;em&gt;controller&lt;/em&gt; fiber. The controller, which was busy waiting for the worker to terminate, has re-raised the exception, which bubbled up to the main fiber. The main fiber, which was waiting for the controller to terminate, has re-raised the exception and has finally exited with an error message and a back trace (you can find the full example &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/examples/core/calc.rb&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The fact that unrescued exceptions bubble up through the fiber hierarchy allow us to control the lifetime of child fibers. Here&amp;#8217;s one way we can deal with uncaught exceptions in the worker fiber:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Uncaught exception in worker: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. Restarting...&quot;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Yes, in Polyphony fibers can be restarted!&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restart&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since the controller fiber can intercept any unrescued exception that occurred in its child, we add a rescue block, report the error and then restart the fiber.&lt;/p&gt;

&lt;p&gt;Another possibility would be to handle the error at the level of the main fiber, or maybe to handle it locally if it&amp;#8217;s only about trivial errors, and let more serious exceptions bubble up - it really depends upon the circumstances. The point is that Polyphony allows us to control the lifetime of any fiber anywhere in the fiber hierarchy with a small set of tools that builds on the rubustness of Ruby exceptions: putting &lt;code&gt;rescue&lt;/code&gt; and &lt;code&gt;ensure&lt;/code&gt; blocks in the right places will already do 99% of the work for us.&lt;/p&gt;

&lt;p&gt;But what if we want to automate the handling of errors? What if we just want things to continue working without us needing to manually write &lt;code&gt;rescue&lt;/code&gt; blocks? Enter fiber supervision.&lt;/p&gt;

&lt;h3 id=&quot;inspired-by-erlang&quot;&gt;Inspired by Erlang&lt;/h3&gt;

&lt;p&gt;The new fiber supervision mechanism in Polyphony is greatly inspired by Erlang &lt;a href=&quot;https://adoptingerlang.org/docs/development/supervision_trees/&quot;&gt;supervision trees&lt;/a&gt;. While Erlang processes are not organised hierarchically, Erlang provides a &lt;a href=&quot;http://erlang.org/doc/design_principles/sup_princ.html&quot;&gt;supervisor behaviour&lt;/a&gt; that allows expressing hierarchical dependencies between processes.&lt;/p&gt;

&lt;p&gt;While a lot of the functionality of Erlang supervision trees is already included in Polyphony by virtue of the structured concurrency paradigm, the Erlang supervisor behaviour allows automating the handling of error conditions. This is what I set to solve in the new fiber supervision API.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;Kernel#supervise&lt;/code&gt; method can be used to supervise one or more fibers. By default, it does nothing more than just waiting for all supervised fibers to terminate. But it can also be used to automatically restart fibers once they have terminated, or restart them only when an exception occurred, or to perform other work when a fiber is terminated (for example, writing to a log file).&lt;/p&gt;

&lt;p&gt;Going back to our example, here&amp;#8217;s how we can use the controller fiber to supervise the worker fiber:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;restart: :always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The call to &lt;code&gt;Kernel#supervise&lt;/code&gt; tells the controller fiber to monitor the worker fiber and to restart it always once it terminates, ignoring any exceptions. Alternatively, we can tell the controller to restart the worker only when an exception occurs:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;restart: :on_error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can also define a custom behavior by passing a block that will be called when the worker fiber terminates:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;log_exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restart&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;staying-in-the-loop-the-advantages-of-fiber-supervision&quot;&gt;Staying in the loop: the advantages of fiber supervision&lt;/h2&gt;

&lt;p&gt;In my work on Polyphony and on Tipi I have discovered a few programming patterns that I find very interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;When using Polyphony you write a lot of loops. A good bunch of those are infinite loops! Take for example the worker fiber above.&lt;/li&gt;
  &lt;li&gt;When developing a concurrent app using Polyphony, any uncaught exception might cause the entire process to terminate, since Polyphony never allows exceptions to be lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we look at Tipi, a Polyphony app that can be used to serve HTTP/S on multiple ports, we&amp;#8217;ll have a separate fiber listening for incoming connections on each port. When a connection is accepted, we spin a new fiber in order to handle the new connection concurrently:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;http_listener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;https_listener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;https_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since the client handling fibers are spun from the listener fibers (either &lt;code&gt;http_listener&lt;/code&gt; or &lt;code&gt;https_listener&lt;/code&gt;), they are considered the children of those fibers. If any exception is raised in a client handling fiber and is not rescued, it &lt;em&gt;will&lt;/em&gt; bubble up to the listener fiber and will cause it to terminate with the exception.&lt;/p&gt;

&lt;p&gt;In addition, the listeners themselves might raise exception when accepting connections - these can be system call errors, I/O errors, OpenSSL errors (for the HTTPS listener) etc. We&amp;#8217;d like an easy way to catch these errors. One way would be to just do this with a &lt;code&gt;rescue&lt;/code&gt; block:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;https_listener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;https_server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rescue&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;HTTPS accept error: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a possibility, but we need to do it manually for each fiber, and we risk adding a lot of rescue blocks (some of them can even be for a specific class of exception) everywhere, an error-prone methodology that can prove problematic if overdone.&lt;/p&gt;

&lt;p&gt;Instead, we can use the &lt;code&gt;Kernel#supervise&lt;/code&gt; API provided by Polyphony to make sure our infinite loops (i.e. our listener fibers) continue running, even when an exception occurs. Thus we can embrace the Erlang moto: &amp;#8220;Let it crash.&amp;#8221; We let it crash, and then we restart it. Here&amp;#8217;s how we can employ this using fiber superivision:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;http_listener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;https_listener&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# If specific fibers are not specified, #supervise will supervise all of the&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# current fiber&#39;s children.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fiber &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; terminated with exception: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;. Restarting...&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restart&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this way we ensure that any &lt;em&gt;uncaught&lt;/em&gt; exception from one of the listeners or their children will not slip through and stop the server from functioning. Any listener that has stopped because of an exception will just be restarted. And applying this to our controller example above:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;kp&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;peer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;restart: :always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Send the job to the worker fiber...&lt;/span&gt;
  &lt;span class=&quot;vi&quot;&gt;@worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ... and wait for the result&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;supervise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;restart: :always&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;bug-fixes-and-other-changes&quot;&gt;Bug fixes and other changes&lt;/h3&gt;

&lt;p&gt;Here&amp;#8217;s a list of other, smaller changes and fixes in Polyphony:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Prevent possible segfault in the io_uring backend by GC marking read/write buffers &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/c6a842f281ff3649d678300dc58883dbe32a6373&quot;&gt;when cancelling an I/O operation&lt;/a&gt;. When cancelling an IO operation, the Ruby process might have already moved on while the kernel is still accessing the associated buffers before finally cancelling the operation. In order to prevent a possible segfault in case a GC cycle kicks in immediately after cancellation, I have introduced a mechanism to GC mark the buffers used until the operation has been cancelled in the kernel as well.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/ebb64e7b3e5a1e32deaec8a8f3d6e8aa8366c7ca&quot;&gt;Improve fiber monitoring&lt;/a&gt;: in preparation for work on fiber supervision, the &lt;code&gt;Fiber#monitor&lt;/code&gt; implementation has undergone a lot of simplification and made much more robust.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Fiber#attach&lt;/code&gt; was &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/406619347e7957d3b0077626589f1c6e131ddb94&quot;&gt;renamed&lt;/a&gt; to &lt;code&gt;Fiber#attach_to&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Fixed &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/0aaaf7fd663472d539b08612ab1edeeb3079bb91&quot;&gt;linking of operations&lt;/a&gt; in &lt;code&gt;Backend#chain&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Fixed &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/722ad0170f2afe776d07e93ebcb4d26b4b17804c&quot;&gt;missing default value&lt;/a&gt; in &lt;code&gt;#readpartial&lt;/code&gt; for socket classes.&lt;/li&gt;
  &lt;li&gt;Fixed &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/029074ad5a3d2a76caec322aebe216b6c88ff720&quot;&gt;removing child fiber&lt;/a&gt; from parent&amp;#8217;s children list when terminated.&lt;/li&gt;
  &lt;li&gt;Reset backend runqueue and other state &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/e7d91451ae871dca6029bceb2a6933d16e6b6c27&quot;&gt;after forking&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Fix &lt;a href=&quot;https://github.com/digital-fabric/polyphony/commit/da2cbdce4e8f462585fd5ff86ae29cc67306c6e5&quot;&gt;compilation on Ruby 3.0&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;changes-in-tipi&quot;&gt;Changes in Tipi&lt;/h2&gt;

&lt;p&gt;The Tipi server is progressing nicely. I&amp;#8217;ve been running it in production over the last few months, and while it&amp;#8217;s still a long way from providing a stable, easy-to-use API for other developers, in terms of features and functionality it&amp;#8217;s already got 90% of the features expected from a modern web server: support for HTTP/1 &lt;em&gt;and&lt;/em&gt; HTTP/2, SSL termination, support for WebSocket and streaming responses, support for serving static files and of course running Rack apps. Tipi is also able to dynamically provision SSL certificates using an &lt;a href=&quot;https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment&quot;&gt;ACME&lt;/a&gt; provider (such as &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let&amp;#8217;s Encrypt&lt;/a&gt;), though this feature is still work in progress.&lt;/p&gt;

&lt;p&gt;Following is a summary of the big changes in Tipi this month.&lt;/p&gt;

&lt;h3 id=&quot;h1p---a-new-http1-parser&quot;&gt;H1P - a new HTTP/1 parser&lt;/h3&gt;

&lt;p&gt;I&amp;#8217;ve hinted before about writing an HTTP/1 parser made for Tipi. Well the work is more or less done, and I&amp;#8217;ve released the parser as a separate project called &lt;a href=&quot;https://github.com/digital-fabric/h1p&quot;&gt;H1P&lt;/a&gt;. What sets this parser apart is the fact that it is completely blocking. While other parsers (at least the ones I know of) provide a callback-based API, where you register callbacks for different events, and then feed the parser with data and wait for those callbacks to be invoked, by contrast H1P provides a blocking API that&amp;#8217;s much easier to use:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H1P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_headers&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_body&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;handle_request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yes, that&amp;#8217;s it (for the most part). And, the beauty of this parser is that you don&amp;#8217;t even need Polyphony in order to use it. In fact you can use it in a &amp;#8220;normal&amp;#8221; threaded server (spawning a thread for each connection), and you can use it in conjunction with the new &lt;a href=&quot;https://rubyapi.org/3.0/o/fiber/schedulerinterface&quot;&gt;fiber scheduler&lt;/a&gt; introduced in Ruby 3.0.&lt;/p&gt;

&lt;p&gt;The H1P parser is implemented in &lt;a href=&quot;https://github.com/digital-fabric/h1p/blob/main/ext/h1p/h1p.c&quot;&gt;less than 750 lines of C&lt;/a&gt;, has zero dependencies and supports chunked encoding and &lt;code&gt;LF&lt;/code&gt;/&lt;code&gt;CRLF&lt;/code&gt; line breaks, has &lt;a href=&quot;https://github.com/digital-fabric/h1p/blob/main/ext/h1p/limits.rb&quot;&gt;hard limits&lt;/a&gt; on token length for minimizing server abuse, and is transport agnostic - you can have it read from any source, even sources that are not IO objects:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;GET &#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;/foo&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; HTTP/1.1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;H1P&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;shift&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;parser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse_headers&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#=&amp;gt; { &#39;:method&#39; =&amp;gt; &#39;GET&#39;, &#39;:path&#39; =&amp;gt; &#39;/foo&#39;, ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I intend to keep on working on H1P, notably on the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add conformance and security tests.&lt;/li&gt;
  &lt;li&gt;Add ability to parse HTTP/1 responses (for implementing HTTP clients.)&lt;/li&gt;
  &lt;li&gt;Add ability to splice the request body into an arbitrary fd (Polyphony-specific.)&lt;/li&gt;
  &lt;li&gt;Improve performance. In a synthetic benchmark, H1P is ~15% slower than the callback-based &lt;a href=&quot;https://github.com/tmm1/http_parser.rb&quot;&gt;http_parser.rb&lt;/a&gt; gem, which uses the &lt;em&gt;old&lt;/em&gt; node.js HTTP/1 parser. In actual use in Tipi, I&amp;#8217;ve seen the throughput actually improve with H1P, and I think this is attributable to the fact that when converting from a callback-based API to a blocking API (as I do in Tipi) there&amp;#8217;s quite a bit of overhead involved in buffering the requests, and all the more so when needing to deal with &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_pipelining&quot;&gt;HTTP pipelining&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to that, I also plan to implement a similar H2P project for handling HTTP/2 connections.&lt;/p&gt;

&lt;h3 id=&quot;automatic-ssl-certificate-provisioning&quot;&gt;Automatic SSL certificate provisioning&lt;/h3&gt;

&lt;p&gt;If there&amp;#8217;s one feature that can be a game changer for a Ruby web server, it&amp;#8217;s automatic SSL certificate provisioning. Tipi already does SSL termination, and that makes it possible to use Tipi without any load balancer or reverse proxy in front of it, since it can deal with incoming HTTPS connections all by itself. But automatic SSL certificates take this autonomy to the next level: you don&amp;#8217;t even have to provide a certificate for Tipi to use. Tipi will just take care of it all by itself, by dynamically provisioning a certificate from an &lt;a href=&quot;https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment&quot;&gt;ACME&lt;/a&gt; provider, such as &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let&amp;#8217;s Encrypt&lt;/a&gt; or &lt;a href=&quot;https://zerossl.com/&quot;&gt;ZeroSSL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Imagine not having to set up Nginx, Apache or Caddy as a reverse proxy in order to run your web app. You just run Tipi (preferably with port-forwarding, so you don&amp;#8217;t need to deal with binding to privileged ports) and point it at your Rack app. This is what I&amp;#8217;m aiming to achieve in the near future.&lt;/p&gt;

&lt;p&gt;So automatic certificates already work in Tipi. In fact, this very website, which I&amp;#8217;ve put together a few weekends ago, already uses automatic certificates. While it works, there&amp;#8217;s still a lot of details to take care of: testing, handling of failures, adding more ACME providers, and finally coming up with a simple API for configuring automatic certificates.&lt;/p&gt;

&lt;h3 id=&quot;other-changes&quot;&gt;Other changes&lt;/h3&gt;

&lt;p&gt;In addition to the above big new features, I&amp;#8217;ve also worked on the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Fixed upgrading to HTTP/2 &lt;a href=&quot;https://github.com/digital-fabric/tipi/commit/86c648a13199c79818793b5f49648c30216b644c&quot;&gt;with a request body&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Start work on CLI interface. Nothing to announce for the moment, but what I&amp;#8217;m aiming for is a command line tool that can be used to serve static files in any directory, serve a rack app, or a custom app using Tipi&amp;#8217;s API, with automatic certificates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-next-for-the-polyphony-ecosystem&quot;&gt;What&amp;#8217;s next for the Polyphony ecosystem?&lt;/h2&gt;

&lt;p&gt;In the last few years I&amp;#8217;ve been creating a set of Ruby gems that I call &lt;a href=&quot;https://github.com/digital-fabric/&quot;&gt;Digital Fabric&lt;/a&gt;, with the moto: &amp;#8220;Software for a better world.&amp;#8221; I believe in empowering small developers to build lightweight, autonomous digital systems to solve specific needs. The Digital Fabric suite already includes tools for working with &lt;a href=&quot;https://github.com/digital-fabric/extralite&quot;&gt;SQLite databases&lt;/a&gt;, creating &lt;a href=&quot;https://github.com/digital-fabric/rubyoshka&quot;&gt;HTML templates&lt;/a&gt;, and &lt;a href=&quot;https://github.com/digital-fabric/modulation&quot;&gt;managing dependencies&lt;/a&gt;, in addition to &lt;a href=&quot;https://github.com/digital-fabric/polyphony&quot;&gt;Polyphony&lt;/a&gt; and  &lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;m a long-time Ruby programmer, and to date my most substantial contribution to the Ruby community is &lt;a href=&quot;http://sequel.jeremyevans.net/&quot;&gt;Sequel&lt;/a&gt;, of which I&amp;#8217;m the original author. The same spirit that guided me in creating Sequel is the one that&amp;#8217;s currently guiding me in working on the Digital Fabric suite of tools: create simple and powerfull APIs that make developers happy and that feel like natural extensions of the Ruby programming language. I believe Polyphony and Tipi have the potential to unleash a new wave of creativity in the Ruby community!&lt;/p&gt;

&lt;p&gt;Here&amp;#8217;s some of the things I intend to work on in the near future:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Further work on the H1P parser, as discussed above.&lt;/li&gt;
  &lt;li&gt;Start work on H2P - a library for dealing with HTTP/2 connections.&lt;/li&gt;
  &lt;li&gt;Continue work on automatic certificates in Tipi.&lt;/li&gt;
  &lt;li&gt;Prepare Polyphony for a 1.0 release.&lt;/li&gt;
  &lt;li&gt;Setup a websites for both Polyphony and Tipi.&lt;/li&gt;
  &lt;li&gt;Continue work on &lt;a href=&quot;https://github.com/digital-fabric/impression&quot;&gt;Impression&lt;/a&gt;, a new experimental web framework, which incidentially I&amp;#8217;m using for this site. It&amp;#8217;s just a sketch for the moment, but I have a bunch of ideas that that I&amp;#8217;d like to test, but it&amp;#8217;s still too early to tell if it will turn into a real project.&lt;/li&gt;
  &lt;li&gt;And finally, prepare Tipi for a first public release once the CLI tool is good enough.&lt;/li&gt;
&lt;/ul&gt;
</description></item><item><title>What&#39;s new in Polyphony - July 2021 edition</title><link>https://noteflakes.com/articles/2021-07-27-polyphony-july-edition</link><guid>https://noteflakes.com/articles/2021-07-27-polyphony-july-edition</guid><pubDate>Tue, 27 Jul 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;Following &lt;a href=&quot;/articles/2021-06-25-polyphony-june-edition&quot;&gt;last month&amp;#8217;s update&lt;/a&gt;, here&amp;#8217;s an update on the latest changes to Polyphony:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Redesigned tracing system&lt;/li&gt;
  &lt;li&gt;New methods for changing fiber ownership&lt;/li&gt;
  &lt;li&gt;Support for appending to buffers when reading&lt;/li&gt;
  &lt;li&gt;Improved backend statistics&lt;/li&gt;
  &lt;li&gt;Improved control over reading with &lt;code&gt;#read&lt;/code&gt; and &lt;code&gt;#readpartial&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;redesigned-tracing-system&quot;&gt;Redesigned tracing system&lt;/h2&gt;

&lt;p&gt;In previous versions, Polyphony included extensions to the core Ruby &lt;a href=&quot;https://rubyapi.org/3.0/o/tracepoint&quot;&gt;TracePoint&lt;/a&gt; API, so that events such as switching fibers, scheduling fibers, polling for I/O completions, could be traced using the same TracePoint API that&amp;#8217;s used for tracing method calls, variable access etc. In Polyphony 0.59 the tracing system was completely overhauled and separated from TracePoint. Polyphony backend events can now be traced by calling &lt;code&gt;Backend#trace_proc&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;trace_proc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;proc&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Events are fed to the tracing proc as plain Ruby arrays, where the first member signifies the type of event. Currently the following events are emitted:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:spin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:schedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_priority&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:block&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:unblock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resume_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:enter_poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:leave_poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_fiber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;new-methods-for-changing-fiber-ownership&quot;&gt;New methods for changing fiber ownership&lt;/h2&gt;

&lt;p&gt;Polyphony follows the &lt;a href=&quot;https://en.wikipedia.org/wiki/Structured_concurrency&quot;&gt;structured concurrency&lt;/a&gt; paradigm, where the lifetime of each fiber is limited to that of the fiber from which it was spun. This mechanism, also called a parent-child relationship, permits developers to spin up thousands of fibers in a structured and controlled manner. Version 0.60 introduces two new methods which allow you to change the parent of a fiber.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Fiber#detach&lt;/code&gt; sets the fiber&amp;#8217;s parent to the main fiber. This method could be useful if you need a fiber to outlive its parent:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;do_something&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;detach&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Fiber.main&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;await&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# parent is dead, but child is still alive!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Fiber#attach&lt;/code&gt; lets you set the fiber&amp;#8217;s parent to any other fiber, which might be useful if you start a fiber in some context but then need it to be limited by the lifetime of another fiber:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;worker_parent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;fiber_maker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin_loop&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;receive&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;worker_parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# at some point we want to stop all workers&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;worker_parent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;terminate&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# boom, all workers are terminated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;support-for-appending-to-buffers-when-reading&quot;&gt;Support for appending to buffers when reading&lt;/h2&gt;

&lt;p&gt;Up until now, the backend read/recv APIs allowed you to provide a buffer and read into it, replacing any content it may hold. A major change introduced in version 0.60 allows reading to any position in the provided buffer, including appending to the buffer. The &lt;code&gt;Backend#read&lt;/code&gt; and &lt;code&gt;Backend#recv&lt;/code&gt; methods now accept a &lt;code&gt;buffer_pos&lt;/code&gt; argument:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# append to a buffer&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;foo&#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;bar&#39;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;baz&#39;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&#39;&#39;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# buffer_pos is the last argument. -1 denotes the end of the buffer&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &#39;foo&#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &#39;foobar&#39;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Polyphony&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; &#39;foobarbaz&#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This addition may seem minor but what it allows us to do, beyond not needing to concatenate strings, is to write parsers that are competely blocking. I&amp;#8217;m currently writing a &lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/ext/tipi/http1_parser.c&quot;&gt;custom HTTP/1 parser&lt;/a&gt; for Tipi that&amp;#8217;s based on this unique feature and which promises to significantly improve both throughput and memory usage. (I&amp;#8217;ll discuss this new parser in detail in another post.)&lt;/p&gt;

&lt;h2 id=&quot;improved-backend-statistics&quot;&gt;Improved backend statistics&lt;/h2&gt;

&lt;p&gt;Polyphony version 0.61 has introduced streamlined and more comprehensive backend statistics, now accessible using &lt;code&gt;Backend#stats&lt;/code&gt;. The statistics are returned as a hash with the following keys:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;:runqueue_size&lt;/code&gt;: the size of the run queue&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:runqueue_length&lt;/code&gt;: the number of fibers currently in the runqueue&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:runqueue_max_length&lt;/code&gt;: the max number of fibers in the runqueue since the last call to &lt;code&gt;#stats&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:op_count&lt;/code&gt;: the number of backend operations since the last call to &lt;code&gt;#stats&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:switch_count&lt;/code&gt;: the number of fiber switches since the last call to &lt;code&gt;#stats&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:poll_count&lt;/code&gt;: the number of times the backend polled for completions since the last call to &lt;code&gt;#stats&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;:pending_ops&lt;/code&gt;: number of operations currently pending&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;improved-control-over-reading-with-read-and-readpartial&quot;&gt;Improved control over reading with &lt;code&gt;#read&lt;/code&gt; and &lt;code&gt;#readpartial&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Finally, Polyphony versions 0.63 and 0.64 have added optional arguments to &lt;code&gt;IO#read&lt;/code&gt; and &lt;code&gt;IO#readpartial&lt;/code&gt; in order to allow developers to have more flexibility and to use the new &amp;#8220;append to buffer&amp;#8221; feature discussed above. Here are the updated signatures for those methods (they apply also to all socket classes):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;IO#read(len = nil, buf = nil, buf_pos = 0)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IO#readpartial(len, str = +&#39;&#39;, buffer_pos = 0, raise_on_eof = true)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note the &lt;code&gt;raise_on_eof&lt;/code&gt; argument, which can be used to control whether &lt;code&gt;#readpartial&lt;/code&gt; raises an &lt;code&gt;EOFError&lt;/code&gt; when an EOF is encountered.&lt;/p&gt;

&lt;h2 id=&quot;whats-next-for-polyphony&quot;&gt;What&amp;#8217;s next for Polyphony&lt;/h2&gt;

&lt;p&gt;As I wrote above, I&amp;#8217;m currently developing a &lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/ext/tipi/http1_parser.c&quot;&gt;custom HTTP/1 parser&lt;/a&gt; for Tipi, which already has promising performance characteristics, reduces dependencies and is completely synchronous (i.e. no callbacks are involved). I hope to be able to switch Tipi to using the new parser in the coming weeks and having it battle tasted in one of my production projects, and then to continue to write a HTTP/2 parser with a similar design.&lt;/p&gt;
</description></item><item><title>What&#39;s new in Polyphony - June 2021 edition</title><link>https://noteflakes.com/articles/2021-06-25-polyphony-june-edition</link><guid>https://noteflakes.com/articles/2021-06-25-polyphony-june-edition</guid><pubDate>Fri, 25 Jun 2021 00:00:00 GMT</pubDate><description>&lt;p&gt;Polyphony 0.58 has just been released. Here&amp;#8217;s a summary and discussion of the latest changes and improvements:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Improved functionality for OpenSSL sockets and servers.&lt;/li&gt;
  &lt;li&gt;Fixes to the &lt;code&gt;Mutex&lt;/code&gt; class.&lt;/li&gt;
  &lt;li&gt;A redesigned event anti-starvation algorithm.&lt;/li&gt;
  &lt;li&gt;A new API for splicing to/from pipes.&lt;/li&gt;
  &lt;li&gt;A new API for chaining multiple I/O operations.&lt;/li&gt;
  &lt;li&gt;New APIs for performing GC and other arbitrary work when the process is idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;improved-functionality-for-openssl-sockets-and-servers&quot;&gt;Improved functionality for OpenSSL sockets and servers&lt;/h2&gt;

&lt;p&gt;Following the work I&amp;#8217;ve lately been doing on the Tipi server (you can read more about that towards the end of this post), I&amp;#8217;ve made significant improvements to working with OpenSSL encrypted servers and client sockets. Here are some of the changes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add &lt;code&gt;SSLSocket#recv_loop&lt;/code&gt; method (#54).&lt;/li&gt;
  &lt;li&gt;Add &lt;code&gt;SSLServer#accept_loop&lt;/code&gt; method.&lt;/li&gt;
  &lt;li&gt;Override &lt;code&gt;SSLSocket#peeraddr&lt;/code&gt; to support the same arity as &lt;code&gt;Socket#peeraddr&lt;/code&gt; (#55) (this is an inconsistency at the level of the Ruby stdlib.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;fixes-to-the-mutex-class&quot;&gt;Fixes to the &lt;code&gt;Mutex&lt;/code&gt; class&lt;/h2&gt;

&lt;p&gt;Following a &lt;a href=&quot;https://github.com/digital-fabric/polyphony/issues/50&quot;&gt;bug report&lt;/a&gt; from @primeapple, trying to use Polyphony in a Rails project (!), some missing methods were added to the Polyphony implementation of the Mutex class: &lt;code&gt;#owned?&lt;/code&gt; and &lt;code&gt;#locked?&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is still too early to tell if Polyphony can be used to drive a Rails app, and frankly I am not actively trying to make it happen, but I&amp;#8217;d love to receive more feedback on how Polyphony interacts with different parts of the Rails ecosystem.&lt;/p&gt;

&lt;h2 id=&quot;a-redesigned-event-anti-starvation-algorithm&quot;&gt;A redesigned event anti-starvation algorithm&lt;/h2&gt;

&lt;p&gt;Polyphony is at its core an efficient fiber scheduler that eschews the traditional non-blocking design that wraps the entire program in one big event loop. Instead, when a fiber needs to perform some long-running operation, it simply passes control to the next fiber on the run queue. If the run queue is empty, the backend is polled for completion of events or operations. This is a blocking call that will return only when one or more events are available for the backend to process. Each completed event or operation will cause the corresponding fiber to be scheduled, and to be eventually resumed.&lt;/p&gt;

&lt;p&gt;Under certain circumstances, though, if the runqueue is never empty (because fibers are kept being scheduled regardless of I/O events), this will prevent Polyphony from polling for events, leading to &lt;em&gt;event starvation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In order to prevent this from happening, Polyphony includes a mechanism for periodically performing a &lt;em&gt;non-blocking&lt;/em&gt; poll, which assures the processing of ready events, even under such conditions. For the libev backend, this is done by calling &lt;code&gt;ev_run(backend-&amp;gt;ev_loop, EVRUN_NOWAIT)&lt;/code&gt;, which will only process ready events without waiting. For the io_uring backend this done by simply processing the available CQEs without issuing a &lt;code&gt;io_uring_enter&lt;/code&gt; system call.&lt;/p&gt;

&lt;p&gt;Now, until Polyphony version 0.54, determining &lt;em&gt;when&lt;/em&gt; to use this mechanism was problematic, and was based on false assumptions. In Polyphony 0.55 the algorithm for determining when to make a non-blocking poll was redesigned, and is now based on counting the number of times fibers have been switched, as well as keeping a high water mark for the number of fibers in the run queue:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A switch counter is incremented every time a fiber is pulled off the run queue.&lt;/li&gt;
  &lt;li&gt;The switch counter is reset when the run queue is empty.&lt;/li&gt;
  &lt;li&gt;A high water mark is updated every time it&amp;#8217;s exceeded by the run queue size (when adding fibers to the run queue).&lt;/li&gt;
  &lt;li&gt;The non-blocking poll is performed when both the high water mark and the switch count reach certain thresholds (currently set at 128 and 64 respectively).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this tickles your interest, you can have a look at &lt;a href=&quot;https://github.com/digital-fabric/polyphony/blob/master/ext/polyphony/runqueue.c&quot;&gt;the code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-new-api-for-splicing-tofrom-pipes&quot;&gt;A new API for splicing to/from pipes&lt;/h2&gt;

&lt;p&gt;The Linux kernel includes a relatively little known system call called &lt;code&gt;splice&lt;/code&gt;, which lets developers move data from one file descriptor to another (for example, from a file to a socket) without needing to copy data back and forth between userspace and the kernel, a costly operation, and in some cases even without copying data inside the kernel itself, by using pipes (which act as kernel buffers). To learn more about &lt;code&gt;splice&lt;/code&gt; and what it&amp;#8217;s good for, read &lt;a href=&quot;https://yarchive.net/comp/linux/splice.html&quot;&gt;this explanation&lt;/a&gt; by Linus Torvalds himself.&lt;/p&gt;

&lt;p&gt;Starting from Polyphony 0.53, I&amp;#8217;ve been gradually adding support for splicing to and from I/Os on both the libev and io_uring backends. The following APIs were added:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Backend#splice(src, dest, maxlen)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Backend#splice_to_eof(src, dest, chunk_size)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, the corresponding methods have been added to the &lt;code&gt;IO&lt;/code&gt; class:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;IO#splice(src, maxlen)&lt;/code&gt; - returns the number of bytes written&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IO#splice_to_eof(src, chunk_size = 8192)&lt;/code&gt; - returns the number bytes written&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we know that to &lt;code&gt;splice&lt;/code&gt; we need to use a pipe, either for the source or the destination or for both, but how do we use it in practice? Suppose we want to write the content of a file to a socket. Here&amp;#8217;s one way we can do this with &lt;code&gt;splice&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write_file_to_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&#39;r&#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;spin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice_to_eof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ensure&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice_to_eof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example we create a pipe, and then we spin up a separate fiber that will splice from the file to the pipe, while the current fiber splices from the pipe to the socket. This technique can be used for files of arbitrary size (even GBs), without loading the file content into Ruby strings and putting pressure on the Ruby garbage collector. On top of this, we do this concurrently and with automatic backpressure (i.e. our socket will not get inondated with MBs of data.)&lt;/p&gt;

&lt;p&gt;While the &lt;code&gt;splice&lt;/code&gt; system call is only available on Linux, the libev backend includes fake implementations of &lt;code&gt;Backend#splice&lt;/code&gt; and &lt;code&gt;Backend#splice_to_eof&lt;/code&gt; done with plain &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;In addition to the above new methods, Polyphony 0.57 also introduces the &lt;code&gt;Backend#splice_chunks&lt;/code&gt; method, which can be used for splicing chunks to some arbitrary destination IO instance, interespersed with writing plain Ruby strings to it. The use case arose while working on the &lt;a href=&quot;https://github.com/digital-fabric/tipi&quot;&gt;Tipi web server&lt;/a&gt;, and trying to optimize serving static files on the web without loading the file content in Ruby strings. The Tipi HTTP/1.1 adapter tries whenver possible to use chunked encoding. In HTTP/1.1 for each chunk there should be a header including the chunk size, followed by the chunk itself, and finally a &lt;code&gt;\r\n&lt;/code&gt; delimiter. In order to abstract away the creation of a pipe (for use with splicing) and the looping etc, I introduced the following method:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Backend#splice_chunks(src, dest, prefix, postfix, chunk_prefix, chunk_postfix, chunk_size)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;#8230; with the following arguments:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;src&lt;/code&gt; - source IO instance&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;dest&lt;/code&gt; - destination IO instance&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;prefix&lt;/code&gt; - prefix to write to destination before splicing from the source&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;postfix&lt;/code&gt; - postfix to write to destination after splicing is done&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;chunk_prefix&lt;/code&gt; - the prefix to write before each chunk (a string or a proc)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;chunk_postfix&lt;/code&gt; - the postfix to write after each chunk (a string or a proc)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;chunk_size&lt;/code&gt; - the maximum chunk size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The chunk prefix and postfix can be a &lt;code&gt;Proc&lt;/code&gt; that accepts the length of the current chunk, and returns a string to be written to the destination. Here&amp;#8217;s how this new API is used in Tipi to serve big files:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Edited for brevity&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;respond_from_io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;format_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice_chunks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@conn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# prefix: HTTP headers&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;formatted_headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# postfix: empty chunk denotes end of response&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;0&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# dynamic chunk prefix with the chunk length&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# chunk delimiter&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the example demonstrates, this allows sending chunks from arbitrary IO instances (be it files or sockets or STDIO or pipes) without any of the data passing through the Ruby runtime, and the API is concise but also allows lots of flexibility. We can imagine using this API to send HTTP/2 data frames without much difficulty.&lt;/p&gt;

&lt;p&gt;While the libev backend is more or less straightforward - doing splicing and writing sequentially one after the other, the io_uring backend implementation benefits from being able to issue multiple &lt;em&gt;ordered&lt;/em&gt; I/O operations at once using the &lt;code&gt;IOSQE_IO_LINK&lt;/code&gt; flag. This allows us to further minimize the number of system calls we make.&lt;/p&gt;

&lt;p&gt;But what of the performance implications? Does using this technique result in any noticable improvements to performance? It&amp;#8217;s still too early to tell how using this technique will affect the numbers in a real-world situation, but preliminary benchmarks for &lt;a href=&quot;https://github.com/digital-fabric/tipi/blob/master/examples/http_server_static.rb&quot;&gt;serving static files with Tipi&lt;/a&gt; show a marked improvement for files bigger than 1MB:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;File size&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Normal - req/s&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Spliced - req/s&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Change&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1KB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;8300&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;7568&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;-8%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64KB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;7256&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;5702&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;-21%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1MB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;730&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;768&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+5%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;4MB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;130&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;189&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+45%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;16MB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;28&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;46&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+64%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;64M&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;12&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+33%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;256MB&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;+50%&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;This benchmark was done using the io_uring backend, using &lt;code&gt;wrk&lt;/code&gt; with the stock settings, i.e. 2 threads and 10 concurrent connections, on an lowly EC2 t3.xlarge machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;a-new-api-for-chaining-multiple-io-operations&quot;&gt;A new API for chaining multiple I/O operations&lt;/h2&gt;

&lt;p&gt;Another big new feature introduced in Polyphony 0.55 is chaining of multiple ordered I/O operations using the &lt;code&gt;Backend#chain&lt;/code&gt; API, which allows developers to specify multiple (outgoing) I/O operations in a single call to the backend, in order to minimize the overhead involved in going back and forth between the fiber issuing the I/O operations and the backend.&lt;/p&gt;

&lt;p&gt;While Polyphony can already write multiple strings to the same file descriptor with a single method call (using &lt;code&gt;writev&lt;/code&gt;), this new API allows developers to perform multiple I/O operations on different file descriptors in a single method call.&lt;/p&gt;

&lt;p&gt;Here as well, the io_uring backend can reap additional performance benefits by issuing multiple ordered I/O operations using a single system call, without having to wakeup the fiber after each I/O operation, in a similar fashion to the &lt;code&gt;Backend#splice_chunks&lt;/code&gt; API we just discussed.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Backend#chain&lt;/code&gt; method takes one or more operation specifications expressed using plain arrays. Here&amp;#8217;s a simplified version of &lt;code&gt;Backend#splice_chunks&lt;/code&gt; implemented using the &lt;code&gt;#chain&lt;/code&gt; API:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;splice_chunks_in_ruby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;postfix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pipe&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chunk_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:splice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;postfix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The following operation specifications are currently supported:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;[:write, destination, data]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;[:send, destination, data, flags]&lt;/code&gt; - for sockets only&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;[:splice, source, destination, len]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;new-apis-for-performing-gc-and-other-arbitrary-work-when-idle&quot;&gt;New APIs for performing GC and other arbitrary work when idle&lt;/h2&gt;

&lt;p&gt;When running web servers in production, I&amp;#8217;d like not only to maximize the server&amp;#8217;s throughput (expressed in requests per second), but also minimize latency. And when we talk about latency we also need to talk about percentiles. One of the things that can really hurt those 99th percentile latency numbers in Ruby web servers is the fact that the Ruby runtime needs to perform garbage collection from time to time, and normally this garbage collection event is both slow (costing tens of milliseconds or even more), and can come at any time, including while processing an incoming request.&lt;/p&gt;

&lt;p&gt;In order to prevent garbage collection from happening while your server is busy preparing a response, a technique called out-of-band GC, or out-of-band processing, consists of disabling the garbage collector, and manually running a GC cycle when the server is otherwise idle (i.e. not busy serving requests.)&lt;/p&gt;

&lt;p&gt;Polyphony 0.58 introduces new APIs that allow you to perform garbage collection or run any code only when the process is otherwise idle (i.e. when no fibers are scheduled.) Here are the new APIs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Thread#idle_gc_period=&lt;/code&gt; - sets the period (in seconds) for performing GC when idle.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Thread#on_idle { ... }&lt;/code&gt; - installs a block to be executed whenever the system is idle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here&amp;#8217;s how you can set automatic GC when idle:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Here we set the minimum interval between consecutive GC&#39;s done only when the&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# thread is otherwise idle to 60 seconds:&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;idle_gc_period&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;GC&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also run an arbitrary block of code when idle by passing a block to &lt;code&gt;Thread#on_idle&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;on_idle&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;do_something_really_unimportant&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;whats-next-for-polyphony&quot;&gt;What&amp;#8217;s next for Polyphony?&lt;/h2&gt;

&lt;p&gt;Polyphony has been in development for almost three years now, and its API is slowly stabilizing. I&amp;#8217;d like to be able to release version 1.0 in a few month but I still have some work left before we arrive there, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Support for UDP sockets.&lt;/li&gt;
  &lt;li&gt;Support for IPv6.&lt;/li&gt;
  &lt;li&gt;Redesign the fiber supervision API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As for actual applications using Polyphony, I am continuing work on the Tipi web server, which is already used in production (in a closed-source product for one of my clients), and which already knows how to do HTTP/1.1, HTTP/2, Websockets and SSL termination.&lt;/p&gt;

&lt;p&gt;I am currently working on two really exciting things:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Add automatic SSL certificate provisioning to Tipi, using the ACME protocol with providers such as Let&amp;#8217;s Encrypt. This will allow developers to run Tipi as an all-in-one app- &lt;em&gt;and&lt;/em&gt; web server, without needing to use a reverse proxy such as Nginx or Caddy at all!&lt;/li&gt;
  &lt;li&gt;Implement a modern HTTP client for Ruby based on Polyphony, with features such as persistent connections by default, automatic support for HTTP/2 connections, sessions, caching and persistent cookies to name but a few.&lt;/li&gt;
&lt;/ul&gt;
</description></item></channel></rss>