<rss xmlns:source="http://source.scripting.com/" version="2.0">
  <channel>
    <title>JP Camara</title>
    <link>https://jpcamara.com/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Tue, 30 Dec 2025 11:20:16 -0500</lastBuildDate>
    <item>
      <title>When good threads go bad</title>
      <link>https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html</link>
      <pubDate>Tue, 30 Dec 2025 11:20:16 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2025/12/30/when-good-threads-go-bad.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/whengoodthreadsgobad.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into several parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;When good threads go bad&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “When good threads go bad”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#threads-curfew&#34;&gt;Threads out after curfew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#stuck-threads&#34;&gt;Stuck threads&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#deadlocks&#34;&gt;Deadlocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#livelocks&#34;&gt;Livelocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#long-running-cpu&#34;&gt;Long-running CPU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#long-running-io&#34;&gt;Long-running IO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#native-extensions&#34;&gt;Native extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#what-happened-to-puma&#34;&gt;What happened to our Puma server, anyways?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-shutdown&#34;&gt;Thread shutdown&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-raise-kill&#34;&gt;&lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#dont-kill-threads&#34;&gt;Don’t &lt;code&gt;kill&lt;/code&gt; threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#interrupt-safely&#34;&gt;Interrupt your threads safely&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ensure-cleanup&#34;&gt;&lt;code&gt;ensure&lt;/code&gt; cleanup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#dont-use-timeout&#34;&gt;Don’t use &lt;code&gt;timeout&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#use-term-on-timeout&#34;&gt;Use &lt;code&gt;term_on_timeout&lt;/code&gt; with &lt;code&gt;rack-timeout&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#monitor-your-thread-costs&#34;&gt;Monitor your thread costs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#get-a-handle&#34;&gt;Get a &lt;code&gt;handle_interrupt&lt;/code&gt; on things&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-handle-interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ensure-success&#34;&gt;The way you &lt;code&gt;ensure&lt;/code&gt; success&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;threads-curfew&#34;&gt;Threads out after curfew 🧵 &lt;/h2&gt;
&lt;p&gt;It’s late, and you start getting alerts that requests to your web server are failing. You try to load a page and it hangs endlessly. The server isn’t responding to anything, and requests are continuing to queue up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-do-you-know-where-your-threads-are.drawio-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s 10PM. Do you know where your &lt;s&gt;children&lt;/s&gt; &lt;em&gt;threads&lt;/em&gt; are?&lt;/p&gt;
&lt;p&gt;📺 &lt;a href=&#34;https://en.wikipedia.org/wiki/Do_you_know_where_your_children_are%3F&#34;&gt;iykyk&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not knowing what else to do, you trigger a server restart. Even doing that, things remain unresponsive for another 30 seconds. Finally you see the server stop, and start up again. As if by magic, everything is running fine again.&lt;/p&gt;
&lt;p&gt;You’re running Puma with threads. It seemed like every thread was unresponsive. What happened to those threads?!&lt;/p&gt;
&lt;h3 id=&#34;stuck-threads&#34;&gt;Stuck threads&lt;/h3&gt;
&lt;p&gt;The reality of the situation is probably mundane.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are you looking up data in a query? Did you remember to index your columns?&lt;/li&gt;
&lt;li&gt;Do you have &lt;a href=&#34;https://www.namitjain.com/blog/n-plus-1-query-problem&#34;&gt;N+1 queries?&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Did you remember to &lt;a href=&#34;https://github.com/ankane/strong_migrations?tab=readme-ov-file#migration-timeouts&#34;&gt;set statement and lock timeouts&lt;/a&gt; before running a schema migration?&lt;/li&gt;
&lt;li&gt;Are you &lt;em&gt;sure&lt;/em&gt; it isn’t 1, 2 or 3?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If it &lt;em&gt;isn’t&lt;/em&gt; those things, there are other ways your threads can go rogue. Let’s look at some ways a thread can get stuck:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deadlocks&lt;/li&gt;
&lt;li&gt;Livelocks&lt;/li&gt;
&lt;li&gt;Long-running CPU&lt;/li&gt;
&lt;li&gt;Long-running IO&lt;/li&gt;
&lt;li&gt;Native extensions&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;deadlocks&#34;&gt;&lt;a href=&#34;https://en.m.wikipedia.org/wiki/Deadlock_(computer_science)&#34;&gt;Deadlocks&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The conventional example of a deadlock is two &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#mutex&#34;&gt;threads attempting to acquire a mutex&lt;/a&gt;held by the other thread.&lt;/p&gt;
&lt;p&gt;They can never make progress, so they’re dead in the water:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mutex_1 = Mutex.new
mutex_2 = Mutex.new
thread_1 = Thread.new do
  mutex_1.synchronize do
    sleep 1
    mutex_2.lock
  end
end
thread_2 = Thread.new do
  mutex_2.synchronize do
    sleep 1
    mutex_1.lock
  end
end
thread_1.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our example, &lt;code&gt;thread_1&lt;/code&gt; acquires &lt;code&gt;mutex_1&lt;/code&gt;. Then &lt;code&gt;thread_2&lt;/code&gt; acquires &lt;code&gt;mutex_2&lt;/code&gt;. Next, &lt;code&gt;thread_1&lt;/code&gt; attempts to acquire &lt;code&gt;mutex_2&lt;/code&gt; and blocks. Then &lt;code&gt;thread_2&lt;/code&gt; attempts to require &lt;code&gt;mutex_1&lt;/code&gt; and blocks. Neither can make progress, and are stuck in place.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/whengoodthreadsgobad.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It’s a traditional example, but Ruby is very good at detecting it! It detects the problem and raises an error, checking if any threads are capable of making progress:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&amp;#39;Thread#join&amp;#39;: No live threads left. Deadlock? (fatal)
  3 threads, 3 sleeps current:0x0000000a35e90c00 main thread:0x0000000101262de0
  * #&amp;lt;Thread:0x00000001001fafa8 sleep_forever&amp;gt;
    rb_thread_t:0x0000000101262de0 native:0x0000000202cfc800 int:0
	   
  * #&amp;lt;Thread:0x0000000121727c08 (irb):101 sleep_forever&amp;gt;
  rb_thread_t:0x0000000a35e90e00 native:0x00000001700f7000 int:0 mutex:0x0000000a3638c000 cond:1
    depended by: tb_thread_id:0x0000000101262de0
	   
  * #&amp;lt;Thread:0x00000001217279d8 (irb):107 sleep_forever&amp;gt;
  rb_thread_t:0x0000000a35e90c00 native:0x0000000170203000 int:0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ruby detects that &lt;code&gt;thread_1&lt;/code&gt; and &lt;code&gt;thread_2&lt;/code&gt; are sleeping, and the third thread is &lt;code&gt;Thread.main&lt;/code&gt;, which sleeps waiting for &lt;code&gt;thread_1&lt;/code&gt; to finish.&lt;/p&gt;
&lt;p&gt;Most long running programs are likely to have &lt;em&gt;some&lt;/em&gt; other thread running, and Ruby only detects if &lt;em&gt;all&lt;/em&gt; threads are stuck. We can get our deadlock example to work by adding an extra thread in a work loop:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mutex_1 = Mutex.new
mutex_2 = Mutex.new
thread_1 = Thread.new do
  mutex_1.synchronize do
    sleep 1
    mutex_2.lock
  end
end
thread_2 = Thread.new do
  mutex_2.synchronize do
    sleep 1
    mutex_1.lock
  end
end

thread_3 = Thread.new do
  loop do
    # process some work...
  end
end

thread_1.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now threads 1 and 2 will never progress, and Ruby lets the program continue running because &lt;code&gt;thread_3&lt;/code&gt; is still active.&lt;/p&gt;
&lt;h4 id=&#34;livelocks&#34;&gt;Livelocks&lt;/h4&gt;
&lt;p&gt;While a deadlock means a thread has stopped processing, a livelock happens when a thread keeps running, but never makes any progress. Here’s another example using an alternative approach for acquiring a mutex:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mutex_1 = Mutex.new
mutex_2 = Mutex.new
thread_1 = Thread.new do
  mutex_1.synchronize do
    sleep 0.1
    while !mutex_2.try_lock; end
  end
end
thread_2 = Thread.new do
  mutex_2.synchronize do
    sleep 0.1
    while !mutex_1.try_lock; end
  end
end
thread_1.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is similar to our deadlock example, but this time we use &lt;code&gt;#try_lock&lt;/code&gt; instead of &lt;code&gt;#lock&lt;/code&gt;. Unlike &lt;code&gt;#lock&lt;/code&gt;, which blocks until the mutex is available, &lt;code&gt;#try_lock&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; if attempting the lock fails. We do a short &lt;code&gt;sleep&lt;/code&gt; in each thread to give them time to acquire the initial locks, then iterate infinitely attempting &lt;code&gt;#try_lock&lt;/code&gt;. The locks will never be acquired, and the loops will run forever. Burn, CPU, burn 🔥.&lt;/p&gt;
&lt;p&gt;Personally I’ve rarely encountered deadlocks and livelocks in threaded code. But I’ve definitely encountered them in databases!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;thread_1 = Thread.new do
  User.find(1).with_lock do
    sleep 0.1
    User.find(2).lock!
  end
end

thread_2 = Thread.new do
  User.find(2).with_lock do
    sleep 0.1
    User.find(1).lock!
  end
end

thread_1.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On PostgreSQL, it will detect this deadlock and raise an error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;PG::TRDeadlockDetected: ERROR:  deadlock detected (ActiveRecord::Deadlocked)
DETAIL:  Process 581 waits for ShareLock on transaction 4003; blocked by process 582.
Process 582 waits for ShareLock on transaction 4002; blocked by process 581.
CONTEXT:  while locking tuple (0,1) in relation &amp;#34;users&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How do we solve deadlocks and livelocks? The answer is a consistent order for locking.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mutex_1 = Mutex.new
mutex_2 = Mutex.new
thread_1 = Thread.new do
  mutex_1.synchronize do
    sleep 1
    mutex_2.lock
  end
end
thread_2 = Thread.new do
  mutex_1.synchronize do
    sleep 1
    mutex_2.lock
  end
end

thread_3 = Thread.new do
  loop do
    # process some work...
  end
end

thread_1.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same example as before, but this time both threads attempt to acquire mutexes in &lt;em&gt;identical&lt;/em&gt; order. As long as you acquire in a consistent order, you should never hit deadlocks or livelocks.&lt;/p&gt;
&lt;h4 id=&#34;long-running-cpu&#34;&gt;Long-running CPU&lt;/h4&gt;
&lt;p&gt;As far as I know, this isn’t &lt;em&gt;actually&lt;/em&gt; possible in pure Ruby. What I mean by “pure” Ruby is a program that only runs Ruby code, and no C/Rust/Zig extensions. The CRuby runtime controls how pure Ruby code runs, and makes sure we can’t hog threads. &lt;em&gt;Mostly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my!&lt;/a&gt;, we dug into the &lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html#timer-interrupt-mask&#34;&gt;&lt;code&gt;TIMER_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;and how it utilizes priority. It allows a thread to influence how large of a time slice it gets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def calculate_priority(priority, limit)
  priority &amp;gt; 0 ? limit &amp;lt;&amp;lt; priority : limit &amp;gt;&amp;gt; -priority
end
	
calculate_priority(0, 100)        # =&amp;gt; 100ms
calculate_priority(2, 100)        # =&amp;gt; 400ms
calculate_priority(-2, 100)       # =&amp;gt; 25ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At a default priority of 0, we get Ruby’s default time slice of 100ms. At -2, we get 25ms. At 2, we get 400ms. This means in theory, can we starve out other threads by increasing our priority?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;calculate_priority(5, 100)       # =&amp;gt; 3276800ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3,276,800 milliseconds is 54 minutes. Can we really block things for 54 minutes!?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;counts = Array.new(10, 0)
done = false

threads = 10.times.map do |i|
  Thread.new do
    j = 0
    loop do
      break if done
      j += 1
      counts[i] += 1 if j % 1_000_000 == 0
    end
  end.tap do |t|
    t.priority = 15 if i == 5
  end
end

sleep 10
done = true
counts.each_with_index { |c, i| puts &amp;quot;#{i}: #{c}&amp;quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s a bunch going on here - but there are only a few details to focus on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For the 6th thread (&lt;code&gt;i == 5&lt;/code&gt;), we set the priority to 15. That’s the priority that in theory gives us a time slice of 54 minutes.&lt;/li&gt;
&lt;li&gt;We set a count every million iterations in each thread.&lt;/li&gt;
&lt;li&gt;We sleep for 10 seconds, then set a killswitch on each thread by setting &lt;code&gt;done&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We print how many times each thread was able to increment their slice of the array.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s the output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;0: 26
1: 27
2: 26
3: 27
4: 28
5: 189
6: 24
7: 24
8: 24
9: 24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As we can see - the priority made a difference! The thread at index &lt;code&gt;5&lt;/code&gt; runs around 7 times as much as every other thread. &lt;em&gt;But&lt;/em&gt;, it does not fully hog the thread like we might have thought. This is only 10 seconds, well under 54 minutes, and the other threads still get prioritized by the scheduler.&lt;/p&gt;
&lt;p&gt;This shows that we can &lt;em&gt;influence&lt;/em&gt; the scheduler, but we can’t completely hog the runtime from pure Ruby code.&lt;/p&gt;
&lt;p&gt;In more practical Ruby code, the &lt;a href=&#34;https://github.com/sidekiq/sidekiq&#34;&gt;Sidekiq&lt;/a&gt; gem gives you the ability to set priority on the threads it creates:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sidekiq.configure_server do |cfg|
  cfg.thread_priority = 15
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interestingly, by default, Sidekiq sets its threads to a priority of -1, which is less than the &lt;code&gt;0&lt;/code&gt; that Ruby uses by default. It describes the rationale:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ruby’s default thread priority is 0, which uses 100ms time slices. This can lead to some surprising thread starvation; if using a lot of CPU-heavy concurrency, it may take several seconds before a Thread gets on the CPU.&lt;/p&gt;
&lt;p&gt;Negative priorities lower the timeslice by half, so -1 = 50ms, -2 = 25ms, etc. With more frequent timeslices, we reduce the risk of unintentional timeouts and starvation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fascinating to see a real-world use-case of priority like this! Sidekiq has run trillions of jobs across hundreds of thousands of apps, and they made the decision to switch the priority.&lt;/p&gt;
&lt;p&gt;Since Ruby 3.4, you can achieve the same thing globally by using &lt;a href=&#34;https://bugs.ruby-lang.org/issues/20861&#34;&gt;&lt;code&gt;RUBY_THREAD_TIMESLICE&lt;/code&gt;&lt;/a&gt;. You can set &lt;code&gt;RUBY_THREAD_TIMESLICE=50&lt;/code&gt; and keep the priority the same, but now the time slice is 50ms.&lt;/p&gt;
&lt;h4 id=&#34;long-running-io&#34;&gt;Long-running IO&lt;/h4&gt;
&lt;p&gt;This is the likeliest scenario that will saturate your threads: long-running IO. In &lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;, we discussed how threads are great at handing off work when blocked on IO:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As soon as you do any IO operation, it just parks that thread/fiber and resumes any other one that isn’t blocked on IO.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, this only works as long as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have other threads available to pickup work&lt;/li&gt;
&lt;li&gt;You’ve tuned your thread counts based on your workload. &lt;a href=&#34;https://youtu.be/6HaXuQJMcvs?si=kC9e7GWZF-PIqGde&amp;amp;t=161&#34;&gt;Amdal’s Law&lt;/a&gt; helps you decide how many threads you should run, based on how much IO is running in your code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Earlier we mentioned slow queries as a possible IO blocker. But let’s say you have a web server running with 5 threads, and you allow users to download files&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DownloadsController &amp;lt; ApplicationController
  def index
    send_file Rails.root.join(&amp;quot;public/large.txt&amp;quot;),
      filename: &amp;quot;large.txt&amp;quot;,
      type: &amp;quot;application/octet-stream&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have a simple Rails controller action, which sends a file to the client making the request. It’s pretty straightforward! The &lt;code&gt;large.txt&lt;/code&gt; file I tested with locally is about 130mb. I’ll run it in a basic Puma setup, using 5 threads:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;bundle exec puma -t 5:5
	
Puma starting in single mode...
 * Puma version: 7.1.0 (&amp;#34;Neon Witch&amp;#34;)
 * Ruby version: ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin23]
 *  Min threads: 5
 *  Max threads: 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’m running a Puma server with 5 threads. Let’s try some benchmarks against it. We’ll use &lt;a href=&#34;https://httpd.apache.org/docs/2.4/programs/ab.html&#34;&gt;Apache Bench&lt;/a&gt; (&lt;code&gt;ab&lt;/code&gt;) to simulate traffic to Puma. &lt;code&gt;-n&lt;/code&gt; means the number of total requests, and &lt;code&gt;-c&lt;/code&gt; is how many concurrent requests to make. Let’s start with 1 request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ab -n 1 -c 1 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   0.070 seconds
Complete requests:      1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cool - 0.07 seconds. How about 3?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ab -n 3 -c 3 http://0.0.0.0:9292/downloads
	
Concurrency Level:      3
Time taken for tests:   0.074 seconds
Complete requests:      3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not much change from a single request! How about 5? This would match the maximum number of simultaneous requests our server can currently support, using 5 threads:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ab -n 5 -c 5 http://0.0.0.0:9292/downloads
	
Concurrency Level:      5
Time taken for tests:   0.134 seconds
Complete requests:      5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Things slow down a &lt;em&gt;little&lt;/em&gt; bit once we max out our threads. But still reasonable. &lt;em&gt;How about 50&lt;/em&gt;?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ab -n 50 -c 50 http://0.0.0.0:9292/downloads
	
Concurrency Level:      50
Time taken for tests:   0.820 seconds
Complete requests:      50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So far, so good. But we’re benefiting from a lot here: the file isn’t &lt;em&gt;particularly&lt;/em&gt; large, and there is &lt;em&gt;no&lt;/em&gt; latency. The server is responding quickly, and the client is consuming the response quickly. Let’s switch things up a bit - how well does it handle a client downloading slowly?&lt;/p&gt;
&lt;p&gt;We’ll use &lt;code&gt;curl&lt;/code&gt; to simulate a slow client. We can use &lt;code&gt;limit-rate&lt;/code&gt; to simulate a client downloading only 1000k per second. &lt;code&gt;curl ... --limit-rate 1000k &amp;amp;&lt;/code&gt; means we’ll download results at a rate of 1000k per second, running in the background (&lt;code&gt;&amp;amp;&lt;/code&gt;). This means it will take our &lt;code&gt;curl&lt;/code&gt; call 2 or 3 minutes to download a 130mb file. At the same time, we’ll run another apache bench to see how things perform:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;curl http://0.0.0.0:9292/downloads -o /tmp/large.txt \
  --limit-rate 1000k &amp;amp;
	
ab -n 1 -c 1 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   0.056 seconds
Complete requests:      1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puma responds quickly. In this scenario, &lt;code&gt;curl&lt;/code&gt; is occupying one thread, and &lt;code&gt;ab&lt;/code&gt; occupies another. Let’s try running 4 curl commands:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i in &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;1..4&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;    --limit-rate 1000k &amp;amp;
&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
	
ab -n &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; -c &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; http://0.0.0.0:9292/downloads
	
Concurrency Level:      &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
Time taken &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; tests:   0.059 seconds
Complete requests:      &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puma still responds fine. &lt;code&gt;curl&lt;/code&gt; is now occupying 4 threads, and &lt;code&gt;ab&lt;/code&gt; uses the remaining 1 thread. Let’s add &lt;em&gt;one&lt;/em&gt; more request using &lt;code&gt;curl&lt;/code&gt;. We also increase the default timeout of &lt;code&gt;ab&lt;/code&gt; (which is 30) to &lt;em&gt;200&lt;/em&gt;, for no particular reason…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i in &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;1..5&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;    --limit-rate 1000k &amp;amp;
&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
	
ab -n &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; -c &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; -s &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt; http://0.0.0.0:9292/downloads
	
Concurrency Level:      &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
Time taken &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; tests:   124.477 seconds
Complete requests:      &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Yikes&lt;/strong&gt;. That did &lt;strong&gt;not&lt;/strong&gt; go well. The moment we had 5 slow running requests from our &lt;code&gt;curl&lt;/code&gt; calls, we saturated all available threads. Our 6th request using &lt;code&gt;ab&lt;/code&gt; sat around waiting, finally finishing 2 &lt;em&gt;minutes&lt;/em&gt; later!&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;critical&lt;/strong&gt; consideration - long-running web work is a throughput killer. Ideally, keep all work as fast as possible and offload long-running work to jobs/other services. Most commonly for a download you’d create a &lt;a href=&#34;https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html&#34;&gt;presigned url&lt;/a&gt; for a service like S3 and redirect to that URL.&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;need&lt;/em&gt; to run long-running IO, you need to allocate many more threads&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h4 id=&#34;native-extensions&#34;&gt;Native extensions&lt;/h4&gt;
&lt;p&gt;In our &lt;a href=&#34;#long-running-cpu&#34;&gt;Long-running CPU&lt;/a&gt; example, we couldn’t get pure Ruby code to completely hog the runtime. We can prioritize a thread higher than other threads, but work still continues to be distributed.&lt;/p&gt;
&lt;p&gt;Once you start running native extensions, the Ruby runtime has more limited influence. It’s up to the extension to properly interface with Ruby and yield control back. Here’s a simple example that will block all other threads, using the standard &lt;code&gt;openssl&lt;/code&gt; gem, using a function written in C, &lt;code&gt;pbkdf2_hmac&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;openssl&amp;quot;

Thread.new do
  loop do
    puts &amp;quot;tick #{Time.now}&amp;quot;
  end
end

t = Thread.new do
  loop do
    OpenSSL::KDF.pbkdf2_hmac(&amp;quot;passphrase&amp;quot;, salt: &amp;quot;salt&amp;quot;, iterations: 12_800_000, length: 32, hash: &amp;quot;sha256&amp;quot;)
  end
end

t.join
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have two threads running - one infinitely printing the time in a loop, and one infinitely calling &lt;code&gt;pbkdf2_hmac&lt;/code&gt; in a loop. Here I give &lt;code&gt;pbkdf2_hmac&lt;/code&gt; a ludicrous number of iterations to force the function to run, in C, for a long period of time:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;tick 2025-12-29 15:20:30 -0500
tick 2025-12-29 15:20:45 -0500
tick 2025-12-29 15:20:59 -0500
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The results show that despite each thread having the same priority, the thread with long-running C extension code hogs all of the runtime. The printing thread is able to print a timestamp roughly every 15 seconds.&lt;/p&gt;
&lt;p&gt;There’s nothing the extension code is doing &lt;em&gt;wrong&lt;/em&gt; per se, but because it runs purely in C, without yielding back to Ruby until its done, Ruby can’t do anything to keep work distribution fair.&lt;/p&gt;
&lt;p&gt;In most cases, well-developed/mature native extensions won’t hit this issue. There are many popular gems that are, or include, native extensions. But if you &lt;em&gt;do&lt;/em&gt; hit an expensive path in a C extension, be aware the Ruby runtime will not be able to control it. If you &lt;em&gt;know&lt;/em&gt; you are interfacing with a slow piece of code in a native extension, keep it off the hot path, same as our long-running IO example.&lt;/p&gt;
&lt;h4 id=&#34;what-happened-to-puma&#34;&gt;What happened to our Puma server, anyways?&lt;/h4&gt;
&lt;p&gt;Remember our production panic scenario from earlier?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Not knowing what else to do, you trigger a server restart. Even doing that, things remain unresponsive for another 30 seconds. Finally you see the server stop, and start up again. As if by magic, everything is running fine again&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You triggered a server restart, and still had to wait 30 seconds? Why wasn’t Puma able to stop sooner? Let’s reuse our download example from earlier, and explain some default Puma behaviors!&lt;/p&gt;
&lt;p&gt;First, let’s start Puma, and see how quickly we can stop it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle exec puma -t 5:5

Puma starting in single mode...
* Puma version: 7.1.0 (&amp;quot;Neon Witch&amp;quot;)
* Ruby version: ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin23]
*  Min threads: 5
*  Max threads: 5

### hit ctrl + c to issue a SIGINT signal

Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-12-29 23:45:21 -0500 ===
- Goodbye!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We instantly see two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Gracefully stopping, waiting for requests to finish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Goodbye!&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puma tells us it is shutting down “gracefully”. With no activity, it is able to instantly stop.&lt;/p&gt;
&lt;p&gt;Now let’s use our &lt;code&gt;DownloadController&lt;/code&gt; again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DownloadsController &amp;lt; ApplicationController
  def index
    send_file Rails.root.join(&amp;quot;public/large.txt&amp;quot;),
      filename: &amp;quot;large.txt&amp;quot;,
      type: &amp;quot;application/octet-stream&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We start Puma again, then occupy each thread with a request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle exec puma -t 5:5

for i in {1..5}; do
  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt \
    --limit-rate 1000k &amp;amp;
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s trying issuing &lt;code&gt;INT&lt;/code&gt; using &lt;code&gt;ctrl+c&lt;/code&gt; again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Puma starting in single mode...
...
Use Ctrl-C to stop

^C &amp;lt;-- ctrl + c

... ~120 second pass, all downloads finish ...

- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2025-12-30 00:04:23 -0500 ===
- Goodbye!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok, so we issued our interruption. But… it waited for every request to &lt;em&gt;completely&lt;/em&gt; finish! We had to wait around 120 seconds before our server shutdown. That’s even worse than earlier!&lt;/p&gt;
&lt;p&gt;This isn’t a blog post about Puma specifically, but I’ll discuss a few factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Running &lt;code&gt;puma -t 5:5&lt;/code&gt; puts you in Puma “single” mode. This is a lightweight way of running puma, at the cost of less control. By default, single-mode Puma does not kill any requests. Like it tells us, it simply is “waiting for requests to finish”&lt;/li&gt;
&lt;li&gt;Alternatively, you can run puma in “cluster” mode. This is done by adding any worker count at all using the &lt;code&gt;-w&lt;/code&gt; flag. Even a &lt;code&gt;-w 1&lt;/code&gt; will move you into cluster mode. Cluster mode has a parent worker which monitors and controls child workers. This costs more memory, but it means the parent worker can kill child workers more reliably&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s try this one more time, starting in cluster mode by setting &lt;code&gt;-w 1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle exec puma -t 5:5 -w 1

[39363] Puma starting in cluster mode...
[39363] *  Min threads: 5
[39363] *  Max threads: 5
[39363] *   Master PID: 39363
[39363] *      Workers: 1
[39363] Use Ctrl-C to stop
^C &amp;lt;-- ctrl + c
[45110] - Gracefully shutting down workers...

... ~30 second pass, downloads are cut early ...

[45110] === puma shutdown: 2025-12-30 00:24:21 -0500 ===
[45110] - Goodbye!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This time we replicate our earlier behavior - 30 seconds pass, and the server is shutdown. Now that we’re running in cluster mode, by default Puma uses a configuration called &lt;code&gt;worker_shutdown_timeout&lt;/code&gt;, which defaults to 30 seconds. If you have a configuration file, you can set it yourself to something longer or shorter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;worker_shutdown_timeout 25 # instead of 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well, by default Puma &lt;em&gt;never&lt;/em&gt; kills threads. In a moment we’re going to be talking about ways to kill a thread. Puma plays it extremely safe, and offers &lt;em&gt;no&lt;/em&gt; ability to kill individual threads. And even when shutting down, it defaults to a thread shutdown policy of &lt;code&gt;:forever&lt;/code&gt;, which means the only way the threads are killed is when the server is entirely shutdown, which shuts down the worker the threads live in.&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; change this. In the same configuration file you’d set &lt;code&gt;worker_shutdown_timeout&lt;/code&gt; you can set &lt;code&gt;force_shutdown_after&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;force_shutdown_after 1 # an integer, :forever, or :immediate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still - this doesn’t do much. It still only impacts full server shutdown. But with this setting, the internal Puma thread pool will &lt;code&gt;raise&lt;/code&gt; on all threads, and then eventually run &lt;code&gt;kill&lt;/code&gt; on them.&lt;/p&gt;
&lt;p&gt;What all this means is that by default in Puma:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;primary&lt;/em&gt; way to fix misbehaving threads is to restart your server&lt;/li&gt;
&lt;li&gt;This will take up to 30 seconds running in cluster mode, unless you change the &lt;code&gt;worker_shutdown_timeout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Puma can’t kill long-running or stuck threads without a full server shutdown. Technically there is &lt;a href=&#34;https://github.com/puma/puma#controlstatus-server&#34;&gt;a control server&lt;/a&gt; you can setup, which can manage individual workers. But it cannot kill individual threads/requests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll talk in-depth later about the available option for killing long-running threads in Puma.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 if you want to dig deeper into how Puma works, I highly recommend &lt;a href=&#34;https://dansvetlov.me/puma-internals/&#34;&gt;Dissecting Puma: Anatomy of a Ruby Web Server&lt;/a&gt;. The &lt;a href=&#34;https://github.com/puma/puma&#34;&gt;source of Puma&lt;/a&gt; is also pretty readable&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;dont-guess&#34;&gt;Don&#39;t guess, measure&lt;/h4&gt;
&lt;p&gt;All of these thread issues are &lt;em&gt;possibilities&lt;/em&gt;, but they mean nothing without empirical data. Measure, then decide your course of option.&lt;/p&gt;
&lt;h3 id=&#34;thread-shutdown&#34;&gt;Thread Shutdown&lt;/h3&gt;
&lt;p&gt;Ok, enough about &lt;em&gt;how&lt;/em&gt; threads get stuck. Once they’re stuck - is there a way to stop them?&lt;/p&gt;
&lt;h4 id=&#34;thread-raise-kill&#34;&gt;&lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-raise&#34;&gt;&lt;code&gt;raise&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-kill&#34;&gt;&lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/dd43f6992d.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;You want to kill… me? 🥺&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ TL;DR You shouldn’t use these methods unless you &lt;em&gt;really&lt;/em&gt; know what you’re doing. Instead, &lt;a href=&#34;#tear-down-safely&#34;&gt;interrupt your thread safely&lt;/a&gt;. Incidentally, you should also &lt;a href=&#34;#dont-use-timeout&#34;&gt;avoid the timeout module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;if you’re writing a generic threaded framework you may need it - for custom one-off threads you can probably manage without it&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes a thread is running and you need to shut it down. There’s two primary methods for achieving that: &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;raise&lt;/code&gt; will raise an error inside of the target thread. If the thread hasn’t started yet, in most cases it is killed before running anything:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;never runs&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;knock it off&amp;#34;&lt;/span&gt;)
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: knock it off&amp;#34;, error=#&amp;lt;RuntimeError: knock it off&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 &lt;code&gt;thread_status&lt;/code&gt; is the helper we defined in the “Thread API” section on &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#thread-statuses&#34;&gt;&lt;code&gt;status&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The error isn’t raised instantly - only at the point the thread is scheduled next. We &lt;code&gt;sleep 0.1&lt;/code&gt; to give thread &lt;code&gt;t&lt;/code&gt; an opportunity to start. The thread scheduler starts it, and it immediately raises our “knock it off” error, effectively running right &lt;em&gt;before&lt;/em&gt; &lt;code&gt;puts &amp;quot;never runs&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If the thread gets a chance to start, the error will be raised on whatever line happened to be running last:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  sleep 5
end
t.join(1)
# We&#39;re only one second into the threads sleep at this point, so knock it off is raised from the `sleep 5` line
t.raise(&amp;quot;knock it off, sleepyhead!&amp;quot;)
sleep 0.1
puts thread_status(t)
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: knock it off, sleepyhead!&amp;quot;, error=#&amp;lt;RuntimeError: knock it off, sleepyhead!&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because it raises whatever error is provided (a &lt;code&gt;RuntimeError&lt;/code&gt; if just a string is provided), we can actually rescue the error, ignore it, and &lt;a href=&#34;https://docs.ruby-lang.org/en/master/syntax/exceptions_rdoc.html&#34;&gt;&lt;code&gt;retry&lt;/code&gt;&lt;/a&gt; 😱:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class KnockItOffError &amp;lt; StandardError; end

t = Thread.new do
  sleep 5
  puts &amp;quot;✌️&amp;quot;
rescue KnockItOffError =&amp;gt; e
  puts &amp;quot;Nice try #{e}&amp;quot;
  retry
end

t.join(1)
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
t.join

puts thread_status(t)
# Nice try 👊 
# ✌️
#&amp;lt;ThreadStatus status=&amp;quot;finished&amp;quot; error=nil&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;, issues start to creep in when errors are thrown in an &lt;code&gt;ensure&lt;/code&gt;. Here we use a &lt;code&gt;ConditionVariable&lt;/code&gt; (we dug into those in &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#condition-variable&#34;&gt;The Thread API&lt;/a&gt;) to guarantee we raise from the &lt;code&gt;ensure&lt;/code&gt; block:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;✌️&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#75715e&#34;&gt;# Signal the condition.wait in the main thread&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
    &lt;span style=&#34;color:#75715e&#34;&gt;# ...perform some cleanup...&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ll never fire 😔&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# This will wait until our Thread ensure runs and signals us&lt;/span&gt;
  condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊&amp;#34;&lt;/span&gt;))
	
mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# We&amp;#39;ve enqueued our error, now signal so condition#wait fires in the ensure block&lt;/span&gt;
  condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34;, error=#&amp;lt;RuntimeError: 👊&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We don’t see “I’ll never fire 😔”. What happens to our cleanup? Shouldn’t &lt;code&gt;ensure&lt;/code&gt;, erm, umm, &lt;em&gt;ensure&lt;/em&gt; that things finish…&lt;/p&gt;
&lt;p&gt;Moving on from &lt;code&gt;raise&lt;/code&gt;, &lt;code&gt;kill&lt;/code&gt; stops the thread from running anymore instructions, no matter what it’s doing. &lt;code&gt;raise&lt;/code&gt; can be &lt;code&gt;rescue&lt;/code&gt;’d, &lt;code&gt;kill&lt;/code&gt; can’t.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;✌️&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Exception is the root of the Error class hierarchy. If you can&amp;#39;t rescue it with this, you can&amp;#39;t rescue it &lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;rescue&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Exception&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#kill cannot be stopped...&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;This will still run&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;# A #kill&amp;#39;d thread gives no indication it was terminated 😔&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;finished&amp;#34;, error=nil&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 &lt;em&gt;Technically&lt;/em&gt;, &lt;code&gt;kill&lt;/code&gt; can be ignored, we’ll explain that when discussing &lt;code&gt;handle_interrupt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;⚠️ Don’t rescue &lt;code&gt;Exception&lt;/code&gt;, it&amp;rsquo;s a bad idea and you could accidentally rescue things like an OutOfMemoryError 😬&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because &lt;code&gt;kill&lt;/code&gt; doesn’t raise an error, you actually can’t even tell that the thread was &lt;code&gt;kill&lt;/code&gt;ed. We just get the normal &lt;code&gt;false&lt;/code&gt; &lt;code&gt;status&lt;/code&gt;, represented in our example by “finished”.&lt;/p&gt;
&lt;p&gt;Like &lt;code&gt;raise&lt;/code&gt;, &lt;code&gt;kill&lt;/code&gt; can also disrupt your &lt;code&gt;ensure&lt;/code&gt; methods:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;✌️&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
    &lt;span style=&#34;color:#75715e&#34;&gt;# perform some cleanup&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ll never fire 😔&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill
	
mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;# ✌️&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;finished&amp;#34; error=nil&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a few aliases for &lt;code&gt;kill&lt;/code&gt; to be aware of as well:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exit &lt;span style=&#34;color:#75715e&#34;&gt;# internally gets the current thread, and calls `kill`&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill(t) &lt;span style=&#34;color:#75715e&#34;&gt;# class method `kill`&lt;/span&gt;
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;exit        &lt;span style=&#34;color:#75715e&#34;&gt;# alias for `kill`&lt;/span&gt;
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;terminate   &lt;span style=&#34;color:#75715e&#34;&gt;# alias for `kill`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Case closed. Feel free to use &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt; on your threads. No harm no foul… oh what’s this here?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/05bee6d9-e6ae-4398-bde8-8f0bc387e600.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html&#34;&gt;Ruby&amp;rsquo;s Thread#raise, Thread#kill, timeout.rb, and net/protocol.rb libraries are broken&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/952b1448-5504-4072-a1b6-ad7ef9d548de.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/&#34;&gt;Why Ruby’s Timeout is dangerous (and Thread.raise is terrifying)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/ebeeaeb7-9cd9-4586-88cf-a17e305cc962.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/&#34;&gt;The Oldest Bug In Ruby - Why Rack::Timeout Might Hose your Server&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/7b8d7333-60d7-4c2f-907c-d571ab76fa26.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&#34;&gt;Timeout: Ruby’s Most Dangerous API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Oh…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media1.tenor.com/m/MYZgsN2TDJAAAAAC/this-is.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Strangely, the thread docs say &lt;em&gt;nothing&lt;/em&gt; about the dangers of these methods&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. These articles are from 2008, 2015 and 2017. Surely no one uses it anymore, considering all that?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/16ceaf75-1492-4520-b80e-d88e626580e0.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nah.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In fairness to threaded gems that use these methods, they are using the official way you shutdown a thread. And they’re usually taking as many precautions as possible, prior to calling them. For the most part, gems use them as a shutdown mechanism, and give plenty of room for the thread to finish normally first.&lt;/p&gt;
&lt;p&gt;The basic problem is this: &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt; force your code to die at any point, with no guarantee of properly cleaning up.&lt;/p&gt;
&lt;p&gt;You might ask: “Couldn’t &lt;code&gt;ctrl+c&lt;/code&gt; do the same thing?”. Yes, an OS signal could kill your process or program before an &lt;code&gt;ensure&lt;/code&gt; runs, but then all related state is also removed - it can cause other issues, but at least your program cannot limp along in a corrupted state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ensure
  # but, but Ruby - you _promised_ me this would run 😭
  @value = false
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So are they pure evil? An occasional necessity? Somewhere in between? I’ll leave that discussion to the code philosophers… in the practical realm, follow these rules:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 A small slice of this next section may look familiar. I included a bit of it in &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#thread-raise-kill&#34;&gt;The Thread API&lt;/a&gt;. This goes &lt;em&gt;much&lt;/em&gt; more in-depth&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Don’t use &lt;code&gt;Thread.kill&lt;/code&gt;, &lt;code&gt;kill&lt;/code&gt; or &lt;code&gt;raise&lt;/code&gt; unless you really, really know what you’re doing. Same applies for the &lt;code&gt;kill&lt;/code&gt; aliases &lt;code&gt;exit&lt;/code&gt; and &lt;code&gt;terminate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If you need to stop a thread, you want it to tear down safely. Make it safely interruptible by adding in a condition that can allow your thread to finish early&lt;/li&gt;
&lt;li&gt;Perform resource cleanups in an &lt;code&gt;ensure&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Don’t use the &lt;code&gt;timeout&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;If you use &lt;code&gt;rack-timeout&lt;/code&gt;, you really should use &lt;code&gt;term_on_timeout&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;When you use threads, even implicitly (aka, via configuration in Puma, Sidekiq, Falcon and SolidQueue), you can end up in weird states from them shutting down. It should be rare, but misbehaving threads (very long transactions, runaway CPU) are the most likely to experience this. Pay attention to long running queries or runaway CPU usage and treat it as an important bug&lt;/li&gt;
&lt;li&gt;If you have something very critical that must properly cleanup 100% of the time, you need &lt;a href=&#34;#&#34;&gt;&lt;code&gt;handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;dont-kill-threads&#34;&gt;(1) Don&#39;t kill threads&lt;/h4&gt;
&lt;p&gt;I’m watching you. Step away from that method, slowly, and no threads have to get hurt.&lt;/p&gt;
&lt;h4 id=&#34;interrupt-safely&#34;&gt;(2) Interrupt your thread safely&lt;/h4&gt;
&lt;p&gt;Instead of killing your thread, set it up to be interruptible. Most mature, threaded frameworks operate this way.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;still_kickin = Concurrent::AtomicBoolean.new(true)
Thread.new do
  while still_kickin.true?
    # more work!
  end
end

still_kickin.make_false
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;ensure-cleanup&#34;&gt;(3) Ensure cleanup&lt;/h4&gt;
&lt;p&gt;Whenever you &lt;em&gt;need&lt;/em&gt; something to run before a method finishes, you should always use an &lt;code&gt;ensure&lt;/code&gt; block. &lt;code&gt;ensure&lt;/code&gt; is kind of like a method lifeguard - even if something goes wrong, it’s there for you. It’s the place code goes to &lt;em&gt;ensure&lt;/em&gt; it’s run before the method finishes (even when an error is raised).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def read_some_data
  read_it
  close_it # bad
end

def read_some_data
  read_it
ensure
  close_it # good
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We &lt;em&gt;know&lt;/em&gt; &lt;code&gt;ensure&lt;/code&gt; is not a silver bullet. &lt;code&gt;Thread#raise&lt;/code&gt; and &lt;code&gt;Thread#kill&lt;/code&gt; do not respect it. But you’re the most likely to clean things up using an &lt;code&gt;ensure&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;dont-use-timeout&#34;&gt;(4) Don’t use &lt;code&gt;timeout&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;If you see this in code, be concerned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;timeout&amp;quot;

Timeout.timeout(1) do
  # 😱 
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For some reason, the &lt;a href=&#34;https://github.com/ruby/timeout&#34;&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/a&gt; gem itself doesn’t warn about any issues. But &lt;a href=&#34;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&#34;&gt;Mike Perham summarizes it best&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/727697a3-b461-4705-a1f1-8d0ed257857e.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There’s nothing that exactly matches what timeout offers: a blanket way of timing out &lt;em&gt;any&lt;/em&gt; operation after the specified time limit. But most gems and Ruby features offer a way to be interrupted - there is a repository called &lt;a href=&#34;https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts&#34;&gt;The Ultimate Guide to Ruby Timeouts&lt;/a&gt; which details everything you need to know. It shows you how to set timeouts safely for basically &lt;em&gt;every&lt;/em&gt; blocking operation you could care about timing out. For instance, how to properly handle timeouts using the &lt;a href=&#34;https://github.com/redis/redis-rb&#34;&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/a&gt; gem:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Redis&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(
  &lt;span style=&#34;color:#e6db74&#34;&gt;connect_timeout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, 
  &lt;span style=&#34;color:#e6db74&#34;&gt;timeout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The one piece mentioned in that repository you should leave alone: &lt;code&gt;Net::HTTP&lt;/code&gt; &lt;code&gt;open_timeout&lt;/code&gt;. Behind the scenes it uses the &lt;code&gt;timeout&lt;/code&gt; module 🙅‍♂️. Leave the 60 second default, it should almost never impact you, and you’re probably worse off lowering it.&lt;/p&gt;
&lt;p&gt;Primarily people use the &lt;code&gt;timeout&lt;/code&gt; gem to manage IO timeouts. In the unlikely case you want to timeout CPU-bound code, it’s up to you to implement it in your processing.&lt;/p&gt;
&lt;h4 id=&#34;use-term-on-timeout&#34;&gt;(5) If you use &lt;a href=&#34;https://github.com/zombocom/rack-timeout&#34;&gt;&lt;code&gt;rack-timeout&lt;/code&gt;&lt;/a&gt;, you really should use &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/doc/settings.md#term-on-timeout&#34;&gt;&lt;code&gt;term_on_timeout&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;rack-timeout&lt;/code&gt; works similarly to the &lt;code&gt;timeout&lt;/code&gt; module. And I already told you not to use that. So what gives? It will call &lt;code&gt;raise&lt;/code&gt; on your threads - isn’t that bad?&lt;/p&gt;
&lt;p&gt;The short answer is yes, it’s still bad.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But&lt;/em&gt;, &lt;code&gt;rack-timeout&lt;/code&gt; is the only real option you have for timing out a web request in Puma. It’s meant as a last resort. From their docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;rack-timeout&lt;/code&gt; is not a solution to the problem of long-running requests, it&amp;rsquo;s a debug and remediation tool. App developers should track rack-timeout&amp;rsquo;s data and address recurring instances of particular timeouts, for example by refactoring code so it runs faster or offsetting lengthy work to happen asynchronously.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On top of that, you should have your own lower level timeouts set so that they would fire before &lt;code&gt;rack-timeout&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You&amp;rsquo;ll want to set all relevant timeouts to something lower than &lt;code&gt;Rack::Timeout&lt;/code&gt;&amp;rsquo;s &lt;code&gt;service_timeout&lt;/code&gt;. Generally you want them to be at least 1s lower, so as to account for time spent elsewhere during the request&amp;rsquo;s lifetime while still giving libraries a chance to time out before Rack::Timeout.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The core issue of any thread &lt;code&gt;raise&lt;/code&gt;/&lt;code&gt;kill&lt;/code&gt; based solution is corrupted state. When using &lt;code&gt;rack-timeout&lt;/code&gt;, you should be using &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/doc/settings.md#term-on-timeout&#34;&gt;&lt;code&gt;term_on_timeout&lt;/code&gt;&lt;/a&gt;, ideally set to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;term_on_timeout&lt;/code&gt; will send a &lt;code&gt;SIGTERM&lt;/code&gt; to the worker the thread is running in, which for most servers indicates a need for a graceful shutdown of that process - any potential corrupted state is isolated to that process and will be cleaned up once the process is shutdown.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;term_on_timeout&lt;/code&gt; only works properly if you’ve got multiple processes serving your requests. And if you get lots and lots of timeouts, it could potentially cause performance problems. See the docs for proper configuration!&lt;/p&gt;
&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; an alternative idea floating around out there of a way to achieve a “Safer Timeout”, at least in Rails apps:&lt;a href=&#34;https://web.meetcleo.com/blog/safer-timeouts&#34;&gt;https://web.meetcleo.com/blog/safer-timeouts&lt;/a&gt;. Maybe I’ll detail it more in the future, but in the meantime, if you’re in a Rails app I would give it a read.&lt;/p&gt;
&lt;h4 id=&#34;monitor-your-thread-costs&#34;&gt;(6) Monitor your thread cost&lt;/h4&gt;
&lt;p&gt;Having threads that do not stop easily is a bug. If you’re seeing rack timeout errors, or jobs that can’t be shut down, track it and prioritize fixing it. Treat it like a bug and allocate time to improve it.&lt;/p&gt;
&lt;h4 id=&#34;get-a-handle&#34;&gt;(7) Get a &lt;code&gt;handle_interrupt&lt;/code&gt; on things&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt; is One Weird Trick &lt;code&gt;Thread#kill&lt;/code&gt; Calls Don’t Want You To Know™. If we’re gonna discuss it, might as well go deep…&lt;/p&gt;
&lt;h4 id=&#34;thread-handle-interrupt&#34;&gt;&lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-c-handle_interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A thread can be externally “interrupted” by a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Thread#kill&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread#raise&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Your program being exited&lt;/li&gt;
&lt;li&gt;A signal, like Ctrl+C&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;handle_interrupt&lt;/code&gt; gives you the ability to control how your program reacts to 1-3. And it means you can define blocks of code which will &lt;em&gt;guarantee&lt;/em&gt; their &lt;code&gt;ensure&lt;/code&gt; blocks run.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;handle_interrupt&lt;/code&gt; is a low-level interface and it’s also the one you are least likely to ever need. You’ll see it used in things like threaded web and job servers where low-level control and better cleanup guarantees are helpful. You’ll find examples of it in Sidekiq, the Async fiber scheduler, Homebrew, the parallel gem and more.&lt;/p&gt;
&lt;p&gt;When you need the strongest guarantees possible about cleaning up your code in response to “interruption”, &lt;code&gt;handle_interrupt&lt;/code&gt; is what you need.&lt;/p&gt;
&lt;p&gt;Let’s look at a simple example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class KnockItOffError &amp;lt; StandardError; end

t = Thread.new do
  sleep 2
  puts &amp;quot;done!&amp;quot;
end
t.join(1)
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
sleep 0.1

puts thread_status(t)
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: 👊&amp;quot; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run that code 👆 and you’ll never see “done!” print. This is the same type of code we saw in the &lt;a href=&#34;#thread-raise-and-kill&#34;&gt;&lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;&lt;/a&gt; section. What can &lt;code&gt;handle_interrupt&lt;/code&gt; do for us?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(KnockItOffError =&amp;gt; :never) do
    sleep 2
    puts &amp;quot;done!&amp;quot;
  end
end

t.join(1)
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
sleep 0.1

puts thread_status(t)
# done!
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: 👊&amp;quot; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we see “done!” printed! To be clear, the error will still be raised &lt;em&gt;eventually&lt;/em&gt;. It can only impact the section it encloses, so the error will be raised right after.&lt;/p&gt;
&lt;p&gt;What’s with the interface - what does &lt;code&gt;KnockItOffError =&amp;gt; :never&lt;/code&gt; mean? Let’s break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;handle_interrupt&lt;/code&gt; takes a hash. Each key is an exception class object, and each value is a symbol representing how to respond to the exception&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KnockItOffError&lt;/code&gt; in our example represents the class that will be handled. Descendants of the class are included, so it could be &lt;code&gt;KnockItOffError&lt;/code&gt; or any of its descendants.&lt;/li&gt;
&lt;li&gt;There are three different symbols allowed as values:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:never&lt;/code&gt; indicates that the exception will “never” interrupt any of the code inside of the block.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:on_blocking&lt;/code&gt; indicates that the exception can only be raised during a “blocking” operation. This includes things like IO read/write, &lt;code&gt;sleep&lt;/code&gt;, and waiting on mutexes. Anything that releases the GVL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:immediate&lt;/code&gt; indicates that the exception should be handled “immediately”. This is effectively the default behavior, so you would generally use this to re-apply an exception ignored at a higher level.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Based on that knowledge, let’s demonstrate a more complex example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:never&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#75715e&#34;&gt;# KnockItOffError can &amp;#34;never&amp;#34; run here&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:immediate&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# KnockItOffError runs &amp;#34;immediate&amp;#34;ly here&lt;/span&gt;
      sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done!&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# KnockItOffError can &amp;#34;never&amp;#34; run here&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Can&amp;#39;t touch this!&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊&amp;#34;&lt;/span&gt;))
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;# Can&amp;#39;t touch this!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this example, “done!” is never printed because it is in the &lt;code&gt;:immediate&lt;/code&gt; block. But we successfully print out “Can’t touch this!” message in our ensure, because we’re within the &lt;code&gt;:never&lt;/code&gt; block for &lt;code&gt;KnockItOffError&lt;/code&gt;. &lt;code&gt;ensure&lt;/code&gt; is now… &lt;em&gt;ensured&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;We’ve used &lt;code&gt;:never&lt;/code&gt; and &lt;code&gt;:immediate&lt;/code&gt;, what about &lt;code&gt;:on_blocking&lt;/code&gt;?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(
    &lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:on_blocking&lt;/span&gt;
  ) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#ae81ff&#34;&gt;1_000_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👋&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊&amp;#34;&lt;/span&gt;))
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;i: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;i&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;# i: 1000000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Our increments work fine, as indicated by &lt;code&gt;i&lt;/code&gt; being one million. But our &lt;code&gt;puts&lt;/code&gt; is a “blocking” call so it gets the boot.&lt;/p&gt;
&lt;p&gt;Should we have used a thread safe counter? Let’s try it again using &lt;code&gt;Concurrent::AtomicFixnum&lt;/code&gt; from &lt;code&gt;concurrent-ruby&lt;/code&gt;, and two threads. We should see &lt;code&gt;i&lt;/code&gt; as two million afterwards:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;concurrent&amp;#34;&lt;/span&gt;
	
i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Concurrent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;AtomicFixnum&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;incrementing_thread&lt;/span&gt;(i)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(
      &lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:on_blocking&lt;/span&gt;
    ) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#ae81ff&#34;&gt;1_000_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
        i&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;increment
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👋&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; incrementing_thread(i)
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; incrementing_thread(i)
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;)
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊&amp;#34;&lt;/span&gt;))
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊👊&amp;#34;&lt;/span&gt;))
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;i: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts thread_status(t)
puts thread_status(t2)
&lt;span style=&#34;color:#75715e&#34;&gt;# i: 1237719&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wait, why? &lt;code&gt;i&lt;/code&gt; is &lt;code&gt;1237719&lt;/code&gt;? Why is &lt;code&gt;i&lt;/code&gt; not two million? What blocked?!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 You’ll definitely see a different number. Sometimes you’ll see &lt;code&gt;1000000&lt;/code&gt;, sometimes you’ll see a higher number, but you’ll pretty much never see &lt;code&gt;2000000&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As it turns out, &lt;code&gt;Concurrent::AtomicFixnum&lt;/code&gt; uses a &lt;code&gt;Mutex&lt;/code&gt; by default. If a &lt;code&gt;Mutex&lt;/code&gt; waits to acquire a lock it is considered a blocking operation! That means it qualifies for &lt;code&gt;:on_blocking&lt;/code&gt; and the error gets raised.&lt;/p&gt;
&lt;p&gt;As a specific fix for &lt;code&gt;AtomicFixnum&lt;/code&gt;, if you install the &lt;code&gt;concurrent-ruby-ext&lt;/code&gt; gem then you get native extensions which are lock-free, no longer use a &lt;code&gt;Mutex&lt;/code&gt;, and properly run our code.&lt;/p&gt;
&lt;p&gt;Once we install &lt;code&gt;concurrent-ruby-ext&lt;/code&gt;, we properly get &lt;code&gt;2000000&lt;/code&gt;!:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# You ran `gem install concurrent-ruby-ext`&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# It automatically loads if present&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;i: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts thread_status(t)
puts thread_status(t2)
&lt;span style=&#34;color:#75715e&#34;&gt;# i: 2000000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But we also know that a &lt;code&gt;Mutex&lt;/code&gt; or any other locking/waiting behavior can cause our &lt;code&gt;:on_blocking&lt;/code&gt; interrupt to fire. So &lt;code&gt;:on_blocking&lt;/code&gt; can have surprising behavior if some other internal of the code were to change later.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;If the thread hasn’t started yet, &lt;code&gt;handle_interrupt&lt;/code&gt; won’t help you. The error will be raised immediately in the thread, before &lt;code&gt;handle_interrupt&lt;/code&gt; can be called:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(
    KnockItOffError =&amp;gt; :never
  ) do
    puts &amp;quot;welcome!&amp;quot;
    puts &amp;quot;later 👋&amp;quot;
  end
end
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
sleep 1
puts thread_status(t)
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: 👊&amp;quot; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;What happens after &lt;code&gt;handle_interrupt&lt;/code&gt;? Once the error is allowed to raise, code directly after it won’t run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(
    KnockItOffError =&amp;gt; :never
  ) do
    puts &amp;quot;can&#39;t stop won&#39;t stop&amp;quot;
    Thread.handle_interrupt(
      KnockItOffError =&amp;gt; :immediate
    ) do
      sleep 2
      puts &amp;quot;this won&#39;t run&amp;quot;
    end
    puts &amp;quot;this won&#39;t run either&amp;quot;
  end
end

t.join(1)
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
sleep 0.1
puts thread_status(t)
# can&#39;t stop won&#39;t stop
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: 👊&amp;quot; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But code after the inner &lt;code&gt;handle_interrupt&lt;/code&gt; &lt;em&gt;could&lt;/em&gt; run, it just depends on if the previous block raises. In this example, &lt;em&gt;all&lt;/em&gt; of the code runs successfully because we don’t raise an error during the inner block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(
    KnockItOffError =&amp;gt; :never
  ) do
    puts &amp;quot;can&#39;t stop won&#39;t stop&amp;quot;
    Thread.handle_interrupt(
      KnockItOffError =&amp;gt; :immediate
    ) do
      sleep 2
      puts &amp;quot;this won&#39;t run&amp;quot;
    end
    sleep 2
    puts &amp;quot;this won&#39;t run either&amp;quot;
  end
end

t.join(3)
t.raise(KnockItOffError.new(&amp;quot;👊&amp;quot;))
t.join # FIXME raises?
puts thread_status(t)
# can&#39;t stop won&#39;t stop
# this won&#39;t run
# this won&#39;t run either
#&amp;lt;ThreadStatus status=&amp;quot;failed w/ error: 👊&amp;quot; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But you’re better off guaranteeing code after the block runs. Use an &lt;code&gt;ensure&lt;/code&gt; to make sure even if the inner block raises an error your code still runs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(
    &lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:never&lt;/span&gt;
  ) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;can&amp;#39;t stop won&amp;#39;t stop&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;handle_interrupt(
      &lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:immediate&lt;/span&gt;
    ) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;this won&amp;#39;t run&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;this will consistently run&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;raise(&lt;span style=&#34;color:#66d9ef&#34;&gt;KnockItOffError&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;👊&amp;#34;&lt;/span&gt;))
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;# can&amp;#39;t stop won&amp;#39;t stop&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# this will consistently run&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: 👊&amp;#34; error=#&amp;lt;KnockItOffError: 👊&amp;gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;Can we even stop the unstoppable &lt;code&gt;Thread#kill&lt;/code&gt;? Yep! From the &lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-c-handle_interrupt-label-Inheritance+with+ExceptionClass&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt; docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For handling all interrupts, use Object and not Exception as the ExceptionClass, as kill/terminate interrupts are not handled by Exception.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we can handle it - but we have to use &lt;code&gt;Object&lt;/code&gt;, which looks a bit odd but works well:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(Object =&amp;gt; :never) do
    sleep 2
    puts &amp;quot;done!&amp;quot;
  end
end

t.join(1)
t.kill
t.join
puts thread_status(t)
# done!
#&amp;lt;ThreadStatus status=&amp;quot;finished&amp;quot; error=nil&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason for this odd syntax is that the &lt;code&gt;kill&lt;/code&gt;/&lt;code&gt;terminate&lt;/code&gt; interrupts are internally handled &lt;a href=&#34;https://bugs.ruby-lang.org/issues/15735&#34;&gt;not as Exception instances, but as integers&lt;/a&gt;. That means this would also work:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t = Thread.new do
  Thread.handle_interrupt(
    Integer =&amp;gt; :never
  ) do
    sleep 2
    puts &amp;quot;done!&amp;quot;
  end
end

t.join(1)
t.kill
t.join
puts thread_status(t)
# done!
#&amp;lt;ThreadStatus status=&amp;quot;finished&amp;quot; error=nil&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still, you’re better off using &lt;code&gt;Object&lt;/code&gt; to avoid the implementation detail.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;Can we stop the &lt;code&gt;timeout&lt;/code&gt; gem from raising at a bad time using &lt;code&gt;handle_interrupt&lt;/code&gt;? The Thread API docs used to &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Thread.html#method-c-handle_interrupt-label-Guarding+from+Timeout-3A-3AError&#34;&gt;specifically use &lt;code&gt;timeout&lt;/code&gt; as a use-case for &lt;code&gt;handle_interrupt&lt;/code&gt;&lt;/a&gt;, but &lt;a href=&#34;https://github.com/ruby/timeout/issues/41&#34;&gt;there’s a non-determinism bug&lt;/a&gt; around thread reuse within the &lt;code&gt;timeout&lt;/code&gt; gem.&lt;/p&gt;
&lt;p&gt;So once again, &lt;a href=&#34;#dont-use-timeout&#34;&gt;don’t use the &lt;code&gt;timeout&lt;/code&gt; gem&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&#34;https://github.com/ruby/ruby/pull/11474&#34;&gt;removed the example from the docs&lt;/a&gt; because it’s too broken, so on Ruby 3.4+, the docs no longer mention &lt;code&gt;handle_interrupt&lt;/code&gt; with the &lt;code&gt;timeout&lt;/code&gt; gem.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;We’ve looked at many &lt;code&gt;handle_interrupt&lt;/code&gt; examples - what do real gems use it for?&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&#34;#&#34;&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt; gem &lt;a href=&#34;https://github.com/socketry/async/blob/main/lib/async/scheduler.rb&#34;&gt;it uses &lt;code&gt;handle_interrupt&lt;/code&gt;&lt;/a&gt; to ignore &lt;code&gt;SignalException&lt;/code&gt; while it shuts down its child tasks:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Stop all children, including transient children, ignoring any signals.
def stop
  Thread.handle_interrupt(::SignalException =&amp;gt; :never) do
    @children&amp;amp;.each do |child|
      child.stop
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;sidekiq&lt;/code&gt;, when it has gracefully attempted a shutdown and is forcing threads to finish, it raises a special error. That error extends &lt;code&gt;Interrupt&lt;/code&gt;, which means most &lt;code&gt;rescue&lt;/code&gt; blocks will not capture it because it is a child of &lt;code&gt;Exception&lt;/code&gt; rather than &lt;code&gt;StandardError&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module Sidekiq
  class Shutdown &amp;lt; Interrupt; end
end
# later...
t.raise(Sidekiq::Shutdown)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To avoid &lt;code&gt;Sidekiq::Shutdown&lt;/code&gt; breaking &lt;em&gt;everything&lt;/em&gt; (including its own internal code), Sidekiq also uses &lt;code&gt;handle_interrupt&lt;/code&gt; to ignore the error in a small piece of shutdown code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown =&amp;gt; :never}
ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown =&amp;gt; :immediate}

def process(uow)
  jobstr = uow.job
  queue = uow.queue_name
     
  #... process logic
  ack = false
  Thread.handle_interrupt(
    IGNORE_SHUTDOWN_INTERRUPTS
  ) do
    Thread.handle_interrupt(
      ALLOW_SHUTDOWN_INTERRUPTS
    ) do
      dispatch(...) do |inst|
        config.server_middleware... do
          execute_job(...)
        end
      end
      ack = true
    rescue Sidekiq::Shutdown
      # Had to force kill this job because it didn&#39;t finish
      # within the timeout.  Don&#39;t acknowledge the work since
      # we didn&#39;t properly finish it.
  ensure
    if ack
      uow.acknowledge
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If this section hasn’t been enough for you, Ben Sheldon gives some additional interesting examples In his article &lt;a href=&#34;https://island94.org/2023/08/appropriately-using-rubys-thread-handle_interrupt&#34;&gt;Appropriately using Ruby’s thread &lt;code&gt;handle_interrupt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;ensure-success&#34;&gt;The way you &lt;code&gt;ensure&lt;/code&gt; success&lt;/h4&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/40e94602-9e2b-4dd2-a60f-3a407944cbe8.jpg&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;
&lt;p&gt;I’m pretty confident, started from scratch, Ruby would not be implemented with &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt; again. I don’t know _which _ model they would choose - but something like a &lt;a href=&#34;https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html&#34;&gt;Java interrupt&lt;/a&gt; would be a good start. And minimally, making &lt;a href=&#34;https://bsky.app/profile/headius.bsky.social/post/3lvnkypqewk2b&#34;&gt;all &lt;code&gt;ensure&lt;/code&gt; blocks uninterruptable&lt;/a&gt;, as well as &lt;a href=&#34;https://www.manageiq.org/blog/2017/09/finalizers-can-be-interrupted-from-time-to-time/&#34;&gt;all finalizers&lt;/a&gt;. I didn’t even get into finalizers - they’re a less common, but also important area that you &lt;em&gt;really&lt;/em&gt; don’t want to interrupt.&lt;/p&gt;
&lt;p&gt;Ruby is one of the only programming languages that lets you kill a thread from outside of the thread. It’s powerful, but mostly, it’s dangerous. It’s one of the sharpest tools available to you, and it should be used &lt;em&gt;sparingly&lt;/em&gt;, or ideally not at all.&lt;/p&gt;
&lt;p&gt;In threaded code, the best offense is a good defense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consider how to safely manage your threads. If your thread is going to do a lot of work, or expensive work, make sure you have an escape hatch (like a boolean check for interrupts)&lt;/li&gt;
&lt;li&gt;Treat performance issues like a bug. If you have threads that are hogging resources and timing things out, you need to fix them. &lt;code&gt;kill&lt;/code&gt;ing them is not a long-term answer&lt;/li&gt;
&lt;li&gt;Ultimately, there is no way to guarantee the safety of your code 100% of the time. The OS could kill your program. Your server could get unplugged. You’re always going to have edge cases that you can’t foresee. But control what you can, and be aware of what can go wrong.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now go forth, armed with the knowledge on what to do when good threads go bad.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Unless you’re in SQLite, where apparently N+1 queries are a virtue 😲 &lt;a href=&#34;https://www.sqlite.org/np1queryprob.html&#34;&gt;https://www.sqlite.org/np1queryprob.html&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In general, you shouldn’t do this directly&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And processes.&lt;/p&gt;
&lt;p&gt;Or use Falcon. See me later in the series when we talk about Fibers 😏&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There’s a small mention about how to handle Timeout errors, but it doesn’t explain much or warn at all&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2025/whengoodthreadsgobad.jpg)

&gt; 👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into several parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt; - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my!](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - When good threads go bad
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “When good threads go bad”. I’ll update the links as each part is released, and include these links in each post.

- [Threads out after curfew](#threads-curfew)
- [Stuck threads](#stuck-threads)
	- [Deadlocks](#deadlocks)
	- [Livelocks](#livelocks)
	- [Long-running CPU](#long-running-cpu)
	- [Long-running IO](#long-running-io)
	- [Native extensions](#native-extensions)
	- [What happened to our Puma server, anyways?](#what-happened-to-puma)
- [Thread shutdown](#thread-shutdown)
	- [`raise` and `kill`](#thread-raise-kill)
		- [Don’t `kill` threads](#dont-kill-threads)
		- [Interrupt your threads safely](#interrupt-safely)
		- [`ensure` cleanup](#ensure-cleanup)
		- [Don’t use `timeout`](#dont-use-timeout)
		- [Use `term_on_timeout` with `rack-timeout`](#use-term-on-timeout)
		- [Monitor your thread costs](#monitor-your-thread-costs)
		- [Get a `handle_interrupt` on things](#get-a-handle)
	- [`Thread.handle_interrupt`](#thread-handle-interrupt)
	- [The way you `ensure` success](#ensure-success)

&lt;h2 id=&#34;threads-curfew&#34;&gt;Threads out after curfew 🧵 &lt;/h2&gt;

It’s late, and you start getting alerts that requests to your web server are failing. You try to load a page and it hangs endlessly. The server isn’t responding to anything, and requests are continuing to queue up.

![](https://cdn.uploads.micro.blog/98548/2025/threads-do-you-know-where-your-threads-are.drawio-2.png)

&gt; It’s 10PM. Do you know where your &lt;s&gt;children&lt;/s&gt; *threads* are?
&gt; 
&gt; 📺 [iykyk](https://en.wikipedia.org/wiki/Do_you_know_where_your_children_are%3F)

Not knowing what else to do, you trigger a server restart. Even doing that, things remain unresponsive for another 30 seconds. Finally you see the server stop, and start up again. As if by magic, everything is running fine again.

You’re running Puma with threads. It seemed like every thread was unresponsive. What happened to those threads?!

&lt;h3 id=&#34;stuck-threads&#34;&gt;Stuck threads&lt;/h3&gt;

The reality of the situation is probably mundane.

1. Are you looking up data in a query? Did you remember to index your columns?
2. Do you have [N+1 queries?](https://www.namitjain.com/blog/n-plus-1-query-problem)[^1]
3. Did you remember to [set statement and lock timeouts](https://github.com/ankane/strong_migrations?tab=readme-ov-file#migration-timeouts) before running a schema migration?
4. Are you _sure_ it isn’t 1, 2 or 3?

If it _isn’t_ those things, there are other ways your threads can go rogue. Let’s look at some ways a thread can get stuck:

- Deadlocks
- Livelocks
- Long-running CPU
- Long-running IO
- Native extensions

&lt;h4 id=&#34;deadlocks&#34;&gt;&lt;a href=&#34;https://en.m.wikipedia.org/wiki/Deadlock_(computer_science)&#34;&gt;Deadlocks&lt;/a&gt;&lt;/h4&gt;

The conventional example of a deadlock is two [threads attempting to acquire a mutex](https://jpcamara.com/2024/08/26/the-thread-api.html#mutex)held by the other thread. 

They can never make progress, so they’re dead in the water:

	mutex_1 = Mutex.new
	mutex_2 = Mutex.new
	thread_1 = Thread.new do
	  mutex_1.synchronize do
	    sleep 1
	    mutex_2.lock
	  end
	end
	thread_2 = Thread.new do
	  mutex_2.synchronize do
	    sleep 1
	    mutex_1.lock
	  end
	end
	thread_1.join

In our example, `thread_1` acquires `mutex_1`. Then `thread_2` acquires `mutex_2`. Next, `thread_1` attempts to acquire `mutex_2` and blocks. Then `thread_2` attempts to require `mutex_1` and blocks. Neither can make progress, and are stuck in place.

![](https://cdn.uploads.micro.blog/98548/2025/whengoodthreadsgobad.gif)

It’s a traditional example, but Ruby is very good at detecting it! It detects the problem and raises an error, checking if any threads are capable of making progress:

```text
&#39;Thread#join&#39;: No live threads left. Deadlock? (fatal)
  3 threads, 3 sleeps current:0x0000000a35e90c00 main thread:0x0000000101262de0
  * #&lt;Thread:0x00000001001fafa8 sleep_forever&gt;
    rb_thread_t:0x0000000101262de0 native:0x0000000202cfc800 int:0
	   
  * #&lt;Thread:0x0000000121727c08 (irb):101 sleep_forever&gt;
  rb_thread_t:0x0000000a35e90e00 native:0x00000001700f7000 int:0 mutex:0x0000000a3638c000 cond:1
    depended by: tb_thread_id:0x0000000101262de0
	   
  * #&lt;Thread:0x00000001217279d8 (irb):107 sleep_forever&gt;
  rb_thread_t:0x0000000a35e90c00 native:0x0000000170203000 int:0
```
Ruby detects that `thread_1` and `thread_2` are sleeping, and the third thread is `Thread.main`, which sleeps waiting for `thread_1` to finish.

Most long running programs are likely to have _some_ other thread running, and Ruby only detects if _all_ threads are stuck. We can get our deadlock example to work by adding an extra thread in a work loop:

	mutex_1 = Mutex.new
	mutex_2 = Mutex.new
	thread_1 = Thread.new do
	  mutex_1.synchronize do
	    sleep 1
	    mutex_2.lock
	  end
	end
	thread_2 = Thread.new do
	  mutex_2.synchronize do
	    sleep 1
	    mutex_1.lock
	  end
	end
	
	thread_3 = Thread.new do
	  loop do
	    # process some work...
	  end
	end
	
	thread_1.join

Now threads 1 and 2 will never progress, and Ruby lets the program continue running because `thread_3` is still active.

&lt;h4 id=&#34;livelocks&#34;&gt;Livelocks&lt;/h4&gt;

While a deadlock means a thread has stopped processing, a livelock happens when a thread keeps running, but never makes any progress. Here’s another example using an alternative approach for acquiring a mutex:

	mutex_1 = Mutex.new
	mutex_2 = Mutex.new
	thread_1 = Thread.new do
	  mutex_1.synchronize do
	    sleep 0.1
	    while !mutex_2.try_lock; end
	  end
	end
	thread_2 = Thread.new do
	  mutex_2.synchronize do
	    sleep 0.1
	    while !mutex_1.try_lock; end
	  end
	end
	thread_1.join

This is similar to our deadlock example, but this time we use `#try_lock` instead of `#lock`. Unlike `#lock`, which blocks until the mutex is available, `#try_lock` returns `false` if attempting the lock fails. We do a short `sleep` in each thread to give them time to acquire the initial locks, then iterate infinitely attempting `#try_lock`. The locks will never be acquired, and the loops will run forever. Burn, CPU, burn 🔥.

Personally I’ve rarely encountered deadlocks and livelocks in threaded code. But I’ve definitely encountered them in databases!

	thread_1 = Thread.new do
	  User.find(1).with_lock do
	    sleep 0.1
	    User.find(2).lock!
	  end
	end
	
	thread_2 = Thread.new do
	  User.find(2).with_lock do
	    sleep 0.1
	    User.find(1).lock!
	  end
	end
	
	thread_1.join

On PostgreSQL, it will detect this deadlock and raise an error:

```text
PG::TRDeadlockDetected: ERROR:  deadlock detected (ActiveRecord::Deadlocked)
DETAIL:  Process 581 waits for ShareLock on transaction 4003; blocked by process 582.
Process 582 waits for ShareLock on transaction 4002; blocked by process 581.
CONTEXT:  while locking tuple (0,1) in relation &#34;users&#34;
```
How do we solve deadlocks and livelocks? The answer is a consistent order for locking.

	mutex_1 = Mutex.new
	mutex_2 = Mutex.new
	thread_1 = Thread.new do
	  mutex_1.synchronize do
	    sleep 1
	    mutex_2.lock
	  end
	end
	thread_2 = Thread.new do
	  mutex_1.synchronize do
	    sleep 1
	    mutex_2.lock
	  end
	end
	
	thread_3 = Thread.new do
	  loop do
	    # process some work...
	  end
	end
	
	thread_1.join

This is the same example as before, but this time both threads attempt to acquire mutexes in _identical_ order. As long as you acquire in a consistent order, you should never hit deadlocks or livelocks.

&lt;h4 id=&#34;long-running-cpu&#34;&gt;Long-running CPU&lt;/h4&gt;

As far as I know, this isn’t _actually_ possible in pure Ruby. What I mean by “pure” Ruby is a program that only runs Ruby code, and no C/Rust/Zig extensions. The CRuby runtime controls how pure Ruby code runs, and makes sure we can’t hog threads. _Mostly_.

In [Bitmasks, Ruby Threads and Interrupts, oh my!](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html), we dug into the [`TIMER_INTERRUPT_MASK`](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html#timer-interrupt-mask)and how it utilizes priority. It allows a thread to influence how large of a time slice it gets:

	def calculate_priority(priority, limit)
	  priority &gt; 0 ? limit &lt;&lt; priority : limit &gt;&gt; -priority
	end
		
	calculate_priority(0, 100)        # =&gt; 100ms
	calculate_priority(2, 100)        # =&gt; 400ms
	calculate_priority(-2, 100)       # =&gt; 25ms

At a default priority of 0, we get Ruby’s default time slice of 100ms. At -2, we get 25ms. At 2, we get 400ms. This means in theory, can we starve out other threads by increasing our priority?

	calculate_priority(5, 100)       # =&gt; 3276800ms

3,276,800 milliseconds is 54 minutes. Can we really block things for 54 minutes!?

	counts = Array.new(10, 0)
	done = false
	
	threads = 10.times.map do |i|
	  Thread.new do
	    j = 0
	    loop do
	      break if done
	      j += 1
	      counts[i] += 1 if j % 1_000_000 == 0
	    end
	  end.tap do |t|
	    t.priority = 15 if i == 5
	  end
	end
	
	sleep 10
	done = true
	counts.each_with_index { |c, i| puts &#34;#{i}: #{c}&#34; }

There’s a bunch going on here - but there are only a few details to focus on:

1. For the 6th thread (`i == 5`), we set the priority to 15. That’s the priority that in theory gives us a time slice of 54 minutes.
2. We set a count every million iterations in each thread.
3. We sleep for 10 seconds, then set a killswitch on each thread by setting `done` to `false`.
4. We print how many times each thread was able to increment their slice of the array.

Here’s the output:

```text
0: 26
1: 27
2: 26
3: 27
4: 28
5: 189
6: 24
7: 24
8: 24
9: 24
```
As we can see - the priority made a difference! The thread at index `5` runs around 7 times as much as every other thread. _But_, it does not fully hog the thread like we might have thought. This is only 10 seconds, well under 54 minutes, and the other threads still get prioritized by the scheduler.

This shows that we can _influence_ the scheduler, but we can’t completely hog the runtime from pure Ruby code.

In more practical Ruby code, the [Sidekiq](https://github.com/sidekiq/sidekiq) gem gives you the ability to set priority on the threads it creates:

	Sidekiq.configure_server do |cfg|
	  cfg.thread_priority = 15
	end

Interestingly, by default, Sidekiq sets its threads to a priority of -1, which is less than the `0` that Ruby uses by default. It describes the rationale:

&gt; Ruby’s default thread priority is 0, which uses 100ms time slices. This can lead to some surprising thread starvation; if using a lot of CPU-heavy concurrency, it may take several seconds before a Thread gets on the CPU.
&gt; 
&gt; Negative priorities lower the timeslice by half, so -1 = 50ms, -2 = 25ms, etc. With more frequent timeslices, we reduce the risk of unintentional timeouts and starvation.

Fascinating to see a real-world use-case of priority like this! Sidekiq has run trillions of jobs across hundreds of thousands of apps, and they made the decision to switch the priority.

Since Ruby 3.4, you can achieve the same thing globally by using [`RUBY_THREAD_TIMESLICE`](https://bugs.ruby-lang.org/issues/20861). You can set `RUBY_THREAD_TIMESLICE=50` and keep the priority the same, but now the time slice is 50ms.

&lt;h4 id=&#34;long-running-io&#34;&gt;Long-running IO&lt;/h4&gt;

This is the likeliest scenario that will saturate your threads: long-running IO. In [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html), we discussed how threads are great at handing off work when blocked on IO:

&gt; As soon as you do any IO operation, it just parks that thread/fiber and resumes any other one that isn’t blocked on IO.

However, this only works as long as:

- You have other threads available to pickup work
- You’ve tuned your thread counts based on your workload. [Amdal’s Law](https://youtu.be/6HaXuQJMcvs?si=kC9e7GWZF-PIqGde&amp;t=161) helps you decide how many threads you should run, based on how much IO is running in your code

Earlier we mentioned slow queries as a possible IO blocker. But let’s say you have a web server running with 5 threads, and you allow users to download files[^2]:

	class DownloadsController &lt; ApplicationController
	  def index
	    send_file Rails.root.join(&#34;public/large.txt&#34;),
	      filename: &#34;large.txt&#34;,
	      type: &#34;application/octet-stream&#34;
	  end
	end

We have a simple Rails controller action, which sends a file to the client making the request. It’s pretty straightforward! The `large.txt` file I tested with locally is about 130mb. I’ll run it in a basic Puma setup, using 5 threads:

```text
bundle exec puma -t 5:5
	
Puma starting in single mode...
 * Puma version: 7.1.0 (&#34;Neon Witch&#34;)
 * Ruby version: ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin23]
 *  Min threads: 5
 *  Max threads: 5
```
I’m running a Puma server with 5 threads. Let’s try some benchmarks against it. We’ll use [Apache Bench](https://httpd.apache.org/docs/2.4/programs/ab.html) (`ab`) to simulate traffic to Puma. `-n` means the number of total requests, and `-c` is how many concurrent requests to make. Let’s start with 1 request:

```text
ab -n 1 -c 1 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   0.070 seconds
Complete requests:      1
```
Cool - 0.07 seconds. How about 3?

```text
ab -n 3 -c 3 http://0.0.0.0:9292/downloads
	
Concurrency Level:      3
Time taken for tests:   0.074 seconds
Complete requests:      3
```
Not much change from a single request! How about 5? This would match the maximum number of simultaneous requests our server can currently support, using 5 threads:

```text
ab -n 5 -c 5 http://0.0.0.0:9292/downloads
	
Concurrency Level:      5
Time taken for tests:   0.134 seconds
Complete requests:      5
```
Things slow down a _little_ bit once we max out our threads. But still reasonable. _How about 50_?

```text
ab -n 50 -c 50 http://0.0.0.0:9292/downloads
	
Concurrency Level:      50
Time taken for tests:   0.820 seconds
Complete requests:      50
```
So far, so good. But we’re benefiting from a lot here: the file isn’t _particularly_ large, and there is _no_ latency. The server is responding quickly, and the client is consuming the response quickly. Let’s switch things up a bit - how well does it handle a client downloading slowly?

We’ll use `curl` to simulate a slow client. We can use `limit-rate` to simulate a client downloading only 1000k per second. `curl ... --limit-rate 1000k &amp;` means we’ll download results at a rate of 1000k per second, running in the background (`&amp;`). This means it will take our `curl` call 2 or 3 minutes to download a 130mb file. At the same time, we’ll run another apache bench to see how things perform:

```text
curl http://0.0.0.0:9292/downloads -o /tmp/large.txt \
  --limit-rate 1000k &amp;
	
ab -n 1 -c 1 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   0.056 seconds
Complete requests:      1
```
Puma responds quickly. In this scenario, `curl` is occupying one thread, and `ab` occupies another. Let’s try running 4 curl commands:

```bash
for i in {1..4}; do
  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt \
    --limit-rate 1000k &amp;
done
	
ab -n 1 -c 1 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   0.059 seconds
Complete requests:      1
```
Puma still responds fine. `curl` is now occupying 4 threads, and `ab` uses the remaining 1 thread. Let’s add _one_ more request using `curl`. We also increase the default timeout of `ab` (which is 30) to _200_, for no particular reason…

```bash
for i in {1..5}; do
  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt \
    --limit-rate 1000k &amp;
done
	
ab -n 1 -c 1 -s 200 http://0.0.0.0:9292/downloads
	
Concurrency Level:      1
Time taken for tests:   124.477 seconds
Complete requests:      1
```
**Yikes**. That did **not** go well. The moment we had 5 slow running requests from our `curl` calls, we saturated all available threads. Our 6th request using `ab` sat around waiting, finally finishing 2 _minutes_ later!

This is a **critical** consideration - long-running web work is a throughput killer. Ideally, keep all work as fast as possible and offload long-running work to jobs/other services. Most commonly for a download you’d create a [presigned url](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html) for a service like S3 and redirect to that URL.

If you _need_ to run long-running IO, you need to allocate many more threads[^3].

&lt;h4 id=&#34;native-extensions&#34;&gt;Native extensions&lt;/h4&gt;

In our [Long-running CPU](#long-running-cpu) example, we couldn’t get pure Ruby code to completely hog the runtime. We can prioritize a thread higher than other threads, but work still continues to be distributed.

Once you start running native extensions, the Ruby runtime has more limited influence. It’s up to the extension to properly interface with Ruby and yield control back. Here’s a simple example that will block all other threads, using the standard `openssl` gem, using a function written in C, `pbkdf2_hmac`:

	require &#34;openssl&#34;
	
	Thread.new do
	  loop do
	    puts &#34;tick #{Time.now}&#34;
	  end
	end
	
	t = Thread.new do
	  loop do
	    OpenSSL::KDF.pbkdf2_hmac(&#34;passphrase&#34;, salt: &#34;salt&#34;, iterations: 12_800_000, length: 32, hash: &#34;sha256&#34;)
	  end
	end
	
	t.join

We have two threads running - one infinitely printing the time in a loop, and one infinitely calling `pbkdf2_hmac` in a loop. Here I give `pbkdf2_hmac` a ludicrous number of iterations to force the function to run, in C, for a long period of time:

```text
tick 2025-12-29 15:20:30 -0500
tick 2025-12-29 15:20:45 -0500
tick 2025-12-29 15:20:59 -0500
```
The results show that despite each thread having the same priority, the thread with long-running C extension code hogs all of the runtime. The printing thread is able to print a timestamp roughly every 15 seconds.

There’s nothing the extension code is doing _wrong_ per se, but because it runs purely in C, without yielding back to Ruby until its done, Ruby can’t do anything to keep work distribution fair.

In most cases, well-developed/mature native extensions won’t hit this issue. There are many popular gems that are, or include, native extensions. But if you _do_ hit an expensive path in a C extension, be aware the Ruby runtime will not be able to control it. If you _know_ you are interfacing with a slow piece of code in a native extension, keep it off the hot path, same as our long-running IO example.

&lt;h4 id=&#34;what-happened-to-puma&#34;&gt;What happened to our Puma server, anyways?&lt;/h4&gt;

Remember our production panic scenario from earlier?

&gt; Not knowing what else to do, you trigger a server restart. Even doing that, things remain unresponsive for another 30 seconds. Finally you see the server stop, and start up again. As if by magic, everything is running fine again

You triggered a server restart, and still had to wait 30 seconds? Why wasn’t Puma able to stop sooner? Let’s reuse our download example from earlier, and explain some default Puma behaviors!

First, let’s start Puma, and see how quickly we can stop it:

	bundle exec puma -t 5:5
	
	Puma starting in single mode...
	* Puma version: 7.1.0 (&#34;Neon Witch&#34;)
	* Ruby version: ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin23]
	*  Min threads: 5
	*  Max threads: 5
	
	### hit ctrl + c to issue a SIGINT signal
	
	Gracefully stopping, waiting for requests to finish
	=== puma shutdown: 2025-12-29 23:45:21 -0500 ===
	- Goodbye!

We instantly see two things:

- `Gracefully stopping, waiting for requests to finish`
- `Goodbye!`

Puma tells us it is shutting down “gracefully”. With no activity, it is able to instantly stop.

Now let’s use our `DownloadController` again:

	class DownloadsController &lt; ApplicationController
	  def index
	    send_file Rails.root.join(&#34;public/large.txt&#34;),
	      filename: &#34;large.txt&#34;,
	      type: &#34;application/octet-stream&#34;
	  end
	end

We start Puma again, then occupy each thread with a request:

	bundle exec puma -t 5:5
	
	for i in {1..5}; do
	  curl http://0.0.0.0:9292/downloads -o /tmp/large_$i.txt \
	    --limit-rate 1000k &amp;
	done

Now let’s trying issuing `INT` using `ctrl+c` again:

	Puma starting in single mode...
	...
	Use Ctrl-C to stop
	
	^C &lt;-- ctrl + c
	
	... ~120 second pass, all downloads finish ...
	
	- Gracefully stopping, waiting for requests to finish
	=== puma shutdown: 2025-12-30 00:04:23 -0500 ===
	- Goodbye!

Ok, so we issued our interruption. But… it waited for every request to _completely_ finish! We had to wait around 120 seconds before our server shutdown. That’s even worse than earlier!

This isn’t a blog post about Puma specifically, but I’ll discuss a few factors:

1. Running `puma -t 5:5` puts you in Puma “single” mode. This is a lightweight way of running puma, at the cost of less control. By default, single-mode Puma does not kill any requests. Like it tells us, it simply is “waiting for requests to finish”
2. Alternatively, you can run puma in “cluster” mode. This is done by adding any worker count at all using the `-w` flag. Even a `-w 1` will move you into cluster mode. Cluster mode has a parent worker which monitors and controls child workers. This costs more memory, but it means the parent worker can kill child workers more reliably

Let’s try this one more time, starting in cluster mode by setting `-w 1`:

	bundle exec puma -t 5:5 -w 1
	
	[39363] Puma starting in cluster mode...
	[39363] *  Min threads: 5
	[39363] *  Max threads: 5
	[39363] *   Master PID: 39363
	[39363] *      Workers: 1
	[39363] Use Ctrl-C to stop
	^C &lt;-- ctrl + c
	[45110] - Gracefully shutting down workers...
	
	... ~30 second pass, downloads are cut early ...
	
	[45110] === puma shutdown: 2025-12-30 00:24:21 -0500 ===
	[45110] - Goodbye!

This time we replicate our earlier behavior - 30 seconds pass, and the server is shutdown. Now that we’re running in cluster mode, by default Puma uses a configuration called `worker_shutdown_timeout`, which defaults to 30 seconds. If you have a configuration file, you can set it yourself to something longer or shorter:

	worker_shutdown_timeout 25 # instead of 30

As well, by default Puma _never_ kills threads. In a moment we’re going to be talking about ways to kill a thread. Puma plays it extremely safe, and offers _no_ ability to kill individual threads. And even when shutting down, it defaults to a thread shutdown policy of `:forever`, which means the only way the threads are killed is when the server is entirely shutdown, which shuts down the worker the threads live in.

You _can_ change this. In the same configuration file you’d set `worker_shutdown_timeout` you can set `force_shutdown_after`:

	force_shutdown_after 1 # an integer, :forever, or :immediate

Still - this doesn’t do much. It still only impacts full server shutdown. But with this setting, the internal Puma thread pool will `raise` on all threads, and then eventually run `kill` on them.

What all this means is that by default in Puma:

- The _primary_ way to fix misbehaving threads is to restart your server
- This will take up to 30 seconds running in cluster mode, unless you change the `worker_shutdown_timeout`
- Puma can’t kill long-running or stuck threads without a full server shutdown. Technically there is [a control server](https://github.com/puma/puma#controlstatus-server) you can setup, which can manage individual workers. But it cannot kill individual threads/requests

We’ll talk in-depth later about the available option for killing long-running threads in Puma.

&gt; 📝 if you want to dig deeper into how Puma works, I highly recommend [Dissecting Puma: Anatomy of a Ruby Web Server](https://dansvetlov.me/puma-internals/). The [source of Puma](https://github.com/puma/puma) is also pretty readable

&lt;h4 id=&#34;dont-guess&#34;&gt;Don&#39;t guess, measure&lt;/h4&gt;

All of these thread issues are _possibilities_, but they mean nothing without empirical data. Measure, then decide your course of option.

&lt;h3 id=&#34;thread-shutdown&#34;&gt;Thread Shutdown&lt;/h3&gt;

Ok, enough about _how_ threads get stuck. Once they’re stuck - is there a way to stop them?

&lt;h4 id=&#34;thread-raise-kill&#34;&gt;&lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-raise&#34;&gt;&lt;code&gt;raise&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-i-kill&#34;&gt;&lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/dd43f6992d.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;

&gt; You want to kill… me? 🥺

&gt; ⚠️ TL;DR You shouldn’t use these methods unless you _really_ know what you’re doing. Instead, [interrupt your thread safely](#tear-down-safely). Incidentally, you should also [avoid the timeout module](#dont-use-timeout).
&gt; 
&gt; if you’re writing a generic threaded framework you may need it - for custom one-off threads you can probably manage without it

Sometimes a thread is running and you need to shut it down. There’s two primary methods for achieving that: `raise` and `kill`.

`raise` will raise an error inside of the target thread. If the thread hasn’t started yet, in most cases it is killed before running anything:

```ruby
t = Thread.new do
  puts &#34;never runs&#34;
end
t.raise(&#34;knock it off&#34;)
sleep 0.01
puts thread_status(t)
#&lt;ThreadStatus status=&#34;failed w/ error: knock it off&#34;, error=#&lt;RuntimeError: knock it off&gt;
```
&gt; 📝 `thread_status` is the helper we defined in the “Thread API” section on [`status`](https://jpcamara.com/2024/08/26/the-thread-api.html#thread-statuses)

The error isn’t raised instantly - only at the point the thread is scheduled next. We `sleep 0.1` to give thread `t` an opportunity to start. The thread scheduler starts it, and it immediately raises our “knock it off” error, effectively running right _before_ `puts &#34;never runs&#34;`.

If the thread gets a chance to start, the error will be raised on whatever line happened to be running last:

	t = Thread.new do
	  sleep 5
	end
	t.join(1)
	# We&#39;re only one second into the threads sleep at this point, so knock it off is raised from the `sleep 5` line
	t.raise(&#34;knock it off, sleepyhead!&#34;)
	sleep 0.1
	puts thread_status(t)
	#&lt;ThreadStatus status=&#34;failed w/ error: knock it off, sleepyhead!&#34;, error=#&lt;RuntimeError: knock it off, sleepyhead!&gt;

Because it raises whatever error is provided (a `RuntimeError` if just a string is provided), we can actually rescue the error, ignore it, and [`retry`](https://docs.ruby-lang.org/en/master/syntax/exceptions_rdoc.html) 😱:

	class KnockItOffError &lt; StandardError; end
	
	t = Thread.new do
	  sleep 5
	  puts &#34;✌️&#34;
	rescue KnockItOffError =&gt; e
	  puts &#34;Nice try #{e}&#34;
	  retry
	end
	
	t.join(1)
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	t.join
	
	puts thread_status(t)
	# Nice try 👊 
	# ✌️
	#&lt;ThreadStatus status=&#34;finished&#34; error=nil&gt;

With `raise` and `kill`, issues start to creep in when errors are thrown in an `ensure`. Here we use a `ConditionVariable` (we dug into those in [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html#condition-variable)) to guarantee we raise from the `ensure` block:

```ruby
mutex = Mutex.new
condition = ConditionVariable.new
t = Thread.new do
  sleep 5
  puts &#34;✌️&#34;
ensure
  mutex.synchronize do
    # Signal the condition.wait in the main thread
    condition.signal
    # ...perform some cleanup...
    condition.wait(mutex)
    puts &#34;I&#39;ll never fire 😔&#34;
  end
end
	
mutex.synchronize do
  # This will wait until our Thread ensure runs and signals us
  condition.wait(mutex)
end
	
t.raise(KnockItOffError.new(&#34;👊&#34;))
	
mutex.synchronize do
  # We&#39;ve enqueued our error, now signal so condition#wait fires in the ensure block
  condition.signal
end
sleep 0.1
	
puts thread_status(t)
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34;, error=#&lt;RuntimeError: 👊&gt;
```
We don’t see “I’ll never fire 😔”. What happens to our cleanup? Shouldn’t `ensure`, erm, umm, _ensure_ that things finish…

Moving on from `raise`, `kill` stops the thread from running anymore instructions, no matter what it’s doing. `raise` can be `rescue`’d, `kill` can’t.

```ruby
t = Thread.new do
  sleep 5
  puts &#34;✌️&#34;
# Exception is the root of the Error class hierarchy. If you can&#39;t rescue it with this, you can&#39;t rescue it 
rescue Exception
  puts &#34;#kill cannot be stopped...&#34;
ensure
  puts &#34;This will still run&#34;
end
	
t.join(1)
t.kill
sleep 0.1
	
puts thread_status(t)
# A #kill&#39;d thread gives no indication it was terminated 😔
#&lt;ThreadStatus status=&#34;finished&#34;, error=nil&gt;
```
&gt; 📝 _Technically_, `kill` can be ignored, we’ll explain that when discussing `handle_interrupt`. 
&gt; 
&gt; ⚠️ Don’t rescue `Exception`, it&#39;s a bad idea and you could accidentally rescue things like an OutOfMemoryError 😬

Because `kill` doesn’t raise an error, you actually can’t even tell that the thread was `kill`ed. We just get the normal `false` `status`, represented in our example by “finished”.

Like `raise`, `kill` can also disrupt your `ensure` methods:

```ruby
mutex = Mutex.new
condition = ConditionVariable.new
t = Thread.new do
  sleep 5
  puts &#34;✌️&#34;
ensure
  mutex.synchronize do
    condition.signal
    # perform some cleanup
    condition.wait(mutex)
    puts &#34;I&#39;ll never fire 😔&#34;
  end
end
	
mutex.synchronize do
  condition.wait(mutex)
end
	
t.kill
	
mutex.synchronize do
  condition.signal
end
sleep 0.1
	
puts thread_status(t)
# ✌️
#&lt;ThreadStatus status=&#34;finished&#34; error=nil&gt;
```
There are a few aliases for `kill` to be aware of as well:

```ruby
t = Thread.new do
  Thread.exit # internally gets the current thread, and calls `kill`
end
	
t2 = Thread.new { sleep }
Thread.kill(t) # class method `kill`
t2.exit        # alias for `kill`
t2.terminate   # alias for `kill`
```
Case closed. Feel free to use `raise` and `kill` on your threads. No harm no foul… oh what’s this here?

![](https://cdn.uploads.micro.blog/98548/2025/05bee6d9-e6ae-4398-bde8-8f0bc387e600.jpg)

[Ruby&#39;s Thread#raise, Thread#kill, timeout.rb, and net/protocol.rb libraries are broken](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html)

![](https://cdn.uploads.micro.blog/98548/2025/952b1448-5504-4072-a1b6-ad7ef9d548de.jpg)

[Why Ruby’s Timeout is dangerous (and Thread.raise is terrifying)](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/)

![](https://cdn.uploads.micro.blog/98548/2025/ebeeaeb7-9cd9-4586-88cf-a17e305cc962.jpg)

[The Oldest Bug In Ruby - Why Rack::Timeout Might Hose your Server](https://www.schneems.com/2017/02/21/the-oldest-bug-in-ruby-why-racktimeout-might-hose-your-server/)

![](https://cdn.uploads.micro.blog/98548/2025/7b8d7333-60d7-4c2f-907c-d571ab76fa26.jpg)

[Timeout: Ruby’s Most Dangerous API](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/)

Oh…

![](https://media1.tenor.com/m/MYZgsN2TDJAAAAAC/this-is.gif)

Strangely, the thread docs say _nothing_ about the dangers of these methods[^4]. These articles are from 2008, 2015 and 2017. Surely no one uses it anymore, considering all that?

![](https://cdn.uploads.micro.blog/98548/2025/16ceaf75-1492-4520-b80e-d88e626580e0.jpg)

&gt; Nah.

In fairness to threaded gems that use these methods, they are using the official way you shutdown a thread. And they’re usually taking as many precautions as possible, prior to calling them. For the most part, gems use them as a shutdown mechanism, and give plenty of room for the thread to finish normally first.

The basic problem is this: `raise` and `kill` force your code to die at any point, with no guarantee of properly cleaning up. 

You might ask: “Couldn’t `ctrl+c` do the same thing?”. Yes, an OS signal could kill your process or program before an `ensure` runs, but then all related state is also removed - it can cause other issues, but at least your program cannot limp along in a corrupted state.

	ensure
	  # but, but Ruby - you _promised_ me this would run 😭
	  @value = false
	end

So are they pure evil? An occasional necessity? Somewhere in between? I’ll leave that discussion to the code philosophers… in the practical realm, follow these rules:

&gt; 📝 A small slice of this next section may look familiar. I included a bit of it in [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html#thread-raise-kill). This goes _much_ more in-depth

1. Don’t use `Thread.kill`, `kill` or `raise` unless you really, really know what you’re doing. Same applies for the `kill` aliases `exit` and `terminate`
2. If you need to stop a thread, you want it to tear down safely. Make it safely interruptible by adding in a condition that can allow your thread to finish early
3. Perform resource cleanups in an `ensure`
4. Don’t use the `timeout` module
5. If you use `rack-timeout`, you really should use `term_on_timeout`
6. When you use threads, even implicitly (aka, via configuration in Puma, Sidekiq, Falcon and SolidQueue), you can end up in weird states from them shutting down. It should be rare, but misbehaving threads (very long transactions, runaway CPU) are the most likely to experience this. Pay attention to long running queries or runaway CPU usage and treat it as an important bug
7. If you have something very critical that must properly cleanup 100% of the time, you need [`handle_interrupt`](#)

&lt;h4 id=&#34;dont-kill-threads&#34;&gt;(1) Don&#39;t kill threads&lt;/h4&gt;

I’m watching you. Step away from that method, slowly, and no threads have to get hurt.

&lt;h4 id=&#34;interrupt-safely&#34;&gt;(2) Interrupt your thread safely&lt;/h4&gt;

Instead of killing your thread, set it up to be interruptible. Most mature, threaded frameworks operate this way.

	still_kickin = Concurrent::AtomicBoolean.new(true)
	Thread.new do
	  while still_kickin.true?
	    # more work!
	  end
	end
	
	still_kickin.make_false

&lt;h4 id=&#34;ensure-cleanup&#34;&gt;(3) Ensure cleanup&lt;/h4&gt;

Whenever you _need_ something to run before a method finishes, you should always use an `ensure` block. `ensure` is kind of like a method lifeguard - even if something goes wrong, it’s there for you. It’s the place code goes to _ensure_ it’s run before the method finishes (even when an error is raised).

	def read_some_data
	  read_it
	  close_it # bad
	end
	
	def read_some_data
	  read_it
	ensure
	  close_it # good
	end

We _know_ `ensure` is not a silver bullet. `Thread#raise` and `Thread#kill` do not respect it. But you’re the most likely to clean things up using an `ensure`.

&lt;h4 id=&#34;dont-use-timeout&#34;&gt;(4) Don’t use &lt;code&gt;timeout&lt;/code&gt;&lt;/h4&gt;

If you see this in code, be concerned:

	require &#34;timeout&#34;
	
	Timeout.timeout(1) do
	  # 😱 
	end

For some reason, the [`timeout`](https://github.com/ruby/timeout) gem itself doesn’t warn about any issues. But [Mike Perham summarizes it best](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/):

![](https://cdn.uploads.micro.blog/98548/2025/727697a3-b461-4705-a1f1-8d0ed257857e.jpg)

There’s nothing that exactly matches what timeout offers: a blanket way of timing out _any_ operation after the specified time limit. But most gems and Ruby features offer a way to be interrupted - there is a repository called [The Ultimate Guide to Ruby Timeouts](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts) which details everything you need to know. It shows you how to set timeouts safely for basically _every_ blocking operation you could care about timing out. For instance, how to properly handle timeouts using the [`redis`](https://github.com/redis/redis-rb) gem:

```ruby
Redis.new(
  connect_timeout: 1, 
  timeout: 1,
  #...
)
```
The one piece mentioned in that repository you should leave alone: `Net::HTTP` `open_timeout`. Behind the scenes it uses the `timeout` module 🙅‍♂️. Leave the 60 second default, it should almost never impact you, and you’re probably worse off lowering it.

Primarily people use the `timeout` gem to manage IO timeouts. In the unlikely case you want to timeout CPU-bound code, it’s up to you to implement it in your processing.

&lt;h4 id=&#34;use-term-on-timeout&#34;&gt;(5) If you use &lt;a href=&#34;https://github.com/zombocom/rack-timeout&#34;&gt;&lt;code&gt;rack-timeout&lt;/code&gt;&lt;/a&gt;, you really should use &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/doc/settings.md#term-on-timeout&#34;&gt;&lt;code&gt;term_on_timeout&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

`rack-timeout` works similarly to the `timeout` module. And I already told you not to use that. So what gives? It will call `raise` on your threads - isn’t that bad?

The short answer is yes, it’s still bad.

_But_, `rack-timeout` is the only real option you have for timing out a web request in Puma. It’s meant as a last resort. From their docs:

&gt; `rack-timeout` is not a solution to the problem of long-running requests, it&#39;s a debug and remediation tool. App developers should track rack-timeout&#39;s data and address recurring instances of particular timeouts, for example by refactoring code so it runs faster or offsetting lengthy work to happen asynchronously.

On top of that, you should have your own lower level timeouts set so that they would fire before `rack-timeout`.

&gt; You&#39;ll want to set all relevant timeouts to something lower than `Rack::Timeout`&#39;s `service_timeout`. Generally you want them to be at least 1s lower, so as to account for time spent elsewhere during the request&#39;s lifetime while still giving libraries a chance to time out before Rack::Timeout.

The core issue of any thread `raise`/`kill` based solution is corrupted state. When using `rack-timeout`, you should be using [`term_on_timeout`](https://github.com/zombocom/rack-timeout/blob/main/doc/settings.md#term-on-timeout), ideally set to `1`.

`term_on_timeout` will send a `SIGTERM` to the worker the thread is running in, which for most servers indicates a need for a graceful shutdown of that process - any potential corrupted state is isolated to that process and will be cleaned up once the process is shutdown.

`term_on_timeout` only works properly if you’ve got multiple processes serving your requests. And if you get lots and lots of timeouts, it could potentially cause performance problems. See the docs for proper configuration!

There _is_ an alternative idea floating around out there of a way to achieve a “Safer Timeout”, at least in Rails apps:[https://web.meetcleo.com/blog/safer-timeouts](https://web.meetcleo.com/blog/safer-timeouts). Maybe I’ll detail it more in the future, but in the meantime, if you’re in a Rails app I would give it a read.

&lt;h4 id=&#34;monitor-your-thread-costs&#34;&gt;(6) Monitor your thread cost&lt;/h4&gt;

Having threads that do not stop easily is a bug. If you’re seeing rack timeout errors, or jobs that can’t be shut down, track it and prioritize fixing it. Treat it like a bug and allocate time to improve it.

&lt;h4 id=&#34;get-a-handle&#34;&gt;(7) Get a &lt;code&gt;handle_interrupt&lt;/code&gt; on things&lt;/h4&gt;

`Thread.handle_interrupt` is One Weird Trick `Thread#kill` Calls Don’t Want You To Know™. If we’re gonna discuss it, might as well go deep…

&lt;h4 id=&#34;thread-handle-interrupt&#34;&gt;&lt;a href=&#34;https://docs.ruby-lang.org/en/4.0/Thread.html#method-c-handle_interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

A thread can be externally “interrupted” by a few things:

1. `Thread#kill`
2. `Thread#raise`
3. Your program being exited
4. A signal, like Ctrl+C

`handle_interrupt` gives you the ability to control how your program reacts to 1-3. And it means you can define blocks of code which will _guarantee_ their `ensure` blocks run.

`handle_interrupt` is a low-level interface and it’s also the one you are least likely to ever need. You’ll see it used in things like threaded web and job servers where low-level control and better cleanup guarantees are helpful. You’ll find examples of it in Sidekiq, the Async fiber scheduler, Homebrew, the parallel gem and more.

When you need the strongest guarantees possible about cleaning up your code in response to “interruption”, `handle_interrupt` is what you need.

Let’s look at a simple example:

	class KnockItOffError &lt; StandardError; end
	
	t = Thread.new do
	  sleep 2
	  puts &#34;done!&#34;
	end
	t.join(1)
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	sleep 0.1
	
	puts thread_status(t)
	#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;

Run that code 👆 and you’ll never see “done!” print. This is the same type of code we saw in the [`raise` and `kill`](#thread-raise-and-kill) section. What can `handle_interrupt` do for us?

	t = Thread.new do
	  Thread.handle_interrupt(KnockItOffError =&gt; :never) do
	    sleep 2
	    puts &#34;done!&#34;
	  end
	end
	
	t.join(1)
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	sleep 0.1
	
	puts thread_status(t)
	# done!
	#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;

Now we see “done!” printed! To be clear, the error will still be raised _eventually_. It can only impact the section it encloses, so the error will be raised right after.

What’s with the interface - what does `KnockItOffError =&gt; :never` mean? Let’s break it down:

- `handle_interrupt` takes a hash. Each key is an exception class object, and each value is a symbol representing how to respond to the exception
- `KnockItOffError` in our example represents the class that will be handled. Descendants of the class are included, so it could be `KnockItOffError` or any of its descendants.
- There are three different symbols allowed as values:
	- `:never` indicates that the exception will “never” interrupt any of the code inside of the block.
	- `:on_blocking` indicates that the exception can only be raised during a “blocking” operation. This includes things like IO read/write, `sleep`, and waiting on mutexes. Anything that releases the GVL.
	- `:immediate` indicates that the exception should be handled “immediately”. This is effectively the default behavior, so you would generally use this to re-apply an exception ignored at a higher level.

Based on that knowledge, let’s demonstrate a more complex example:

```ruby
t = Thread.new do
  Thread.handle_interrupt(KnockItOffError =&gt; :never) do
    # KnockItOffError can &#34;never&#34; run here
    Thread.handle_interrupt(KnockItOffError =&gt; :immediate) do
      # KnockItOffError runs &#34;immediate&#34;ly here
      sleep 2
      puts &#34;done!&#34;
    end
  # KnockItOffError can &#34;never&#34; run here
  ensure
    puts &#34;Can&#39;t touch this!&#34;
  end
end
	
t.join(1)
t.raise(KnockItOffError.new(&#34;👊&#34;))
sleep 0.1
	
puts thread_status(t)
# Can&#39;t touch this!
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;
```
In this example, “done!” is never printed because it is in the `:immediate` block. But we successfully print out “Can’t touch this!” message in our ensure, because we’re within the `:never` block for `KnockItOffError`. `ensure` is now… _ensured_.

———

We’ve used `:never` and `:immediate`, what about `:on_blocking`?

```ruby
i = 0
t = Thread.new do
  Thread.handle_interrupt(
    KnockItOffError =&gt; :on_blocking
  ) do
    1_000_000.times do
      i += 1
    end
    puts &#34;👋&#34;
  end
end
t.join(0.01)
t.raise(KnockItOffError.new(&#34;👊&#34;))
sleep 1
puts &#34;i: #{i}&#34;
puts thread_status(t)
# i: 1000000
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;
```
Our increments work fine, as indicated by `i` being one million. But our `puts` is a “blocking” call so it gets the boot.

Should we have used a thread safe counter? Let’s try it again using `Concurrent::AtomicFixnum` from `concurrent-ruby`, and two threads. We should see `i` as two million afterwards:

```ruby
require &#34;concurrent&#34;
	
i = Concurrent::AtomicFixnum.new(0)
	
def incrementing_thread(i)
  Thread.new do
    Thread.handle_interrupt(
      KnockItOffError =&gt; :on_blocking
    ) do
      1_000_000.times do
        i.increment
      end
      puts &#34;👋&#34;
    end
  end
end
	
t = incrementing_thread(i)
t2 = incrementing_thread(i)
	
t.join(0.01)
t2.join(0.01)
t.raise(KnockItOffError.new(&#34;👊&#34;))
t2.raise(KnockItOffError.new(&#34;👊👊&#34;))
sleep 1
puts &#34;i: #{i.value}&#34;
puts thread_status(t)
puts thread_status(t2)
# i: 1237719
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;
#&lt;ThreadStatus status=&#34;failed w/ error: 👊👊&#34; error=#&lt;KnockItOffError: 👊👊&gt;&gt;
```
Wait, why? `i` is `1237719`? Why is `i` not two million? What blocked?!

&gt; 📝 You’ll definitely see a different number. Sometimes you’ll see `1000000`, sometimes you’ll see a higher number, but you’ll pretty much never see `2000000`

As it turns out, `Concurrent::AtomicFixnum` uses a `Mutex` by default. If a `Mutex` waits to acquire a lock it is considered a blocking operation! That means it qualifies for `:on_blocking` and the error gets raised. 

As a specific fix for `AtomicFixnum`, if you install the `concurrent-ruby-ext` gem then you get native extensions which are lock-free, no longer use a `Mutex`, and properly run our code.

Once we install `concurrent-ruby-ext`, we properly get `2000000`!:

```ruby
# You ran `gem install concurrent-ruby-ext`
# It automatically loads if present
# ...
puts &#34;i: #{i.value}&#34;
puts thread_status(t)
puts thread_status(t2)
# i: 2000000
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;
#&lt;ThreadStatus status=&#34;failed w/ error: 👊👊&#34; error=#&lt;KnockItOffError: 👊👊&gt;&gt;
```
But we also know that a `Mutex` or any other locking/waiting behavior can cause our `:on_blocking` interrupt to fire. So `:on_blocking` can have surprising behavior if some other internal of the code were to change later.

———

If the thread hasn’t started yet, `handle_interrupt` won’t help you. The error will be raised immediately in the thread, before `handle_interrupt` can be called:

	t = Thread.new do
	  Thread.handle_interrupt(
	    KnockItOffError =&gt; :never
	  ) do
	    puts &#34;welcome!&#34;
	    puts &#34;later 👋&#34;
	  end
	end
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	sleep 1
	puts thread_status(t)
	#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;

———

What happens after `handle_interrupt`? Once the error is allowed to raise, code directly after it won’t run:

	t = Thread.new do
	  Thread.handle_interrupt(
	    KnockItOffError =&gt; :never
	  ) do
	    puts &#34;can&#39;t stop won&#39;t stop&#34;
	    Thread.handle_interrupt(
	      KnockItOffError =&gt; :immediate
	    ) do
	      sleep 2
	      puts &#34;this won&#39;t run&#34;
	    end
	    puts &#34;this won&#39;t run either&#34;
	  end
	end
	
	t.join(1)
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	sleep 0.1
	puts thread_status(t)
	# can&#39;t stop won&#39;t stop
	#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;

But code after the inner `handle_interrupt` _could_ run, it just depends on if the previous block raises. In this example, _all_ of the code runs successfully because we don’t raise an error during the inner block:

	t = Thread.new do
	  Thread.handle_interrupt(
	    KnockItOffError =&gt; :never
	  ) do
	    puts &#34;can&#39;t stop won&#39;t stop&#34;
	    Thread.handle_interrupt(
	      KnockItOffError =&gt; :immediate
	    ) do
	      sleep 2
	      puts &#34;this won&#39;t run&#34;
	    end
	    sleep 2
	    puts &#34;this won&#39;t run either&#34;
	  end
	end
	
	t.join(3)
	t.raise(KnockItOffError.new(&#34;👊&#34;))
	t.join # FIXME raises?
	puts thread_status(t)
	# can&#39;t stop won&#39;t stop
	# this won&#39;t run
	# this won&#39;t run either
	#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;

But you’re better off guaranteeing code after the block runs. Use an `ensure` to make sure even if the inner block raises an error your code still runs:

```ruby
t = Thread.new do
  Thread.handle_interrupt(
    KnockItOffError =&gt; :never
  ) do
    puts &#34;can&#39;t stop won&#39;t stop&#34;
    Thread.handle_interrupt(
      KnockItOffError =&gt; :immediate
    ) do
      sleep 2
      puts &#34;this won&#39;t run&#34;
    end
  ensure
    puts &#34;this will consistently run&#34;
  end
end
	
t.join(1)
t.raise(KnockItOffError.new(&#34;👊&#34;))
sleep 0.1
puts thread_status(t)
# can&#39;t stop won&#39;t stop
# this will consistently run
#&lt;ThreadStatus status=&#34;failed w/ error: 👊&#34; error=#&lt;KnockItOffError: 👊&gt;&gt;
```
———

Can we even stop the unstoppable `Thread#kill`? Yep! From the [`Thread.handle_interrupt`](https://docs.ruby-lang.org/en/4.0/Thread.html#method-c-handle_interrupt-label-Inheritance+with+ExceptionClass) docs:

&gt; For handling all interrupts, use Object and not Exception as the ExceptionClass, as kill/terminate interrupts are not handled by Exception.

So we can handle it - but we have to use `Object`, which looks a bit odd but works well:

	t = Thread.new do
	  Thread.handle_interrupt(Object =&gt; :never) do
	    sleep 2
	    puts &#34;done!&#34;
	  end
	end
	
	t.join(1)
	t.kill
	t.join
	puts thread_status(t)
	# done!
	#&lt;ThreadStatus status=&#34;finished&#34; error=nil&gt;

The reason for this odd syntax is that the `kill`/`terminate` interrupts are internally handled [not as Exception instances, but as integers](https://bugs.ruby-lang.org/issues/15735). That means this would also work:

	t = Thread.new do
	  Thread.handle_interrupt(
	    Integer =&gt; :never
	  ) do
	    sleep 2
	    puts &#34;done!&#34;
	  end
	end
	
	t.join(1)
	t.kill
	t.join
	puts thread_status(t)
	# done!
	#&lt;ThreadStatus status=&#34;finished&#34; error=nil&gt;

Still, you’re better off using `Object` to avoid the implementation detail.

———

Can we stop the `timeout` gem from raising at a bad time using `handle_interrupt`? The Thread API docs used to [specifically use `timeout` as a use-case for `handle_interrupt`](https://docs.ruby-lang.org/en/3.3/Thread.html#method-c-handle_interrupt-label-Guarding+from+Timeout-3A-3AError), but [there’s a non-determinism bug](https://github.com/ruby/timeout/issues/41) around thread reuse within the `timeout` gem. 

So once again, [don’t use the `timeout` gem](#dont-use-timeout). 

I [removed the example from the docs](https://github.com/ruby/ruby/pull/11474) because it’s too broken, so on Ruby 3.4+, the docs no longer mention `handle_interrupt` with the `timeout` gem.

———

We’ve looked at many `handle_interrupt` examples - what do real gems use it for?

In the [`async`](#) gem [it uses `handle_interrupt`](https://github.com/socketry/async/blob/main/lib/async/scheduler.rb) to ignore `SignalException` while it shuts down its child tasks:

	# Stop all children, including transient children, ignoring any signals.
	def stop
	  Thread.handle_interrupt(::SignalException =&gt; :never) do
	    @children&amp;.each do |child|
	      child.stop
	    end
	  end
	end

In `sidekiq`, when it has gracefully attempted a shutdown and is forcing threads to finish, it raises a special error. That error extends `Interrupt`, which means most `rescue` blocks will not capture it because it is a child of `Exception` rather than `StandardError`:

	module Sidekiq
	  class Shutdown &lt; Interrupt; end
	end
	# later...
	t.raise(Sidekiq::Shutdown)

To avoid `Sidekiq::Shutdown` breaking _everything_ (including its own internal code), Sidekiq also uses `handle_interrupt` to ignore the error in a small piece of shutdown code:

	IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown =&gt; :never}
	ALLOW_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown =&gt; :immediate}
	
	def process(uow)
	  jobstr = uow.job
	  queue = uow.queue_name
	     
	  #... process logic
	  ack = false
	  Thread.handle_interrupt(
	    IGNORE_SHUTDOWN_INTERRUPTS
	  ) do
	    Thread.handle_interrupt(
	      ALLOW_SHUTDOWN_INTERRUPTS
	    ) do
	      dispatch(...) do |inst|
	        config.server_middleware... do
	          execute_job(...)
	        end
	      end
	      ack = true
	    rescue Sidekiq::Shutdown
	      # Had to force kill this job because it didn&#39;t finish
	      # within the timeout.  Don&#39;t acknowledge the work since
	      # we didn&#39;t properly finish it.
	  ensure
	    if ack
	      uow.acknowledge
	    end
	  end
	end

If this section hasn’t been enough for you, Ben Sheldon gives some additional interesting examples In his article [Appropriately using Ruby’s thread `handle_interrupt`](https://island94.org/2023/08/appropriately-using-rubys-thread-handle_interrupt).

&lt;h4 id=&#34;ensure-success&#34;&gt;The way you &lt;code&gt;ensure&lt;/code&gt; success&lt;/h4&gt;

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/40e94602-9e2b-4dd2-a60f-3a407944cbe8.jpg&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;

I’m pretty confident, started from scratch, Ruby would not be implemented with `raise` and `kill` again. I don’t know _which _ model they would choose - but something like a [Java interrupt](https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html) would be a good start. And minimally, making [all `ensure` blocks uninterruptable](https://bsky.app/profile/headius.bsky.social/post/3lvnkypqewk2b), as well as [all finalizers](https://www.manageiq.org/blog/2017/09/finalizers-can-be-interrupted-from-time-to-time/). I didn’t even get into finalizers - they’re a less common, but also important area that you _really_ don’t want to interrupt.

Ruby is one of the only programming languages that lets you kill a thread from outside of the thread. It’s powerful, but mostly, it’s dangerous. It’s one of the sharpest tools available to you, and it should be used _sparingly_, or ideally not at all.

In threaded code, the best offense is a good defense:

- Consider how to safely manage your threads. If your thread is going to do a lot of work, or expensive work, make sure you have an escape hatch (like a boolean check for interrupts)
- Treat performance issues like a bug. If you have threads that are hogging resources and timing things out, you need to fix them. `kill`ing them is not a long-term answer
- Ultimately, there is no way to guarantee the safety of your code 100% of the time. The OS could kill your program. Your server could get unplugged. You’re always going to have edge cases that you can’t foresee. But control what you can, and be aware of what can go wrong.

Now go forth, armed with the knowledge on what to do when good threads go bad.

[^1]:	Unless you’re in SQLite, where apparently N+1 queries are a virtue 😲 [https://www.sqlite.org/np1queryprob.html](https://www.sqlite.org/np1queryprob.html)

[^2]:	In general, you shouldn’t do this directly

[^3]:	And processes.

    Or use Falcon. See me later in the series when we talk about Fibers 😏

[^4]:	There’s a small mention about how to handle Timeout errors, but it doesn’t explain much or warn at all 
</source:markdown>
    </item>
    
    <item>
      <title>Bitmasks, Ruby Threads and Interrupts, oh my!</title>
      <link>https://jpcamara.com/2025/10/21/bitmasks-threads-and-interrupts.html</link>
      <pubDate>Tue, 21 Oct 2025 21:12:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2025/10/21/bitmasks-threads-and-interrupts.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-copy-of-pending-errors.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into several parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “Bitmasks, Threads and Interrupts, oh my!&amp;quot;. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#interrupting-threads&#34;&gt;Interrupting Threads&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#managing-threads&#34;&gt;Managing threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#important-interruption&#34;&gt;An important interruption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#integer-masks&#34;&gt;Why integer masks?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#checking-for-interrupts&#34;&gt;Checking for interrupts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#jumping-to-instructions&#34;&gt;Jumping to instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#pervasive-interrupts&#34;&gt;Pervasive interrupts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#interrupt-masks&#34;&gt;Interrupt masks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#masks-all-the-way&#34;&gt;It’s masks all the way down&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#weve-been-waiting&#34;&gt;The &lt;code&gt;interrupt&lt;/code&gt;ion we’ve all been waiting for&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#timer-interrupt-mask&#34;&gt;&lt;code&gt;TIMER_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#trap-interrupt-mask&#34;&gt;&lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#pending-interrupt-mask&#34;&gt;&lt;code&gt;PENDING_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#terminate-interrupt-mask&#34;&gt;&lt;code&gt;TERMINATE_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#postponed-job-interrupt-mask&#34;&gt;&lt;code&gt;POSTPONED_JOB_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#vm-barrier-interrupt-mask&#34;&gt;&lt;code&gt;VM_BARRIER_INTERRUPT_MASK&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;interrupting-threads&#34;&gt;Interrupting Threads 🧵&lt;/h2&gt;
&lt;p&gt;The Ruby thread scheduler is &lt;em&gt;rude&lt;/em&gt;. There, I said it.&lt;/p&gt;
&lt;p&gt;It’s always telling your threads how to run, when to run, how long they can run - it stops them whenever it wants and then tells &lt;em&gt;other&lt;/em&gt; threads &lt;em&gt;they&lt;/em&gt; have to start working. It’s bossy as hell. On top of that, it’s not even polite about it. It feels free to &lt;em&gt;interrupt&lt;/em&gt; your threads - it decides when, and your threads have to just play along and listen.&lt;/p&gt;
&lt;p&gt;But threads put up with it. They even &lt;em&gt;benefit&lt;/em&gt; from it, if you can believe that. The thread scheduler may be abrupt, but it&amp;rsquo;s looking out for the runtime. If that’s the case - there must be a good reason… Why do threads put up with these scheduler shenanigans?&lt;/p&gt;
&lt;h3 id=&#34;managing-threads&#34;&gt;Managing threads 👷🏼‍♂️👷🏻‍♀️&lt;/h3&gt;
&lt;p&gt;An important purpose of a thread scheduler is efficiency and fairness. It manages what threads are running, for how long, and what context each thread gets loaded with.&lt;/p&gt;
&lt;p&gt;In normal operation this comes down to a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time sharing: every thread, under normal circumstances, gets roughly 100ms of CPU runtime&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Since only one thread can run Ruby code at a time&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, this keeps a single thread from dominating the program&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Blocking operations: certain operations will “block” a thread. Most forms of IO, and &lt;code&gt;sleep&lt;/code&gt;, for instance. When the thread blocks, the thread scheduler allows other threads to run&lt;/li&gt;
&lt;li&gt;Priority: we can suggest a priority for our threads, and the thread scheduler will take it into consideration when choosing what to run next and for how long&lt;/li&gt;
&lt;li&gt;Passing control: we can suggest actions to the thread scheduler, like &lt;code&gt;pass&lt;/code&gt;ing control or &lt;code&gt;stop&lt;/code&gt;ping the current thread so other threads can take over&lt;/li&gt;
&lt;li&gt;Locking: we can synchronize access to resources, and the thread scheduler chooses the order of access to the resource&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How does it handle all this?&lt;/p&gt;
&lt;h3 id=&#34;important-interruption&#34;&gt;An important interruption&lt;/h3&gt;
&lt;p&gt;The scheduler isn’t a single object - it is the behavior produced by specific VM checks and functions which opt-in to thread scheduling behavior. In this post, we’ll focus on time sharing, priority, and interrupting threads - which are managed through a concept deeply woven into CRuby - a set of functions the VM calls at specific checkpoints.&lt;/p&gt;
&lt;p&gt;In the CRuby runtime, this concept revolves around “interrupts”&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;. It contains a set of possible events that could “interrupt” the flow of a Ruby program in general, and different threads in particular. There are several interrupt events possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timer interrupt&lt;/li&gt;
&lt;li&gt;Trap interrupt&lt;/li&gt;
&lt;li&gt;Pending interrupt&lt;/li&gt;
&lt;li&gt;Terminate interrupt&lt;/li&gt;
&lt;li&gt;VM barrier interrupt&lt;/li&gt;
&lt;li&gt;Postponed job interrupt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the CRuby internals, these are represented by an integer mask. If we took the C code and represented it in Ruby, it would look like this (each mask is a hex value):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;TIMER_INTERRUPT_MASK&lt;/span&gt;         &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;PENDING_INTERRUPT_MASK&lt;/span&gt;       &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x02&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;POSTPONED_JOB_INTERRUPT_MASK&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x04&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;TRAP_INTERRUPT_MASK&lt;/span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x08&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;TERMINATE_INTERRUPT_MASK&lt;/span&gt;     &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x10&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;VM_BARRIER_INTERRUPT_MASK&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;integer-masks&#34;&gt;Why integer masks?&lt;/h3&gt;
&lt;p&gt;What is an integer mask, and why would CRuby represent internal states this way?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 “mask” is a conventional name for a pattern to isolate specific bits. A “mask” sounds like something that would cover up something else (ie, a mask covering your face). In a sense, these serve a similar purpose - the mask is put on or taken off, clearing or setting bits and testing them efficiently.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Integer masks provide a compact and efficient way to represent multiple program states within a single number. Each state is stored as a single bit - representing a power of 2 - so in concept a 64-bit integer can represent up to 64 independent on/off states. Here’s a visualization of the first 8 bits in a number (a byte):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 0 0 0 0 0 0 0
| | | | | | | |
| | | | | | | +- 1        (2^0)     0x01
| | | | | | +--- 2        (2^1)     0x02
| | | | | +----- 4        (2^2)     0x04
| | | | +------- 8        (2^3)     0x08
| | | +--------- 16       (2^4)     0x10
| | +----------- 32       (2^5)     0x20
| +------------- 64       (2^6)     0x40
+--------------- 128      (2^7)     0x80
      Byte       Decimal  Power     Hex
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This is an adaptation of a visual from a blog post about bitwise operations: &lt;a href=&#34;https://www.hendrik-erz.de/post/bitwise-flags-are-beautiful-and-heres-why&#34;&gt;https://www.hendrik-erz.de/post/bitwise-flags-are-beautiful-and-heres-why&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Being able to represent all this information in a compact way is convenient, but checking whether it matches a particular mask is also &lt;em&gt;very&lt;/em&gt; fast. CPUs are well optimized for this sort of thing.&lt;/p&gt;
&lt;p&gt;There are several operators for performing these checks called “bitwise” operators. Here’s a table of the operators, and the impact they have on bits:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;
				AND
			&lt;/th&gt;
			&lt;th&gt;
				OR
			&lt;/th&gt;
			&lt;th&gt;
				XOR
			&lt;/th&gt;
			&lt;th&gt;
				NOT
			&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;
				A
			&lt;/th&gt;
			&lt;th&gt;
				B
			&lt;/th&gt;
			&lt;th&gt;
				A &amp; B
			&lt;/th&gt;
			&lt;th&gt;
				A | B
			&lt;/th&gt;
			&lt;th&gt;
				A ^ B
			&lt;/th&gt;
			&lt;th&gt;
				~A
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We can demonstrate using the Ruby binary syntax&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bitwise AND:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000101&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;
  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000110&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0b00000100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Bitwise OR:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000101&lt;/span&gt;
  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000110&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0b00000111&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Bitwise XOR:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000101&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt;
  &lt;span style=&#34;color:#ae81ff&#34;&gt;0b00000110&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0b00000011&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Bitwise NOT:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;  (&lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0b11111000&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xFF&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0b00000111&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;&amp;amp; 0xFF&lt;/code&gt; forces us into 8-bits to demonstrate the &lt;code&gt;NOT&lt;/code&gt; correctly&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These aren’t relevant to masks specifically, but for completeness, you can also shift bits left or right to change values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  0b00000101 &amp;lt;&amp;lt; 1 # Left shift
# 0b00001010

  0b00000101 &amp;gt;&amp;gt; 1 # Right shift
# 0b00000010
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;checking-for-interrupts&#34;&gt;Checking for interrupts&lt;/h3&gt;
&lt;p&gt;The reason these efficient mask checks matter, is because these interrupts are checked &lt;em&gt;a lot&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a program that simply iterates for a little while, incrementing a counter&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500_000&lt;/span&gt;
  i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will check interrupts &lt;em&gt;five hundred thousand times&lt;/em&gt;&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;, one check for each iteration of the loop. That’s a lot. And if your programming language is going to do something a lot, it needs to be efficient. The overhead of checking for interrupts should be undetectable in your Ruby program. As discussed earlier, bit mask checks are one of the most efficient checks you can make.&lt;/p&gt;
&lt;p&gt;But why does this innocuous program need to check for interrupts so often? It’s part of the thread scheduler opt-in! The Ruby virtual machine is filled with checkpoints where it is safe for Ruby internals to check for interruptions in the program. One of those checkpoints is an &lt;code&gt;if&lt;/code&gt; statement (did you think I’d say &lt;code&gt;while&lt;/code&gt; loop?!).&lt;/p&gt;
&lt;p&gt;Let’s &lt;a href=&#34;https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html#inside-the-vm&#34;&gt;disassemble this into Ruby bytecode&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;puts &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVMInstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;compile(
  &lt;span style=&#34;color:#66d9ef&#34;&gt;DATA&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read
)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disassemble
	
&lt;span style=&#34;color:#75715e&#34;&gt;__END__
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;i = 0
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;while i &amp;lt; 500_000
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  i += 1
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which gives us the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0000 putobject_INT2FIX_0_
0001 setlocal_WC_0           i@0    # | i = 0
0003 jump                    16     # | jump to while i &amp;lt; ... 
...
0009 getlocal_WC_0           i@0    # | i += 1
0011 putobject_INT2FIX_1_           # |
0012 opt_plus                       # |
0014 setlocal_WC_0           i@0.   # |____________
0016 getlocal_WC_0           i@0    # | i &amp;lt; 500_000
0018 putobject               500000 # |
0020 opt_lt                         # |
0022 branchif                9      # | jump to instruction 9,
                                    # | which is i += 1
                                    # |____________
0024 putnil
0025 leave
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the moment you can trust me that &lt;code&gt;branchif&lt;/code&gt; is the critical section here. Let’s see how &lt;code&gt;branchif &lt;/code&gt; is defined:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEFINE_INSN
branchif
(OFFSET dst)
(VALUE val)
()
{
    if (RTEST(val)) {
        RUBY_VM_CHECK_INTS(ec);
        JUMP(dst);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;❗️Woah! What the heck is that weird syntax? Is that Ruby? Is that C?&lt;/p&gt;
&lt;p&gt;It’s neither! This is a special, CRuby internal specific DSL that is &lt;em&gt;similar&lt;/em&gt; to C. In CRuby, there is a file called &lt;code&gt;insns.def&lt;/code&gt; which defines every instruction the Ruby Virtual Machine (YARV) can run.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DEFINE_INSN&lt;/code&gt; tells us we are defining an instruction&lt;/li&gt;
&lt;li&gt;&lt;code&gt;branchif&lt;/code&gt; is the instruction name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OFFSET dst&lt;/code&gt; is the argument - 9 in our case, which would take us to &lt;code&gt;0009 getlocal_WC_0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VALUE val&lt;/code&gt; is the last value pushed on the stack - the result of &lt;code&gt;i &amp;lt; 500_000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; that last empty set of parens is the optional return value - we don’t have one - we jump if &lt;code&gt;val&lt;/code&gt; is true, or we fall through&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Interesting! A couple things stick out to me here when the&lt;code&gt;RTEST(val)&lt;/code&gt; (our &lt;code&gt;while&lt;/code&gt; condition) is true:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We’re running &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt; anytime we call an &lt;code&gt;if&lt;/code&gt; statement. &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt; is a key function for checking the interrupt queue. It’s embedded within VM instructions themselves!&lt;/li&gt;
&lt;li&gt;We &lt;code&gt;JUMP&lt;/code&gt; to a destination&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Fun fact: one of the places &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt; is called is from the &lt;code&gt;once&lt;/code&gt; bytecode instruction. An unexpected callback to my article &lt;a href=&#34;https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html&#34;&gt;The o in Ruby regex stands for “oh the humanity!”&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;jumping-to-instructions&#34;&gt;Jumping to instructions&lt;/h3&gt;
&lt;p&gt;In the typical case of a &lt;code&gt;branchif&lt;/code&gt;, it would jump to the appropriate part of an &lt;code&gt;if&lt;/code&gt; statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; is_it_true?
  &lt;span style=&#34;color:#75715e&#34;&gt;# if it is true, jump here!&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# if it isn&amp;#39;t, jump here!&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What caught my eye is that internally an &lt;code&gt;if&lt;/code&gt; statement basically acts like &lt;code&gt;goto&lt;/code&gt;! Sorry &lt;a href=&#34;https://en.wikipedia.org/wiki/Considered_harmful&#34;&gt;Dijkstra&lt;/a&gt;&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And because &lt;code&gt;branchif&lt;/code&gt; can jump anywhere you tell it, that also means it can jump to &lt;em&gt;previous&lt;/em&gt; code as well. In the case of a &lt;code&gt;while&lt;/code&gt; loop, &lt;code&gt;branchif&lt;/code&gt; truly takes on its &lt;code&gt;goto&lt;/code&gt; roots. Instead of jumping to a future piece of code, it reruns the content of the &lt;code&gt;while&lt;/code&gt; loop by jumping back to earlier instructions!&lt;/p&gt;
&lt;h3 id=&#34;pervasive-interrupts&#34;&gt;Pervasive interrupts&lt;/h3&gt;
&lt;p&gt;Want to double the number of checks from our example? Let’s &lt;code&gt;add&lt;/code&gt; a method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(a, b)
  a &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; b
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500_000&lt;/span&gt;
  i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
  j &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; add(i, j)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now CRuby checks the interrupt queue &lt;em&gt;one million times&lt;/em&gt;&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;! That’s because of the &lt;code&gt;opt_send_without_block&lt;/code&gt; instruction, which is one of the instructions for Ruby method calls:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DEFINE_INSN
opt_send_without_block
(CALL_DATA cd)
(...)
(VALUE val)
{
    // ...
    val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
    // Before returning from exec, int check!
    JIT_EXEC(ec, val);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;👆More of that fancy CRuby DSL&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We know that interrupts are woven into vm instructions themselves in &lt;code&gt;insns.def&lt;/code&gt;, but it&amp;rsquo;s not alone. Interrupts are checked throughout CRuby. For example, in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IO&lt;/li&gt;
&lt;li&gt;Threads&lt;/li&gt;
&lt;li&gt;Processes&lt;/li&gt;
&lt;li&gt;The Regex engine&lt;/li&gt;
&lt;li&gt;BigNumber&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And you&amp;rsquo;ll find the checks in various forms: &lt;code&gt;RUBY_VM_CHECK_INTS_BLOCKING&lt;/code&gt;, &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt;, &lt;code&gt;rb_thread_check_ints&lt;/code&gt;, &lt;code&gt;vm_check_ints_blocking&lt;/code&gt;, &lt;code&gt;vm_check_ints&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;We know &lt;em&gt;what&lt;/em&gt; gets called to check for interrupts - but how do these &amp;ldquo;ints&amp;rdquo; get set by CRuby?&lt;/p&gt;
&lt;h3 id=&#34;interrupt-masks&#34;&gt;Interrupt masks&lt;/h3&gt;
&lt;p&gt;CRuby has macros for setting each of the interrupt flags:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_TIMER_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, TIMER_INTERRUPT_MASK)
&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, PENDING_INTERRUPT_MASK)
&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, POSTPONED_JOB_INTERRUPT_MASK)
&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_TRAP_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, TRAP_INTERRUPT_MASK)
&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_TERMINATE_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, TERMINATE_INTERRUPT_MASK)
&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  ATOMIC_OR((ec)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_flag, VM_BARRIER_INTERRUPT_MASK)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 &lt;code&gt;ec&lt;/code&gt; in these examples refers to the “execution context”, which contains per-thread information about the running Ruby program&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This &lt;code&gt;ATOMIC_OR&lt;/code&gt; macro is an abstraction on top of bitwise operations that stays roughly as efficient, but makes sure the operations run atomically. Multiple operating system threads can run this at the same time - &lt;code&gt;ATOMIC_OR&lt;/code&gt; helps to avoid &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write&#34;&gt;read-modify-write&lt;/a&gt; issues.&lt;/p&gt;
&lt;p&gt;Those abstractions obscure the actual bitwise operation - we learned a bit about Ruby bitwise operations earlier, let’s show these macros in Ruby form for clarity:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ExecutionContext&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;

  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_timer_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TIMER_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PENDING_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_postponed_job_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;POSTPONED_JOB_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_trap_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TRAP_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_terminate_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TERMINATE_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_set_vm_barrier_interrupt&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;VM_BARRIER_INTERRUPT_MASK&lt;/span&gt;
    self
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s also add some binary conversion methods so we can conveniently print our results:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;to_b&lt;/span&gt;(number)
  number&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_s(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;rjust(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;0&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ExecutionContext&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;interrupt_to_b&lt;/span&gt;
    to_b(@interrupt_flag)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 &lt;a href=&#34;https://docs.ruby-lang.org/en/master/Integer.html#method-i-to_s&#34;&gt;Integer#to_s&lt;/a&gt; can be handed a &lt;code&gt;base&lt;/code&gt;, which converts to the specified base before returning as a string. In our case, we are converting it to base 2 to show it as binary. We then &lt;code&gt;rjust&lt;/code&gt; to pad the left side with 0’s. So for instance, this returns &lt;code&gt;to_b(2)&lt;/code&gt; as &lt;code&gt;00000010&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For reference, here are the CRuby interrupt masks and which bits they set in our &lt;code&gt;interrupt_flag&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 0 0 0 0 0 0 0 = 0x0 = interrupt_flag
    | | | | | |
    | | | | | +- TIMER_INTERRUPT_MASK
    | | | | +--- PENDING_INTERRUPT_MASK
    | | | +----- POSTPONED_JOB_INTERRUPT_MASK
    | | +------- TRAP_INTERRUPT_MASK
    | +--------- TERMINATE_INTERRUPT_MASK
    +----------- VM_BARRIER_INTERRUPT_MASK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Knowing that, and equipped with our &lt;code&gt;ExecutionContext&lt;/code&gt; class, let&amp;rsquo;s set some flags!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;new_ec&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ExecutionContext&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_timer_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00000001&amp;#34;&lt;/span&gt;
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00000010&amp;#34;&lt;/span&gt;
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_postponed_job_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00000100&amp;#34;&lt;/span&gt;
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_trap_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00001000&amp;#34;&lt;/span&gt;
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_terminate_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00010000&amp;#34;&lt;/span&gt;
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_vm_barrier_interrupt&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00100000&amp;#34;&lt;/span&gt;
	
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_timer_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_postponed_job_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_trap_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_terminate_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_vm_barrier_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00111111&amp;#34;&lt;/span&gt;
	
new_ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_timer_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_trap_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_vm_barrier_interrupt
  &lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;interrupt_to_b
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; &amp;#34;00101011&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s nice we understand how to set them, but they don’t do anything on their own. They must be interpreted by one of the opt-in functions. We’ve been beating around the bush long enough. We’re opting-in, great. What do these opt-in functions actually &lt;em&gt;do&lt;/em&gt;?&lt;/p&gt;
&lt;h3 id=&#34;masks-all-the-way&#34;&gt;It&#39;s masks all the way down&lt;/h3&gt;
&lt;p&gt;Let’s start with &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt;. This is a macro that gets replaced with a function call to &lt;code&gt;rb_vm_check_ints&lt;/code&gt;. Inside of &lt;code&gt;rb_vm_check_ints&lt;/code&gt;, it calls &lt;code&gt;RUBY_VM_INTERRUPTED_ANY&lt;/code&gt;, and if that is true it calls &lt;code&gt;rb_threadptr_execute_interrupts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define RUBY_VM_CHECK_INTS(ec) rb_vm_check_ints(ec)
static inline void
rb_vm_check_ints(rb_execution_context_t *ec)
{
  if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {
    rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want to get to &lt;code&gt;rb_threadptr_execute_interrupts&lt;/code&gt;, but what does &lt;code&gt;RUBY_VM_INTERRUPTED_ANY&lt;/code&gt; do?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static inline bool
RUBY_VM_INTERRUPTED_ANY(rb_execution_context_t *ec)
{
	// ...
    return ATOMIC_LOAD_RELAXED(ec-&amp;gt;interrupt_flag) &amp;amp; ~(ec)-&amp;gt;interrupt_mask;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seems simple. Let’s translate the code into our Ruby &lt;code&gt;ExecutionContext&lt;/code&gt; class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ExecutionContext
  # ...
  def ruby_vm_interrupted_any?
    # (flag &amp;amp; ~mask) != 0
    (@interrupt_flag &amp;amp; ~@interrupt_mask) != 0
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then set a flag and try it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new_ec.ruby_vm_set_interrupt.ruby_vm_interrupted_any?
# `ruby_vm_interrupted_any?&#39;: undefined method `~&#39; for nil
# (@interrupt_flag &amp;amp; ~@interrupt_mask) != 0
#                    ^
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oops! I didn’t define &lt;code&gt;@interrupt_mask&lt;/code&gt;. What is that exactly!? Looks like it’s defined alongside the &lt;code&gt;interrupt_flag&lt;/code&gt; on the execution context.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct rb_execution_context_struct {
  // ...
  rb_atomic_t interrupt_flag;
  rb_atomic_t interrupt_mask;
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;👺 It’s a mask! It’s a flag! It’s a… confusing mental model…&lt;/p&gt;
&lt;p&gt;We have &lt;code&gt;interrupt_flag&lt;/code&gt;, we have the various &lt;code&gt;*_INTERRUPT_MASK&lt;/code&gt; constants, and now &lt;code&gt;interrupt_mask&lt;/code&gt;. Getting a little lost? I was.&lt;/p&gt;
&lt;p&gt;I think it’s helpful to think of &lt;code&gt;interrupt_flag&lt;/code&gt; as &lt;code&gt;interrupt_pending&lt;/code&gt;, and &lt;code&gt;interrupt_mask&lt;/code&gt; as &lt;code&gt;interrupt_blocked&lt;/code&gt;. &lt;code&gt;interrupt_flag&lt;/code&gt; contains operations waiting to run, and &lt;code&gt;interrupt_mask&lt;/code&gt; contains operations that are currently &lt;em&gt;blocked&lt;/em&gt; from running.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What is that &lt;code&gt;&amp;amp; ~&lt;/code&gt; business? Remember that &lt;code&gt;&amp;amp;&lt;/code&gt; is Bitwise AND, and will only return 1 if both bits are 1. &lt;code&gt;~&lt;/code&gt; is Bitwise NOT, and will change 1s to 0s, and 0s to 1s. As an example, using the &lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 0 0 0 0 0 0 0 0 = 0x0 = interrupt_flag
 0 0 0 0 0 0 0 0 = 0x0 = interrupt_mask
         |
         +------- TRAP_INTERRUPT_MASK

 0 0 0 0 1 0 0 0 &amp;amp;     # interrupt_flag
~0 0 0 0 1 0 0 0       # interrupt_mask

 0 0 0 0 1 0 0 0 &amp;amp;     # interrupt_flag
 1 1 1 1 0 1 1 1       # interrupt_mask

 0 0 0 0 0 0 0 0       # TRAP_INTERRUPT_MASK is blocked!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s only used in a few places - but it seems to serve roles on critical paths, like preventing recursive calls within &lt;code&gt;Signal#trap&lt;/code&gt; handlers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int
signal_exec(VALUE cmd, int sig)
{
    rb_execution_context_t *ec = GET_EC();
    volatile rb_atomic_t old_interrupt_mask = ec-&amp;gt;interrupt_mask;
    // ...
    ec-&amp;gt;interrupt_mask |= TRAP_INTERRUPT_MASK;
    // run signal handlers like Signal#trap
    ec-&amp;gt;interrupt_mask = old_interrupt_mask;
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the &lt;code&gt;interrupt_mask&lt;/code&gt; matches the &lt;code&gt;interrupt_flag&lt;/code&gt;, &lt;code&gt;RUBY_VM_INTERRUPTED_ANY&lt;/code&gt; won’t allow us to recursively trigger a signal handler. If we were to remove the &lt;code&gt;interrupt_mask&lt;/code&gt; check, this code would call itself recursively forever and stack overflow:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;pid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fork &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Signal&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;trap(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pid)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;, pid)
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;waitall
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Process.kill&amp;#39;: stack level too deep (SystemStackError)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;But &lt;em&gt;with&lt;/em&gt; the interrupt block code, it just runs forever, endlessly queueing up another trap. It’s kind of hard to find a compelling example of this mask - it mostly seems like very defensive programming!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’ve ever written a signal handler in rails and tried using a &lt;code&gt;Rails.logger&lt;/code&gt;, you’ve hit the &lt;code&gt;interrupt_mask&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;trap(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Rails&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;logger&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;info(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TRAP fired!&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;kill(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TERM&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pid)
&lt;span style=&#34;color:#75715e&#34;&gt;# log writing failed. can&amp;#39;t be called from trap context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is because &lt;code&gt;Mutex#lock&lt;/code&gt; raises an error if it is used inside an interrupt trap. If the interrupt mask has &lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt; set, it means we&amp;rsquo;re running in a &lt;code&gt;trap&lt;/code&gt; and blocking anymore trap interrupts from firing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static VALUE
do_mutex_lock(VALUE self, int interruptible_p)
{
  rb_execution_context_t *ec = GET_EC();
  rb_thread_t *th = ec-&amp;gt;thread_ptr;

  if (th-&amp;gt;ec-&amp;gt;interrupt_mask &amp;amp; TRAP_INTERRUPT_MASK) {
    rb_raise(rb_eThreadError, &amp;quot;can&#39;t be called from trap context&amp;quot;);
  }
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Internally, Rails.logger is a &lt;code&gt;Logger&lt;/code&gt; instance from the &lt;a href=&#34;https://rubygems.org/gems/logger/versions/1.7.0?locale=en&#34;&gt;&lt;code&gt;logger&lt;/code&gt;&lt;/a&gt; gem. That &lt;code&gt;Logger&lt;/code&gt; writes to logs using a &lt;code&gt;LogDevice&lt;/code&gt;. It uses the &lt;code&gt;MonitorMixin&lt;/code&gt;, which gives it a built-in &lt;code&gt;Monitor&lt;/code&gt; instance to &lt;code&gt;synchronize&lt;/code&gt; with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Logger&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;LogDevice&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;include&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MonitorMixin&lt;/span&gt;
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(message)
      handle_write_errors(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;writing&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
        synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# We can&amp;#39;t lock a mutex in a signal!&lt;/span&gt;
          &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 You can learn more about &lt;code&gt;Monitor&lt;/code&gt;s and &lt;code&gt;synchronize&lt;/code&gt; in my post on &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#monitor&#34;&gt;The Thread API&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For completeness, let’s add &lt;code&gt;@interrupt_mask&lt;/code&gt; to our &lt;code&gt;ExecutionContext&lt;/code&gt; class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ExecutionContext&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;
    @interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
    @interrupt_mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;with_interrupt_mask&lt;/span&gt;(mask)
    old_interrupt_mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @interrupt_mask
    @interrupt_mask &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; mask
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
    @interrupt_mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; old_interrupt_mask    
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_vm_interrupted_any?&lt;/span&gt;
    &lt;span style=&#34;color:#75715e&#34;&gt;# (flag &amp;amp; ~mask) != 0&lt;/span&gt;
    (@interrupt_flag &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;~&lt;/span&gt;@interrupt_mask) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mask_to_b&lt;/span&gt;
    to_b(@interrupt_mask)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now &lt;code&gt;#ruby_vm_interrupted_any?&lt;/code&gt; should work! And we can create sections where we block certain interrupts from being fired:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;ec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ExecutionContext&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_set_trap_interrupt
ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_interrupted_any? &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; true&lt;/span&gt;
ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;with_interrupt_mask(&lt;span style=&#34;color:#66d9ef&#34;&gt;TRAP_INTERRUPT_MASK&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  ec&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;ruby_vm_interrupted_any? &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; false&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;weve-been-waiting&#34;&gt;The `interrupt`ion we&#39;ve all been waiting for&lt;/h3&gt;
&lt;p&gt;Ok, now we know how to check and block the flags, we know the general places they are checked, and we know why it’s valuable for those checks to be efficient. Let’s look at what this has all led up to. What actually happens when an interrupt is detected? We’ll break it down piece-by-piece, but here’s the full function to start. Understanding this function gives us insight into when Ruby decides to yield, raise exceptions, and deliver signals:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;rb_threadptr_execute_interrupts&lt;/span&gt;(rb_thread_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;th, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; blocking_timing)
{
  rb_atomic_t interrupt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; postponed_job_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; FALSE;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;raised_flag) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ret;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ((interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; threadptr_get_interrupts(th)) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; sig;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; timer_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; pending_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; trap_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; terminate_interrupt;
	
    timer_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TIMER_INTERRUPT_MASK;
    pending_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; PENDING_INTERRUPT_MASK;
    postponed_job_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; POSTPONED_JOB_INTERRUPT_MASK;
    trap_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TRAP_INTERRUPT_MASK;
    terminate_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TERMINATE_INTERRUPT_MASK; &lt;span style=&#34;color:#75715e&#34;&gt;// request from other ractors
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; VM_BARRIER_INTERRUPT_MASK) {
      RB_VM_LOCKING();
    }
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (postponed_job_interrupt) {
      rb_postponed_job_flush(th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;vm);
    }
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (trap_interrupt) {
      &lt;span style=&#34;color:#75715e&#34;&gt;/* signal handling */&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ractor.main_thread) {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rb_thread_status prev_status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status;

        th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; THREAD_RUNNABLE;
        {
          &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ((sig &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_get_next_signal()) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
            ret &lt;span style=&#34;color:#f92672&#34;&gt;|=&lt;/span&gt; rb_signal_exec(th, sig);
          }
        }
        th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; prev_status;
      }
	
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;ccan_list_empty(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;interrupt_exec_tasks)) {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rb_thread_status prev_status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status;

        th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; THREAD_RUNNABLE;
        {
          threadptr_interrupt_exec_exec(th);
        }
        th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; prev_status;
      }
    }
	
    &lt;span style=&#34;color:#75715e&#34;&gt;/* exception from another thread */&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (pending_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; threadptr_pending_interrupt_active_p(th)) {
      VALUE err &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_threadptr_pending_interrupt_deque(th, blocking_timing &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; INTERRUPT_ON_BLOCKING : INTERRUPT_NONE);
      ret &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TRUE;
	
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (UNDEF_P(err)) {
        &lt;span style=&#34;color:#75715e&#34;&gt;/* no error */&lt;/span&gt;
      }
      &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (err &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; RUBY_FATAL_THREAD_KILLED        &lt;span style=&#34;color:#75715e&#34;&gt;/* Thread#kill received */&lt;/span&gt;   &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt;
               err &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; RUBY_FATAL_THREAD_TERMINATED   &lt;span style=&#34;color:#75715e&#34;&gt;/* Terminate thread */&lt;/span&gt;       &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt;
               err &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; INT2FIX(TAG_FATAL) &lt;span style=&#34;color:#75715e&#34;&gt;/* Thread.exit etc. */&lt;/span&gt;         ) {
        terminate_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
      }
      &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (err &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;vm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;special_exceptions[ruby_error_stream_closed]) {
          &lt;span style=&#34;color:#75715e&#34;&gt;/* the only special exception to be queued across thread */&lt;/span&gt;
          err &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ruby_vm_special_exception_copy(err);
        }
        &lt;span style=&#34;color:#75715e&#34;&gt;/* set runnable if th was slept. */&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; THREAD_STOPPED &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt;
            th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; THREAD_STOPPED_FOREVER)
          th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; THREAD_RUNNABLE;
        rb_exc_raise(err);
      }
    }
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (terminate_interrupt) {
      rb_threadptr_to_kill(th);
    }
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (timer_interrupt) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; limits_us &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; thread_default_quantum_ms &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;;
	
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
        limits_us &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;=&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
        limits_us &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority;
	
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; THREAD_RUNNABLE)
        th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;running_time_us &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 10ms = 10_000us
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	
      EXEC_EVENT_HOOK(th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ec, RUBY_INTERNAL_EVENT_SWITCH, th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;cfp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;self,
                      &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, Qundef);
	
      rb_thread_schedule_limits(limits_us);
    }
  }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ret;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There it is. Guess we’re done here! See you next time!&lt;/p&gt;
&lt;p&gt;What a joker I am 🙄.&lt;/p&gt;
&lt;p&gt;Let’s start walking through the function. Most of the function lives inside of a &lt;code&gt;while&lt;/code&gt; loop. The &lt;code&gt;while&lt;/code&gt; loop sets &lt;code&gt;interrupt&lt;/code&gt; to the return value of &lt;code&gt;threadptr_get_interrupts&lt;/code&gt;. That function gets the current &lt;code&gt;interrupt_flag &amp;amp; ~interrupt_mask&lt;/code&gt;, clearing out everything in &lt;code&gt;ec-&amp;gt;interrupt_flag&lt;/code&gt; in the process (except what was hidden by &lt;code&gt;ec-&amp;gt;interrupt_mask&lt;/code&gt;). We continue to iterate as long as the interrupt flag doesn’t come back with &lt;code&gt;0&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing)
{
  rb_atomic_t interrupt;
  int postponed_job_interrupt = 0;
  int ret = FALSE;

  // ...

  while ((interrupt = threadptr_get_interrupts(th)) != 0) {
    // ...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why are we using &lt;code&gt;while&lt;/code&gt; on a single int field, where we check for every mask at once? While we’re checking existing values we’ve pulled from the &lt;code&gt;interrupt_flag&lt;/code&gt;, it’s possible new bit masks have been set. If another mask gets set while we’re processing the current interrupts, we keep looping until we return &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next up we use a Bitwise AND to check which masks are currently set. If they’re set, the &lt;code&gt;int&lt;/code&gt; will be a non-zero value (truthy), otherwise &lt;code&gt;0&lt;/code&gt; (falsey). We’ll use those for the &lt;code&gt;if&lt;/code&gt; statements later on:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;rb_threadptr_execute_interrupts&lt;/span&gt;(rb_thread_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;th, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; blocking_timing)
{
  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; ((interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; threadptr_get_interrupts(th)) &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; sig;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; timer_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; pending_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; trap_interrupt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; terminate_interrupt;
	
    timer_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TIMER_INTERRUPT_MASK;
    pending_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; PENDING_INTERRUPT_MASK;
    postponed_job_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; POSTPONED_JOB_INTERRUPT_MASK;
    trap_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TRAP_INTERRUPT_MASK;
    terminate_interrupt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; interrupt &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; TERMINATE_INTERRUPT_MASK; &lt;span style=&#34;color:#75715e&#34;&gt;// request from other ractors
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;timer-interrupt-mask&#34;&gt;`TIMER_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;Now we start checking for work. Let’s start with time slice and priority handling with &lt;code&gt;TIMER_INTERRUPT_MASK&lt;/code&gt;. In &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt; I discussed how context gets switched between threads in Ruby:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are two common reasons context gets switched between threads in CRuby, which can result in operations only partially completing (ie, setting the proper result, then checking that result):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;~100ms of Ruby processing have elapsed&lt;/li&gt;
&lt;li&gt;A blocking operation has been invoked&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;TIMER_INTERRUPT_MASK&lt;/code&gt; condition is where we check for that processing time&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;. On Linux/Unix, CRuby maintains a timer thread which typically checks for work every 10ms. As part of that, it calls &lt;code&gt;RUBY_VM_SET_TIMER_INTERRUPT&lt;/code&gt;, which sets the &lt;code&gt;TIMER_INTERRUPT_MASK&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-timer-diagram.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The timer interrupt is fairly straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the current “quantum” (the CRuby name for the amount of time each thread can run before being context switched)&lt;/li&gt;
&lt;li&gt;Priority is used to increase or decrease the amount of time a thread can run above or below the default&lt;/li&gt;
&lt;li&gt;It is assumed the thread ran 10ms before this code, so it adds 10ms to the &lt;code&gt;running_time&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;An event hook is fired notifying interested plugins that a thread context switch is happening&lt;/li&gt;
&lt;li&gt;Calls &lt;code&gt;rb_thread_schedule_limits&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (timer_interrupt) {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; limits_us &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; thread_default_quantum_ms &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
    limits_us &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;=&lt;/span&gt; th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
    limits_us &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;priority;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;status &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; THREAD_RUNNABLE)
    th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;running_time_us &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;; &lt;span style=&#34;color:#75715e&#34;&gt;// 10ms = 10_000us
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	
  EXEC_EVENT_HOOK(th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ec,
    RUBY_INTERNAL_EVENT_SWITCH, th&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;cfp&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;self,
    &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, Qundef);
	
  rb_thread_schedule_limits(limits_us);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Since Ruby 3.4, you can set your own &lt;code&gt;thread_default_quantum_ms&lt;/code&gt; using the env variable &lt;a href=&#34;https://bugs.ruby-lang.org/issues/20861&#34;&gt;&lt;code&gt;RUBY_THREAD_TIMESLICE&lt;/code&gt;&lt;/a&gt;. This means the long-held CRuby constant of 100ms time slices is now adjustable, and folks have been adjusting it to handle &lt;a href=&#34;https://github.com/sidekiq/sidekiq/discussions/5039#discussioncomment-14064274&#34;&gt;different CPU saturated workloads&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;rb_thread_schedule_limits&lt;/code&gt; checks if the thread is over its allotted running time, and yields if so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void
rb_thread_schedule_limits(uint32_t limits_us)
{
  rb_thread_t *th = GET_THREAD();

  if (th-&amp;gt;running_time_us &amp;gt;= limits_us) {
    thread_sched_yield(TH_SCHED(th), th);
    rb_ractor_thread_switch(th-&amp;gt;ractor, th, true);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve discussed bit manipulation quite a bit - feels negligent to not briefly discuss that right and left bit shift for priority 🤷🏻‍♂️.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (th-&amp;gt;priority &amp;gt; 0)
  limits_us &amp;lt;&amp;lt;= th-&amp;gt;priority;
else
  limits_us &amp;gt;&amp;gt;= -th-&amp;gt;priority;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;th-&amp;gt;priority&lt;/code&gt; is greater than zero, we shift every bit left. If not, it negates the priority (so negative priorities turn positive) and shifts every bit right. We can demonstrate how this would work easily in Ruby, using milliseconds (&lt;code&gt;ms&lt;/code&gt;) instead of microseconds (&lt;code&gt;us&lt;/code&gt;) for simplicity:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculate_priority&lt;/span&gt;(priority, limit)
  priority &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; limit &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; priority : limit &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;priority
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
calculate_priority(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)        &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 100&lt;/span&gt;
calculate_priority(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)        &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 400&lt;/span&gt;
calculate_priority(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)       &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 25&lt;/span&gt;
to_b(&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)                         &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 01100100  = 100&lt;/span&gt;
to_b(calculate_priority(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;))  &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 01100100  = 100&lt;/span&gt;
to_b(calculate_priority(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;))  &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 110010000 = 400&lt;/span&gt;
to_b(calculate_priority(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)) &lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 00011001  = 25&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;#  01100100    01100100&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#    &amp;lt;&amp;lt; 2        &amp;gt;&amp;gt; 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 110010000    00011001&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That means that at the default quantum of 100ms, if you give a CRuby thread a priority of 2, it will be given 400ms of runtime before being forced to switch! And -2 means your thread will only run for 25ms at a time. When we shift right, we lose bits, which is why the value is lower.&lt;/p&gt;
&lt;h4 id=&#34;trap-interrupt-mask&#34;&gt;`TRAP_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;Now we’re onto signal handling using &lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt;. The first part is what you might expect from a “trap” interrupt - signal handling. According to this code - you’ll only &lt;em&gt;ever&lt;/em&gt; run trap handlers on the main thread. If there is a trap mask and we aren’t on the main thread, we ignore it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* signal handling */
if (th == th-&amp;gt;vm-&amp;gt;ractor.main_thread) {
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The thread in this Ruby example will always equal &lt;code&gt;Thread#main&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trap(&amp;quot;INT&amp;quot;) do
  puts &amp;quot;hello from #{Thread.current}: #{Thread.current == Thread.main}&amp;quot;
  # =&amp;gt; hello from #&amp;lt;Thread:0x000000010445b2a8 run&amp;gt;: true
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we iterate through each available signal. If multiple signals have not been processed, we process them all here. &lt;code&gt;rb_signal_exec&lt;/code&gt; internally calls &lt;code&gt;signal_exec&lt;/code&gt;, which we looked at earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while ((sig = rb_get_next_signal()) != 0) {
  ret |= rb_signal_exec(th, sig);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-signal-handling.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Prior to Ruby 3.4, that was the exclusive purpose of &lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt;. Ruby 3.4+ also uses it to alert other threads that there is work for them to execute. You put work into the threads &lt;code&gt;interrupt_exec_tasks&lt;/code&gt; list, and call &lt;code&gt;threadptr_interrupt_exec_exec&lt;/code&gt; on each thread:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!ccan_list_empty(&amp;amp;th-&amp;gt;interrupt_exec_tasks)) {
  // ...
  threadptr_interrupt_exec_exec(th);
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;threadptr_interrupt_exec_exec&lt;/code&gt; runs the requested task (a function), either in a new thread, or inline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (task-&amp;gt;flags &amp;amp; rb_interrupt_exec_flag_new_thread) {
  rb_thread_create(task-&amp;gt;func, task-&amp;gt;data);
}
else {
  (*task-&amp;gt;func)(task-&amp;gt;data);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seems generally handy, but was introduced for a specific purpose: supporting &lt;code&gt;require&lt;/code&gt; and &lt;code&gt;autoload&lt;/code&gt; inside of Ractors:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Ruby &amp;lt; 3.3
Ractor.new { pp &amp;quot;hey there!&amp;quot; } # autoloads `pp`
# =&amp;gt; `require&#39;: can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
Ractor.new {
  require &amp;quot;json&amp;quot;
  puts JSON.parse(&#39;&amp;quot;hey there!&amp;quot;&#39;)
}
# =&amp;gt; `require&#39;: can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)

# Ruby &amp;gt;= 3.4
Ractor.new { pp &amp;quot;hey there!&amp;quot; }
# =&amp;gt; &amp;quot;hey there!&amp;quot;
Ractor.new {
  require &amp;quot;json&amp;quot;
  puts JSON.parse(&#39;&amp;quot;hey there!&amp;quot;&#39;)
}
# =&amp;gt; hey there!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Requiring a gem requires accessing non-shareable objects - Ractors cannot access any state that is non-shareable. The only Ractor with access to these non-shareable objects is the main Ractor, &lt;code&gt;Ractor.main&lt;/code&gt;. To get around this, non-main Ractors add a task to the &lt;code&gt;interrupt_exec_tasks&lt;/code&gt; list on the main Ractor thread, and set &lt;code&gt;TRAP_INTERRUPT_MASK&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rb_ractor_t *main_r = GET_VM()-&amp;gt;ractor.main_ractor;
// for `require` calls
rb_ractor_interrupt_exec(main_r, ractor_require_func...)
// for autoloading, like when calling `pp`
rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func...)

// ...
// ractor_require_func/ractor_autoload_load_func are
//   referenced in task-&amp;gt;node
ccan_list_add_tail(&amp;amp;th-&amp;gt;interrupt_exec_tasks, &amp;amp;task-&amp;gt;node);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-ractor-require.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;pending-interrupt-mask&#34;&gt;`PENDING_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;Now we’ve got the heavy-hitter of thread interrupts - &lt;code&gt;PENDING_INTERRUPT_MASK&lt;/code&gt;. It’s not clear from the name, but this mask gets set by &lt;code&gt;Thread#raise&lt;/code&gt; and &lt;code&gt;Thread#kill&lt;/code&gt;. It doesn’t get much more interruptive than arbitrarily raising an error within, or killing a thread.&lt;/p&gt;
&lt;p&gt;The reason it’s called &lt;code&gt;PENDING_INTERRUPT_MASK&lt;/code&gt; is because it indicates there are errors waiting to be evaluated in the threads &lt;code&gt;pending_interrupt_queue&lt;/code&gt;. Every thread has a &lt;code&gt;pending_interrupt_queue&lt;/code&gt;, and it manages the interrupts that have been enqueued by calls like &lt;code&gt;Thread#raise&lt;/code&gt; and &lt;code&gt;Thread#kill&lt;/code&gt;. Sometimes those interrupts are actual error instances (&lt;code&gt;Thread#raise&lt;/code&gt;), and sometimes they are integer flags (&lt;code&gt;Thread#kill&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We start off by checking if there are any active pending interrupts in the queue. If there are, we dequeue the first available interrupt. The &lt;code&gt;blocking_timing&lt;/code&gt; relates to the &lt;code&gt;#handle_interrupt&lt;/code&gt; method, and we’ll dig into those next time in “When good threads go bad”. For now, just know it gives you the ability to defer the thread being interrupted:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* exception from another thread */
if (pending_interrupt &amp;amp;&amp;amp; threadptr_pending_interrupt_active_p(th)) {
  VALUE err = rb_threadptr_pending_interrupt_deque(th, blocking_timing ? INTERRUPT_ON_BLOCKING : INTERRUPT_NONE);
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/threads-pending-errors.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;We check if the dequeued interrupt is one of the flags set by &lt;code&gt;Thread#kill&lt;/code&gt;/&lt;code&gt;Thread#terminate&lt;/code&gt;/&lt;code&gt;Thread#exit&lt;/code&gt;, all representing that the thread should be killed immediately. We set the &lt;code&gt;terminate_interrupt&lt;/code&gt;, which later in the function triggers &lt;code&gt;rb_threadptr_to_kill&lt;/code&gt;. This kills the thread and cannot be rescued:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (/* Thread#kill received */
    err == RUBY_FATAL_THREAD_KILLED ||
    /* Terminate thread */
    err == RUBY_FATAL_THREAD_TERMINATED ||
    /* Thread.exit etc. */
    err == INT2FIX(TAG_FATAL)) {
  terminate_interrupt = 1;
}

// ...
// outside of the pending interrupt if statement
if (terminate_interrupt) {
  rb_threadptr_to_kill(th);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the error isn&amp;rsquo;t one of the &lt;code&gt;Thread#kill&lt;/code&gt; flags, it must be an actual Ruby exception. We make sure the thread is in a running state. Then we force it to raise an error at whatever point in the code it goes to execute next. This raises whatever error we set with &lt;code&gt;Thread#raise&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* set runnable if th was slept. */
if (th-&amp;gt;status == THREAD_STOPPED ||
    th-&amp;gt;status == THREAD_STOPPED_FOREVER)
  th-&amp;gt;status = THREAD_RUNNABLE;
rb_exc_raise(err);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Personally, I was surprised to find that these interrupts are stored in a queue! Can we try to prove it in our Ruby code? Let’s try:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CatchyError = Class.new(StandardError)

class ErrorCatcher
  def self.===(exception)
    exception.message =~ /1|2|3/
  end
end

t = Thread.new do
  sleep
rescue ErrorCatcher
  redo
rescue CatchyError
  raise
end

sleep 0.1
t.raise(CatchyError.new(&#39;1&#39;))
t.raise(CatchyError.new(&#39;2&#39;))
t.raise(CatchyError.new(&#39;3&#39;))
t.raise(CatchyError.new(&#39;4&#39;))
t.join
# =&amp;gt; #&amp;lt;Thread:0x0000000120961420 (irb):84 run&amp;gt; terminated with exception (report_on_exception is true):
#   in &#39;Kernel#sleep&#39;: 4 (CatchyError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We setup a dynamic error matcher so we can raise the same error, but catch it differently depending on the message&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;We &lt;code&gt;rescue&lt;/code&gt; and &lt;code&gt;redo&lt;/code&gt; if we get a &lt;code&gt;CatchyError&lt;/code&gt; with &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, or &lt;code&gt;3&lt;/code&gt; as the message&lt;/li&gt;
&lt;li&gt;Even though we &lt;code&gt;t.raise&lt;/code&gt; four times, only the fourth &lt;code&gt;CatchyError&lt;/code&gt; is raised. If you change the regex to match &lt;code&gt;/1|2/&lt;/code&gt;, it will fail on the third error instead&lt;/li&gt;
&lt;li&gt;It really is running through the queue of errors!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;terminate-interrupt-mask&#34;&gt;`TERMINATE_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;TERMINATE_INTERRUPT_MASK&lt;/code&gt; is pretty niche. You’ll remember this code from the &lt;code&gt;Thread#kill&lt;/code&gt; code earlier triggered by the &lt;code&gt;pending_interrupt_queue&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (terminate_interrupt) {
  rb_threadptr_to_kill(th);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are two ways to trigger that code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using &lt;code&gt;Thread#kill&lt;/code&gt;, as we already learned&lt;/li&gt;
&lt;li&gt;When a Ruby process is shutting down. As part of that shutdown, all Ractors are terminated, which set &lt;code&gt;TERMINATE_INTERRUPT_MASK&lt;/code&gt; on each of their threads&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;postponed-job-interrupt-mask&#34;&gt;`POSTPONED_JOB_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;Still in the niche-zone, we’ve got &lt;code&gt;POSTPONED_JOB_INTERRUPT_MASK&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This mask is used when work needs to be performed, but can’t safely run in its current context. By making it an interrupt mask, the work can be inserted into a safe point for execution in the CRuby runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (postponed_job_interrupt) {
  rb_postponed_job_flush(th-&amp;gt;vm);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;rb_postponed_job_flush&lt;/code&gt; function iterates through work in the &lt;code&gt;postponed_job_queue&lt;/code&gt;, calling each function in the queue.&lt;/p&gt;
&lt;p&gt;In CRuby, I can only find references to it in the &lt;code&gt;Tracepoint&lt;/code&gt; source code. In concept, it seems very similar to the &lt;code&gt;interrupt_exec_tasks&lt;/code&gt; used for &lt;code&gt;Ractor#require&lt;/code&gt;. I’m sure there is a CRuby committer who could explain this further - I’d be curious to understand it better!&lt;/p&gt;
&lt;h4 id=&#34;vm-barrier-interrupt-mask&#34;&gt;`VM_BARRIER_INTERRUPT_MASK`&lt;/h4&gt;
&lt;p&gt;Not to be outdone by &lt;code&gt;TERMINATE_INTERRUPT_MASK&lt;/code&gt; and &lt;code&gt;POSTPONED_JOB_INTERRUPT_MASK&lt;/code&gt;, we’ve got king-niche: &lt;code&gt;VM_BARRIER_INTERRUPT_MASK&lt;/code&gt;. When set, it runs &lt;code&gt;RB_VM_LOCKING()&lt;/code&gt; on the thread:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (interrupt &amp;amp; VM_BARRIER_INTERRUPT_MASK) {
  RB_VM_LOCKING();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s niche, but seems to play an important role in giving the entire VM exclusive access to an operation. This appears to have been introduced with Ractors in Ruby 3. That makes sense - Ractors are the first truly parallel unit of execution in Ruby.&lt;/p&gt;
&lt;p&gt;Certain operations, like YJIT compiling bytecode, require exclusive access to the VM when running. For instance, when &lt;code&gt;rb_yjit_compile_iseq&lt;/code&gt; is called, the first thing it does is call &lt;code&gt;rb_vm_barrier&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void
rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
{
    RB_VM_LOCKING() {
        rb_vm_barrier();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;rb_vm_barrier&lt;/code&gt; sets the &lt;code&gt;VM_BARRIER_INTERRUPT_MASK&lt;/code&gt; on all running threads across Ractors, then waits for each to stop at the barrier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// interrupts all running threads
rb_thread_t *ith;
ccan_list_for_each(&amp;amp;vm-&amp;gt;ractor.sched.running_threads, ith, sched.node.running_threads) {
  if (ith-&amp;gt;ractor != cr) {
    RUBY_VM_SET_VM_BARRIER_INTERRUPT(ith-&amp;gt;ec);
  }
}

// wait for other ractors
while (!ractor_sched_barrier_completed_p(vm)) {
  ractor_sched_set_unlocked(vm, cr);
  rb_native_cond_wait(&amp;amp;vm-&amp;gt;ractor.sched.barrier_complete_cond, &amp;amp;vm-&amp;gt;ractor.sched.lock);
  ractor_sched_set_locked(vm, cr);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/untitled-diagram-2025-10-22-051842.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;😮‍💨 We dug &lt;em&gt;deep&lt;/em&gt; in this one. Bitmasks, CRuby internals, Thread management - what could be next? With all this knowledge, we’re primed and ready to dig into what to do when a thread goes rogue. See you next time in “When good threads go bad” 👋🏼&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;With &lt;a href=&#34;https://bugs.ruby-lang.org/issues/20861&#34;&gt;Aaron Patterson&amp;rsquo;s PR&lt;/a&gt; to have a configurable quantum, this can be configured now. But whatever it’s set to is still static during a programs execution, and still defaults to 100ms&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs&#34;&gt;https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It can still happen, but it’s less likely. We’ll discuss ways it can happen later in the series&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;JRuby does as well! &lt;a href=&#34;https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/RubyThread.java#L822&#34;&gt;https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/RubyThread.java#L822&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The different number syntaxes in Ruby (binary, octal, decimal, hex) are really just sugar on the Integer class. So when you run these code snippets you’ll actually see a decimal integer rather than the binary I show in the comment&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If you don’t want to use a &lt;code&gt;while&lt;/code&gt; loop, there’s a great alternative here: &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3m2ntcgzhe22c&#34;&gt;https://bsky.app/profile/jpcamara.com/post/3m2ntcgzhe22c&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;1,001,102, to be exact&lt;/p&gt;
&lt;p&gt;See &lt;a href=&#34;https://jpcamara.com/2024/11/28/counting-c-method.html&#34;&gt;https://jpcamara.com/2024/11/28/counting-c-method.html&lt;/a&gt; for how I got that number&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If we simplified the loop to be &lt;code&gt;while(true); end&lt;/code&gt;, we’d actually exclusively use the &lt;code&gt;jump&lt;/code&gt; instruction: &lt;a href=&#34;https://redgetan.cc/understanding-timeouts-in-cruby/#6-working-examples&#34;&gt;https://redgetan.cc/understanding-timeouts-in-cruby/#6-working-examples&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;But interesting way to think about an if statement!&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Fun fact - if you run this example with YJIT it goes back down to five hundred thousand. YJIT seems to inline the method call and that bypasses the ints check&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This particular check is technically linux/unix specific. On Windows, &lt;code&gt;thread_win32.c&lt;/code&gt; is used and it maintains its own timer thread and priority controls specific to Windows.&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Thanks to the Honeybadger blog for the tip on dynamic exception matchers!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.honeybadger.io/blog/level-up-ruby-rescue-with-dynamic-exception-matchers/&#34;&gt;https://www.honeybadger.io/blog/level-up-ruby-rescue-with-dynamic-exception-matchers/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2025/threads-copy-of-pending-errors.drawio.png)

&gt; 👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into several parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt; - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my!](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “Bitmasks, Threads and Interrupts, oh my!&#34;. I’ll update the links as each part is released, and include these links in each post.

- [Interrupting Threads](#interrupting-threads)
	- [Managing threads](#managing-threads)
	- [An important interruption](#important-interruption)
	- [Why integer masks?](#integer-masks)
	- [Checking for interrupts](#checking-for-interrupts)
	- [Jumping to instructions](#jumping-to-instructions)
	- [Pervasive interrupts](#pervasive-interrupts)
	- [Interrupt masks](#interrupt-masks)
	- [It’s masks all the way down](#masks-all-the-way)
	- [The `interrupt`ion we’ve all been waiting for](#weve-been-waiting)
		- [`TIMER_INTERRUPT_MASK`](#timer-interrupt-mask)
		- [`TRAP_INTERRUPT_MASK`](#trap-interrupt-mask)
		- [`PENDING_INTERRUPT_MASK`](#pending-interrupt-mask)
		- [`TERMINATE_INTERRUPT_MASK`](#terminate-interrupt-mask)
		- [`POSTPONED_JOB_INTERRUPT_MASK`](#postponed-job-interrupt-mask)
		- [`VM_BARRIER_INTERRUPT_MASK`](#vm-barrier-interrupt-mask)

&lt;h2 id=&#34;interrupting-threads&#34;&gt;Interrupting Threads 🧵&lt;/h2&gt;

The Ruby thread scheduler is _rude_. There, I said it. 

It’s always telling your threads how to run, when to run, how long they can run - it stops them whenever it wants and then tells _other_ threads _they_ have to start working. It’s bossy as hell. On top of that, it’s not even polite about it. It feels free to _interrupt_ your threads - it decides when, and your threads have to just play along and listen.

But threads put up with it. They even _benefit_ from it, if you can believe that. The thread scheduler may be abrupt, but it&#39;s looking out for the runtime. If that’s the case - there must be a good reason… Why do threads put up with these scheduler shenanigans?

&lt;h3 id=&#34;managing-threads&#34;&gt;Managing threads 👷🏼‍♂️👷🏻‍♀️&lt;/h3&gt;

An important purpose of a thread scheduler is efficiency and fairness. It manages what threads are running, for how long, and what context each thread gets loaded with.

In normal operation this comes down to a few things:

- Time sharing: every thread, under normal circumstances, gets roughly 100ms of CPU runtime[^1]. Since only one thread can run Ruby code at a time[^2], this keeps a single thread from dominating the program[^3]
- Blocking operations: certain operations will “block” a thread. Most forms of IO, and `sleep`, for instance. When the thread blocks, the thread scheduler allows other threads to run
- Priority: we can suggest a priority for our threads, and the thread scheduler will take it into consideration when choosing what to run next and for how long
- Passing control: we can suggest actions to the thread scheduler, like `pass`ing control or `stop`ping the current thread so other threads can take over
- Locking: we can synchronize access to resources, and the thread scheduler chooses the order of access to the resource

How does it handle all this?

&lt;h3 id=&#34;important-interruption&#34;&gt;An important interruption&lt;/h3&gt;

The scheduler isn’t a single object - it is the behavior produced by specific VM checks and functions which opt-in to thread scheduling behavior. In this post, we’ll focus on time sharing, priority, and interrupting threads - which are managed through a concept deeply woven into CRuby - a set of functions the VM calls at specific checkpoints.

In the CRuby runtime, this concept revolves around “interrupts”[^4]. It contains a set of possible events that could “interrupt” the flow of a Ruby program in general, and different threads in particular. There are several interrupt events possible:

- Timer interrupt
- Trap interrupt
- Pending interrupt
- Terminate interrupt
- VM barrier interrupt
- Postponed job interrupt

In the CRuby internals, these are represented by an integer mask. If we took the C code and represented it in Ruby, it would look like this (each mask is a hex value):

```ruby
TIMER_INTERRUPT_MASK         = 0x01
PENDING_INTERRUPT_MASK       = 0x02
POSTPONED_JOB_INTERRUPT_MASK = 0x04
TRAP_INTERRUPT_MASK          = 0x08
TERMINATE_INTERRUPT_MASK     = 0x10
VM_BARRIER_INTERRUPT_MASK    = 0x20
```
&lt;h3 id=&#34;integer-masks&#34;&gt;Why integer masks?&lt;/h3&gt;

What is an integer mask, and why would CRuby represent internal states this way?

&gt; 📝 “mask” is a conventional name for a pattern to isolate specific bits. A “mask” sounds like something that would cover up something else (ie, a mask covering your face). In a sense, these serve a similar purpose - the mask is put on or taken off, clearing or setting bits and testing them efficiently.

Integer masks provide a compact and efficient way to represent multiple program states within a single number. Each state is stored as a single bit - representing a power of 2 - so in concept a 64-bit integer can represent up to 64 independent on/off states. Here’s a visualization of the first 8 bits in a number (a byte):

	0 0 0 0 0 0 0 0
	| | | | | | | |
	| | | | | | | +- 1        (2^0)     0x01
	| | | | | | +--- 2        (2^1)     0x02
	| | | | | +----- 4        (2^2)     0x04
	| | | | +------- 8        (2^3)     0x08
	| | | +--------- 16       (2^4)     0x10
	| | +----------- 32       (2^5)     0x20
	| +------------- 64       (2^6)     0x40
	+--------------- 128      (2^7)     0x80
	      Byte       Decimal  Power     Hex

&gt; This is an adaptation of a visual from a blog post about bitwise operations: [https://www.hendrik-erz.de/post/bitwise-flags-are-beautiful-and-heres-why](https://www.hendrik-erz.de/post/bitwise-flags-are-beautiful-and-heres-why)

Being able to represent all this information in a compact way is convenient, but checking whether it matches a particular mask is also _very_ fast. CPUs are well optimized for this sort of thing.

There are several operators for performing these checks called “bitwise” operators. Here’s a table of the operators, and the impact they have on bits:

&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;&lt;/th&gt;
			&lt;th&gt;
				AND
			&lt;/th&gt;
			&lt;th&gt;
				OR
			&lt;/th&gt;
			&lt;th&gt;
				XOR
			&lt;/th&gt;
			&lt;th&gt;
				NOT
			&lt;/th&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;th&gt;
				A
			&lt;/th&gt;
			&lt;th&gt;
				B
			&lt;/th&gt;
			&lt;th&gt;
				A &amp; B
			&lt;/th&gt;
			&lt;th&gt;
				A | B
			&lt;/th&gt;
			&lt;th&gt;
				A ^ B
			&lt;/th&gt;
			&lt;th&gt;
				~A
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				1
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
			&lt;td&gt;
				0
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

We can demonstrate using the Ruby binary syntax[^5]:

- Bitwise AND:

```ruby
  0b00000101 &amp;
  0b00000110
# 0b00000100
```
- Bitwise OR:

```ruby
  0b00000101
  0b00000110
# 0b00000111
```
- Bitwise XOR:

```ruby
  0b00000101 ^
  0b00000110
# 0b00000011
```
- Bitwise NOT:

```ruby
  (~0b11111000) &amp; 0xFF
# 0b00000111
```
&gt; The `&amp; 0xFF` forces us into 8-bits to demonstrate the `NOT` correctly

These aren’t relevant to masks specifically, but for completeness, you can also shift bits left or right to change values:

	  0b00000101 &lt;&lt; 1 # Left shift
	# 0b00001010

	  0b00000101 &gt;&gt; 1 # Right shift
	# 0b00000010

&lt;h3 id=&#34;checking-for-interrupts&#34;&gt;Checking for interrupts&lt;/h3&gt;

The reason these efficient mask checks matter, is because these interrupts are checked _a lot_.

Here’s a program that simply iterates for a little while, incrementing a counter[^6]:

```ruby
i = 0
while i &lt; 500_000
  i += 1
end
```
This will check interrupts _five hundred thousand times_[^7], one check for each iteration of the loop. That’s a lot. And if your programming language is going to do something a lot, it needs to be efficient. The overhead of checking for interrupts should be undetectable in your Ruby program. As discussed earlier, bit mask checks are one of the most efficient checks you can make.

But why does this innocuous program need to check for interrupts so often? It’s part of the thread scheduler opt-in! The Ruby virtual machine is filled with checkpoints where it is safe for Ruby internals to check for interruptions in the program. One of those checkpoints is an `if` statement (did you think I’d say `while` loop?!).

Let’s [disassemble this into Ruby bytecode](https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html#inside-the-vm):

```ruby
puts RubyVMInstructionSequence.compile(
  DATA.read
).disassemble
	
__END__
i = 0
while i &lt; 500_000
  i += 1
end
```
Which gives us the following:

	0000 putobject_INT2FIX_0_
	0001 setlocal_WC_0           i@0    # | i = 0
	0003 jump                    16     # | jump to while i &lt; ... 
	...
	0009 getlocal_WC_0           i@0    # | i += 1
	0011 putobject_INT2FIX_1_           # |
	0012 opt_plus                       # |
	0014 setlocal_WC_0           i@0.   # |____________
	0016 getlocal_WC_0           i@0    # | i &lt; 500_000
	0018 putobject               500000 # |
	0020 opt_lt                         # |
	0022 branchif                9      # | jump to instruction 9,
	                                    # | which is i += 1
	                                    # |____________
	0024 putnil
	0025 leave

For the moment you can trust me that `branchif` is the critical section here. Let’s see how `branchif ` is defined:

	DEFINE_INSN
	branchif
	(OFFSET dst)
	(VALUE val)
	()
	{
	    if (RTEST(val)) {
	        RUBY_VM_CHECK_INTS(ec);
	        JUMP(dst);
	    }
	}

&gt; ❗️Woah! What the heck is that weird syntax? Is that Ruby? Is that C?
&gt; 
&gt; It’s neither! This is a special, CRuby internal specific DSL that is _similar_ to C. In CRuby, there is a file called `insns.def` which defines every instruction the Ruby Virtual Machine (YARV) can run. 
&gt; 
&gt; - `DEFINE_INSN` tells us we are defining an instruction
&gt; - `branchif` is the instruction name
&gt; - `OFFSET dst` is the argument - 9 in our case, which would take us to `0009 getlocal_WC_0`
&gt; - `VALUE val` is the last value pushed on the stack - the result of `i &lt; 500_000`
&gt; - `()` that last empty set of parens is the optional return value - we don’t have one - we jump if `val` is true, or we fall through

Interesting! A couple things stick out to me here when the`RTEST(val)` (our `while` condition) is true:

1. We’re running `RUBY_VM_CHECK_INTS` anytime we call an `if` statement. `RUBY_VM_CHECK_INTS` is a key function for checking the interrupt queue. It’s embedded within VM instructions themselves!
2. We `JUMP` to a destination[^8]

&gt; Fun fact: one of the places `RUBY_VM_CHECK_INTS` is called is from the `once` bytecode instruction. An unexpected callback to my article [The o in Ruby regex stands for “oh the humanity!”](https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html)

&lt;h3 id=&#34;jumping-to-instructions&#34;&gt;Jumping to instructions&lt;/h3&gt;

In the typical case of a `branchif`, it would jump to the appropriate part of an `if` statement:

```ruby
if is_it_true?
  # if it is true, jump here!
else
  # if it isn&#39;t, jump here!
end
```
What caught my eye is that internally an `if` statement basically acts like `goto`! Sorry [Dijkstra](https://en.wikipedia.org/wiki/Considered_harmful)[^9].

And because `branchif` can jump anywhere you tell it, that also means it can jump to _previous_ code as well. In the case of a `while` loop, `branchif` truly takes on its `goto` roots. Instead of jumping to a future piece of code, it reruns the content of the `while` loop by jumping back to earlier instructions!

&lt;h3 id=&#34;pervasive-interrupts&#34;&gt;Pervasive interrupts&lt;/h3&gt;

Want to double the number of checks from our example? Let’s `add` a method:

```ruby
def add(a, b)
  a + b
end
	
i = 0
j = 0
while i &lt; 500_000
  i += 1
  j = add(i, j)
end
```
Now CRuby checks the interrupt queue _one million times_[^10]! That’s because of the `opt_send_without_block` instruction, which is one of the instructions for Ruby method calls:

	DEFINE_INSN
	opt_send_without_block
	(CALL_DATA cd)
	(...)
	(VALUE val)
	{
	    // ...
	    val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
	    // Before returning from exec, int check!
	    JIT_EXEC(ec, val);
	    // ...
	}

&gt; 👆More of that fancy CRuby DSL

We know that interrupts are woven into vm instructions themselves in `insns.def`, but it&#39;s not alone. Interrupts are checked throughout CRuby. For example, in:

- IO
- Threads
- Processes
- The Regex engine
- BigNumber


And you&#39;ll find the checks in various forms: `RUBY_VM_CHECK_INTS_BLOCKING`, `RUBY_VM_CHECK_INTS`, `rb_thread_check_ints`, `vm_check_ints_blocking`, `vm_check_ints`, etc.


We know _what_ gets called to check for interrupts - but how do these &#34;ints&#34; get set by CRuby? 

&lt;h3 id=&#34;interrupt-masks&#34;&gt;Interrupt masks&lt;/h3&gt;

CRuby has macros for setting each of the interrupt flags:

```c
#define RUBY_VM_SET_TIMER_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, TIMER_INTERRUPT_MASK)
#define RUBY_VM_SET_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, PENDING_INTERRUPT_MASK)
#define RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, POSTPONED_JOB_INTERRUPT_MASK)
#define RUBY_VM_SET_TRAP_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, TRAP_INTERRUPT_MASK)
#define RUBY_VM_SET_TERMINATE_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, TERMINATE_INTERRUPT_MASK)
#define RUBY_VM_SET_VM_BARRIER_INTERRUPT(ec)
  ATOMIC_OR((ec)-&gt;interrupt_flag, VM_BARRIER_INTERRUPT_MASK)
```
&gt; 📝 `ec` in these examples refers to the “execution context”, which contains per-thread information about the running Ruby program

This `ATOMIC_OR` macro is an abstraction on top of bitwise operations that stays roughly as efficient, but makes sure the operations run atomically. Multiple operating system threads can run this at the same time - `ATOMIC_OR` helps to avoid [read-modify-write](https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write) issues.

Those abstractions obscure the actual bitwise operation - we learned a bit about Ruby bitwise operations earlier, let’s show these macros in Ruby form for clarity:

```rb
class ExecutionContext
  def initialize
    @interrupt_flag = 0
  end

  def ruby_vm_set_timer_interrupt
    @interrupt_flag |= TIMER_INTERRUPT_MASK
    self
  end
	
  def ruby_vm_set_interrupt
    @interrupt_flag |= PENDING_INTERRUPT_MASK
    self
  end
	
  def ruby_vm_set_postponed_job_interrupt
    @interrupt_flag |= POSTPONED_JOB_INTERRUPT_MASK
    self
  end
	
  def ruby_vm_set_trap_interrupt
    @interrupt_flag |= TRAP_INTERRUPT_MASK
    self
  end
	
  def ruby_vm_set_terminate_interrupt
    @interrupt_flag |= TERMINATE_INTERRUPT_MASK
    self
  end
	
  def ruby_vm_set_vm_barrier_interrupt
    @interrupt_flag |= VM_BARRIER_INTERRUPT_MASK
    self
  end
end
```
Let&#39;s also add some binary conversion methods so we can conveniently print our results:

```ruby
def to_b(number)
  number.to_s(2).rjust(8, &#39;0&#39;)
end
	
class ExecutionContext
  # ...
  def interrupt_to_b
    to_b(@interrupt_flag)
  end
end
```
&gt;  📝 [Integer#to_s](https://docs.ruby-lang.org/en/master/Integer.html#method-i-to_s) can be handed a `base`, which converts to the specified base before returning as a string. In our case, we are converting it to base 2 to show it as binary. We then `rjust` to pad the left side with 0’s. So for instance, this returns `to_b(2)` as `00000010`.

For reference, here are the CRuby interrupt masks and which bits they set in our `interrupt_flag`:

	0 0 0 0 0 0 0 0 = 0x0 = interrupt_flag
	    | | | | | |
	    | | | | | +- TIMER_INTERRUPT_MASK
	    | | | | +--- PENDING_INTERRUPT_MASK
	    | | | +----- POSTPONED_JOB_INTERRUPT_MASK
	    | | +------- TRAP_INTERRUPT_MASK
	    | +--------- TERMINATE_INTERRUPT_MASK
	    +----------- VM_BARRIER_INTERRUPT_MASK

Knowing that, and equipped with our `ExecutionContext` class, let&#39;s set some flags!

```ruby
def new_ec
  ExecutionContext.new
end
	
new_ec.ruby_vm_set_timer_interrupt.interrupt_to_b
# =&gt; &#34;00000001&#34;
new_ec.ruby_vm_set_interrupt.interrupt_to_b
# =&gt; &#34;00000010&#34;
new_ec.ruby_vm_set_postponed_job_interrupt.interrupt_to_b
# =&gt; &#34;00000100&#34;
new_ec.ruby_vm_set_trap_interrupt.interrupt_to_b
# =&gt; &#34;00001000&#34;
new_ec.ruby_vm_set_terminate_interrupt.interrupt_to_b
# =&gt; &#34;00010000&#34;
new_ec.ruby_vm_set_vm_barrier_interrupt.interrupt_to_b
# =&gt; &#34;00100000&#34;
	
new_ec.ruby_vm_set_timer_interrupt
  .ruby_vm_set_interrupt
  .ruby_vm_set_postponed_job_interrupt
  .ruby_vm_set_trap_interrupt
  .ruby_vm_set_terminate_interrupt
  .ruby_vm_set_vm_barrier_interrupt
  .interrupt_to_b
# =&gt; &#34;00111111&#34;
	
new_ec.ruby_vm_set_timer_interrupt
  .ruby_vm_set_interrupt
  .ruby_vm_set_trap_interrupt
  .ruby_vm_set_vm_barrier_interrupt
  .interrupt_to_b
# =&gt; &#34;00101011&#34;
```
It&#39;s nice we understand how to set them, but they don’t do anything on their own. They must be interpreted by one of the opt-in functions. We’ve been beating around the bush long enough. We’re opting-in, great. What do these opt-in functions actually _do_?

&lt;h3 id=&#34;masks-all-the-way&#34;&gt;It&#39;s masks all the way down&lt;/h3&gt;

Let’s start with `RUBY_VM_CHECK_INTS`. This is a macro that gets replaced with a function call to `rb_vm_check_ints`. Inside of `rb_vm_check_ints`, it calls `RUBY_VM_INTERRUPTED_ANY`, and if that is true it calls `rb_threadptr_execute_interrupts`:

	#define RUBY_VM_CHECK_INTS(ec) rb_vm_check_ints(ec)
	static inline void
	rb_vm_check_ints(rb_execution_context_t *ec)
	{
	  if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {
	    rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);
	  }
	}

We want to get to `rb_threadptr_execute_interrupts`, but what does `RUBY_VM_INTERRUPTED_ANY` do?

	static inline bool
	RUBY_VM_INTERRUPTED_ANY(rb_execution_context_t *ec)
	{
		// ...
	    return ATOMIC_LOAD_RELAXED(ec-&gt;interrupt_flag) &amp; ~(ec)-&gt;interrupt_mask;
	}

Seems simple. Let’s translate the code into our Ruby `ExecutionContext` class:

	class ExecutionContext
	  # ...
	  def ruby_vm_interrupted_any?
	    # (flag &amp; ~mask) != 0
	    (@interrupt_flag &amp; ~@interrupt_mask) != 0
	  end
	end

Then set a flag and try it:

	new_ec.ruby_vm_set_interrupt.ruby_vm_interrupted_any?
	# `ruby_vm_interrupted_any?&#39;: undefined method `~&#39; for nil
	# (@interrupt_flag &amp; ~@interrupt_mask) != 0
	#                    ^

Oops! I didn’t define `@interrupt_mask`. What is that exactly!? Looks like it’s defined alongside the `interrupt_flag` on the execution context.

	struct rb_execution_context_struct {
	  // ...
	  rb_atomic_t interrupt_flag;
	  rb_atomic_t interrupt_mask;
	  // ...
	}

&gt; 👺 It’s a mask! It’s a flag! It’s a… confusing mental model…
&gt; 
&gt; We have `interrupt_flag`, we have the various `*_INTERRUPT_MASK` constants, and now `interrupt_mask`. Getting a little lost? I was.
&gt; 
&gt; I think it’s helpful to think of `interrupt_flag` as `interrupt_pending`, and `interrupt_mask` as `interrupt_blocked`. `interrupt_flag` contains operations waiting to run, and `interrupt_mask` contains operations that are currently _blocked_ from running.

What is that `&amp; ~` business? Remember that `&amp;` is Bitwise AND, and will only return 1 if both bits are 1. `~` is Bitwise NOT, and will change 1s to 0s, and 0s to 1s. As an example, using the `TRAP_INTERRUPT_MASK`:

	 0 0 0 0 0 0 0 0 = 0x0 = interrupt_flag
	 0 0 0 0 0 0 0 0 = 0x0 = interrupt_mask
	         |
	         +------- TRAP_INTERRUPT_MASK
	
	 0 0 0 0 1 0 0 0 &amp;     # interrupt_flag
	~0 0 0 0 1 0 0 0       # interrupt_mask
	
	 0 0 0 0 1 0 0 0 &amp;     # interrupt_flag
	 1 1 1 1 0 1 1 1       # interrupt_mask
	
	 0 0 0 0 0 0 0 0       # TRAP_INTERRUPT_MASK is blocked!

It’s only used in a few places - but it seems to serve roles on critical paths, like preventing recursive calls within `Signal#trap` handlers:

	static int
	signal_exec(VALUE cmd, int sig)
	{
	    rb_execution_context_t *ec = GET_EC();
	    volatile rb_atomic_t old_interrupt_mask = ec-&gt;interrupt_mask;
	    // ...
	    ec-&gt;interrupt_mask |= TRAP_INTERRUPT_MASK;
	    // run signal handlers like Signal#trap
	    ec-&gt;interrupt_mask = old_interrupt_mask;
	    // ...
	}

Because the `interrupt_mask` matches the `interrupt_flag`, `RUBY_VM_INTERRUPTED_ANY` won’t allow us to recursively trigger a signal handler. If we were to remove the `interrupt_mask` check, this code would call itself recursively forever and stack overflow:

```ruby
pid = fork do
  Signal.trap(&#34;TERM&#34;) do
    Process.kill(&#34;TERM&#34;, Process.pid)
  end
end
Process.kill(&#34;TERM&#34;, pid)
Process.waitall
	
# Process.kill&#39;: stack level too deep (SystemStackError)
```
&gt; But _with_ the interrupt block code, it just runs forever, endlessly queueing up another trap. It’s kind of hard to find a compelling example of this mask - it mostly seems like very defensive programming!

If you’ve ever written a signal handler in rails and tried using a `Rails.logger`, you’ve hit the `interrupt_mask`.

```ruby
trap(&#34;TERM&#34;) do
  Rails.logger.info(&#34;TRAP fired!&#34;)
end
Process.kill(&#34;TERM&#34;, Process.pid)
# log writing failed. can&#39;t be called from trap context
```
This is because `Mutex#lock` raises an error if it is used inside an interrupt trap. If the interrupt mask has `TRAP_INTERRUPT_MASK` set, it means we&#39;re running in a `trap` and blocking anymore trap interrupts from firing:

	static VALUE
	do_mutex_lock(VALUE self, int interruptible_p)
	{
	  rb_execution_context_t *ec = GET_EC();
	  rb_thread_t *th = ec-&gt;thread_ptr;
	
	  if (th-&gt;ec-&gt;interrupt_mask &amp; TRAP_INTERRUPT_MASK) {
	    rb_raise(rb_eThreadError, &#34;can&#39;t be called from trap context&#34;);
	  }
	  // ...
	}

Internally, Rails.logger is a `Logger` instance from the [`logger`](https://rubygems.org/gems/logger/versions/1.7.0?locale=en) gem. That `Logger` writes to logs using a `LogDevice`. It uses the `MonitorMixin`, which gives it a built-in `Monitor` instance to `synchronize` with:

```ruby
class Logger
  class LogDevice
    include MonitorMixin
	
    def write(message)
      handle_write_errors(&#34;writing&#34;) do
        synchronize do # We can&#39;t lock a mutex in a signal!
          # ...
        end
      end
    end
  end
end
```
&gt; 📝 You can learn more about `Monitor`s and `synchronize` in my post on [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html#monitor)

For completeness, let’s add `@interrupt_mask` to our `ExecutionContext` class:

```ruby
class ExecutionContext
  def initialize
    @interrupt_flag = 0
    @interrupt_mask = 0
  end
  # ...
	
  def with_interrupt_mask(mask)
    old_interrupt_mask = @interrupt_mask
    @interrupt_mask |= mask
  ensure
    @interrupt_mask = old_interrupt_mask    
  end
	
  def ruby_vm_interrupted_any?
    # (flag &amp; ~mask) != 0
    (@interrupt_flag &amp; ~@interrupt_mask) != 0
  end
	
  def mask_to_b
    to_b(@interrupt_mask)
  end
end
```
Now `#ruby_vm_interrupted_any?` should work! And we can create sections where we block certain interrupts from being fired:

```ruby
ec = ExecutionContext.new
ec.ruby_vm_set_trap_interrupt
ec.ruby_vm_interrupted_any? # =&gt; true
ec.with_interrupt_mask(TRAP_INTERRUPT_MASK) do
  ec.ruby_vm_interrupted_any? # =&gt; false
end
```
&lt;h3 id=&#34;weve-been-waiting&#34;&gt;The `interrupt`ion we&#39;ve all been waiting for&lt;/h3&gt;

Ok, now we know how to check and block the flags, we know the general places they are checked, and we know why it’s valuable for those checks to be efficient. Let’s look at what this has all led up to. What actually happens when an interrupt is detected? We’ll break it down piece-by-piece, but here’s the full function to start. Understanding this function gives us insight into when Ruby decides to yield, raise exceptions, and deliver signals:

```c
int
rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing)
{
  rb_atomic_t interrupt;
  int postponed_job_interrupt = 0;
  int ret = FALSE;
	
  if (th-&gt;ec-&gt;raised_flag) return ret;
	
  while ((interrupt = threadptr_get_interrupts(th)) != 0) {
    int sig;
    int timer_interrupt;
    int pending_interrupt;
    int trap_interrupt;
    int terminate_interrupt;
	
    timer_interrupt = interrupt &amp; TIMER_INTERRUPT_MASK;
    pending_interrupt = interrupt &amp; PENDING_INTERRUPT_MASK;
    postponed_job_interrupt = interrupt &amp; POSTPONED_JOB_INTERRUPT_MASK;
    trap_interrupt = interrupt &amp; TRAP_INTERRUPT_MASK;
    terminate_interrupt = interrupt &amp; TERMINATE_INTERRUPT_MASK; // request from other ractors
	
    if (interrupt &amp; VM_BARRIER_INTERRUPT_MASK) {
      RB_VM_LOCKING();
    }
	
    if (postponed_job_interrupt) {
      rb_postponed_job_flush(th-&gt;vm);
    }
	
    if (trap_interrupt) {
      /* signal handling */
      if (th == th-&gt;vm-&gt;ractor.main_thread) {
        enum rb_thread_status prev_status = th-&gt;status;

        th-&gt;status = THREAD_RUNNABLE;
        {
          while ((sig = rb_get_next_signal()) != 0) {
            ret |= rb_signal_exec(th, sig);
          }
        }
        th-&gt;status = prev_status;
      }
	
      if (!ccan_list_empty(&amp;th-&gt;interrupt_exec_tasks)) {
        enum rb_thread_status prev_status = th-&gt;status;

        th-&gt;status = THREAD_RUNNABLE;
        {
          threadptr_interrupt_exec_exec(th);
        }
        th-&gt;status = prev_status;
      }
    }
	
    /* exception from another thread */
    if (pending_interrupt &amp;&amp; threadptr_pending_interrupt_active_p(th)) {
      VALUE err = rb_threadptr_pending_interrupt_deque(th, blocking_timing ? INTERRUPT_ON_BLOCKING : INTERRUPT_NONE);
      ret = TRUE;
	
      if (UNDEF_P(err)) {
        /* no error */
      }
      else if (err == RUBY_FATAL_THREAD_KILLED        /* Thread#kill received */   ||
               err == RUBY_FATAL_THREAD_TERMINATED   /* Terminate thread */       ||
               err == INT2FIX(TAG_FATAL) /* Thread.exit etc. */         ) {
        terminate_interrupt = 1;
      }
      else {
        if (err == th-&gt;vm-&gt;special_exceptions[ruby_error_stream_closed]) {
          /* the only special exception to be queued across thread */
          err = ruby_vm_special_exception_copy(err);
        }
        /* set runnable if th was slept. */
        if (th-&gt;status == THREAD_STOPPED ||
            th-&gt;status == THREAD_STOPPED_FOREVER)
          th-&gt;status = THREAD_RUNNABLE;
        rb_exc_raise(err);
      }
    }
	
    if (terminate_interrupt) {
      rb_threadptr_to_kill(th);
    }
	
    if (timer_interrupt) {
      uint32_t limits_us = thread_default_quantum_ms * 1000;
	
      if (th-&gt;priority &gt; 0)
        limits_us &lt;&lt;= th-&gt;priority;
      else
        limits_us &gt;&gt;= -th-&gt;priority;
	
      if (th-&gt;status == THREAD_RUNNABLE)
        th-&gt;running_time_us += 10 * 1000; // 10ms = 10_000us
	
      EXEC_EVENT_HOOK(th-&gt;ec, RUBY_INTERNAL_EVENT_SWITCH, th-&gt;ec-&gt;cfp-&gt;self,
                      0, 0, 0, Qundef);
	
      rb_thread_schedule_limits(limits_us);
    }
  }
  return ret;
}
```
There it is. Guess we’re done here! See you next time!

What a joker I am 🙄.

Let’s start walking through the function. Most of the function lives inside of a `while` loop. The `while` loop sets `interrupt` to the return value of `threadptr_get_interrupts`. That function gets the current `interrupt_flag &amp; ~interrupt_mask`, clearing out everything in `ec-&gt;interrupt_flag` in the process (except what was hidden by `ec-&gt;interrupt_mask`). We continue to iterate as long as the interrupt flag doesn’t come back with `0`:

	int
	rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing)
	{
	  rb_atomic_t interrupt;
	  int postponed_job_interrupt = 0;
	  int ret = FALSE;
	
	  // ...
	
	  while ((interrupt = threadptr_get_interrupts(th)) != 0) {
	    // ...
	  }
	}

Why are we using `while` on a single int field, where we check for every mask at once? While we’re checking existing values we’ve pulled from the `interrupt_flag`, it’s possible new bit masks have been set. If another mask gets set while we’re processing the current interrupts, we keep looping until we return `0`.

Next up we use a Bitwise AND to check which masks are currently set. If they’re set, the `int` will be a non-zero value (truthy), otherwise `0` (falsey). We’ll use those for the `if` statements later on:

```c
int
rb_threadptr_execute_interrupts(rb_thread_t *th, int blocking_timing)
{
  // ...
	
  while ((interrupt = threadptr_get_interrupts(th)) != 0) {
    int sig;
    int timer_interrupt;
    int pending_interrupt;
    int trap_interrupt;
    int terminate_interrupt;
	
    timer_interrupt = interrupt &amp; TIMER_INTERRUPT_MASK;
    pending_interrupt = interrupt &amp; PENDING_INTERRUPT_MASK;
    postponed_job_interrupt = interrupt &amp; POSTPONED_JOB_INTERRUPT_MASK;
    trap_interrupt = interrupt &amp; TRAP_INTERRUPT_MASK;
    terminate_interrupt = interrupt &amp; TERMINATE_INTERRUPT_MASK; // request from other ractors
    // ...
  }
}
```
&lt;h4 id=&#34;timer-interrupt-mask&#34;&gt;`TIMER_INTERRUPT_MASK`&lt;/h4&gt;

Now we start checking for work. Let’s start with time slice and priority handling with `TIMER_INTERRUPT_MASK`. In [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs) I discussed how context gets switched between threads in Ruby:

&gt; There are two common reasons context gets switched between threads in CRuby, which can result in operations only partially completing (ie, setting the proper result, then checking that result):
&gt; 
&gt; 1. ~100ms of Ruby processing have elapsed
&gt; 2. A blocking operation has been invoked


The `TIMER_INTERRUPT_MASK` condition is where we check for that processing time[^11]. On Linux/Unix, CRuby maintains a timer thread which typically checks for work every 10ms. As part of that, it calls `RUBY_VM_SET_TIMER_INTERRUPT`, which sets the `TIMER_INTERRUPT_MASK`. 

![](https://cdn.uploads.micro.blog/98548/2025/threads-timer-diagram.png)

The timer interrupt is fairly straightforward:

1. Get the current “quantum” (the CRuby name for the amount of time each thread can run before being context switched)
2. Priority is used to increase or decrease the amount of time a thread can run above or below the default
3. It is assumed the thread ran 10ms before this code, so it adds 10ms to the `running_time`
4. An event hook is fired notifying interested plugins that a thread context switch is happening
5. Calls `rb_thread_schedule_limits`.

```c
if (timer_interrupt) {
  uint32_t limits_us = thread_default_quantum_ms * 1000;
	
  if (th-&gt;priority &gt; 0)
    limits_us &lt;&lt;= th-&gt;priority;
  else
    limits_us &gt;&gt;= -th-&gt;priority;
	
  if (th-&gt;status == THREAD_RUNNABLE)
    th-&gt;running_time_us += 10 * 1000; // 10ms = 10_000us
	
  EXEC_EVENT_HOOK(th-&gt;ec,
    RUBY_INTERNAL_EVENT_SWITCH, th-&gt;ec-&gt;cfp-&gt;self,
    0, 0, 0, Qundef);
	
  rb_thread_schedule_limits(limits_us);
}
```
&gt; Since Ruby 3.4, you can set your own `thread_default_quantum_ms` using the env variable [`RUBY_THREAD_TIMESLICE`](https://bugs.ruby-lang.org/issues/20861). This means the long-held CRuby constant of 100ms time slices is now adjustable, and folks have been adjusting it to handle [different CPU saturated workloads](https://github.com/sidekiq/sidekiq/discussions/5039#discussioncomment-14064274).

`rb_thread_schedule_limits` checks if the thread is over its allotted running time, and yields if so:

	static void
	rb_thread_schedule_limits(uint32_t limits_us)
	{
	  rb_thread_t *th = GET_THREAD();
	
	  if (th-&gt;running_time_us &gt;= limits_us) {
	    thread_sched_yield(TH_SCHED(th), th);
	    rb_ractor_thread_switch(th-&gt;ractor, th, true);
	  }
	}

We’ve discussed bit manipulation quite a bit - feels negligent to not briefly discuss that right and left bit shift for priority 🤷🏻‍♂️.

	if (th-&gt;priority &gt; 0)
	  limits_us &lt;&lt;= th-&gt;priority;
	else
	  limits_us &gt;&gt;= -th-&gt;priority;

If `th-&gt;priority` is greater than zero, we shift every bit left. If not, it negates the priority (so negative priorities turn positive) and shifts every bit right. We can demonstrate how this would work easily in Ruby, using milliseconds (`ms`) instead of microseconds (`us`) for simplicity:

```ruby
def calculate_priority(priority, limit)
  priority &gt; 0 ? limit &lt;&lt; priority : limit &gt;&gt; -priority
end
	
calculate_priority(0, 100)        # =&gt; 100
calculate_priority(2, 100)        # =&gt; 400
calculate_priority(-2, 100)       # =&gt; 25
to_b(100)                         # =&gt; 01100100  = 100
to_b(calculate_priority(0, 100))  # =&gt; 01100100  = 100
to_b(calculate_priority(2, 100))  # =&gt; 110010000 = 400
to_b(calculate_priority(-2, 100)) # =&gt; 00011001  = 25
	
#  01100100    01100100
#    &lt;&lt; 2        &gt;&gt; 2
# 110010000    00011001
```
That means that at the default quantum of 100ms, if you give a CRuby thread a priority of 2, it will be given 400ms of runtime before being forced to switch! And -2 means your thread will only run for 25ms at a time. When we shift right, we lose bits, which is why the value is lower.

&lt;h4 id=&#34;trap-interrupt-mask&#34;&gt;`TRAP_INTERRUPT_MASK`&lt;/h4&gt;

Now we’re onto signal handling using `TRAP_INTERRUPT_MASK`. The first part is what you might expect from a “trap” interrupt - signal handling. According to this code - you’ll only _ever_ run trap handlers on the main thread. If there is a trap mask and we aren’t on the main thread, we ignore it:

	/* signal handling */
	if (th == th-&gt;vm-&gt;ractor.main_thread) {
	  // ...
	}

The thread in this Ruby example will always equal `Thread#main`:

	trap(&#34;INT&#34;) do
	  puts &#34;hello from #{Thread.current}: #{Thread.current == Thread.main}&#34;
	  # =&gt; hello from #&lt;Thread:0x000000010445b2a8 run&gt;: true
	end

Next we iterate through each available signal. If multiple signals have not been processed, we process them all here. `rb_signal_exec` internally calls `signal_exec`, which we looked at earlier:

	while ((sig = rb_get_next_signal()) != 0) {
	  ret |= rb_signal_exec(th, sig);
	}

![](https://cdn.uploads.micro.blog/98548/2025/threads-signal-handling.drawio.png)

Prior to Ruby 3.4, that was the exclusive purpose of `TRAP_INTERRUPT_MASK`. Ruby 3.4+ also uses it to alert other threads that there is work for them to execute. You put work into the threads `interrupt_exec_tasks` list, and call `threadptr_interrupt_exec_exec` on each thread:

	if (!ccan_list_empty(&amp;th-&gt;interrupt_exec_tasks)) {
	  // ...
	  threadptr_interrupt_exec_exec(th);
	  // ...
	}

`threadptr_interrupt_exec_exec` runs the requested task (a function), either in a new thread, or inline:

	if (task-&gt;flags &amp; rb_interrupt_exec_flag_new_thread) {
	  rb_thread_create(task-&gt;func, task-&gt;data);
	}
	else {
	  (*task-&gt;func)(task-&gt;data);
	}

Seems generally handy, but was introduced for a specific purpose: supporting `require` and `autoload` inside of Ractors:

	# Ruby &lt; 3.3
	Ractor.new { pp &#34;hey there!&#34; } # autoloads `pp`
	# =&gt; `require&#39;: can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
	Ractor.new {
	  require &#34;json&#34;
	  puts JSON.parse(&#39;&#34;hey there!&#34;&#39;)
	}
	# =&gt; `require&#39;: can not access non-shareable objects in constant Kernel::RUBYGEMS_ACTIVATION_MONITOR by non-main ractor. (Ractor::IsolationError)
	
	# Ruby &gt;= 3.4
	Ractor.new { pp &#34;hey there!&#34; }
	# =&gt; &#34;hey there!&#34;
	Ractor.new {
	  require &#34;json&#34;
	  puts JSON.parse(&#39;&#34;hey there!&#34;&#39;)
	}
	# =&gt; hey there!

Requiring a gem requires accessing non-shareable objects - Ractors cannot access any state that is non-shareable. The only Ractor with access to these non-shareable objects is the main Ractor, `Ractor.main`. To get around this, non-main Ractors add a task to the `interrupt_exec_tasks` list on the main Ractor thread, and set `TRAP_INTERRUPT_MASK`:

	rb_ractor_t *main_r = GET_VM()-&gt;ractor.main_ractor;
	// for `require` calls
	rb_ractor_interrupt_exec(main_r, ractor_require_func...)
	// for autoloading, like when calling `pp`
	rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func...)
	
	// ...
	// ractor_require_func/ractor_autoload_load_func are
	//   referenced in task-&gt;node
	ccan_list_add_tail(&amp;th-&gt;interrupt_exec_tasks, &amp;task-&gt;node);

![](https://cdn.uploads.micro.blog/98548/2025/threads-ractor-require.drawio.png)

&lt;h4 id=&#34;pending-interrupt-mask&#34;&gt;`PENDING_INTERRUPT_MASK`&lt;/h4&gt;

Now we’ve got the heavy-hitter of thread interrupts - `PENDING_INTERRUPT_MASK`. It’s not clear from the name, but this mask gets set by `Thread#raise` and `Thread#kill`. It doesn’t get much more interruptive than arbitrarily raising an error within, or killing a thread.

The reason it’s called `PENDING_INTERRUPT_MASK` is because it indicates there are errors waiting to be evaluated in the threads `pending_interrupt_queue`. Every thread has a `pending_interrupt_queue`, and it manages the interrupts that have been enqueued by calls like `Thread#raise` and `Thread#kill`. Sometimes those interrupts are actual error instances (`Thread#raise`), and sometimes they are integer flags (`Thread#kill`).

We start off by checking if there are any active pending interrupts in the queue. If there are, we dequeue the first available interrupt. The `blocking_timing` relates to the `#handle_interrupt` method, and we’ll dig into those next time in “When good threads go bad”. For now, just know it gives you the ability to defer the thread being interrupted:

	/* exception from another thread */
	if (pending_interrupt &amp;&amp; threadptr_pending_interrupt_active_p(th)) {
	  VALUE err = rb_threadptr_pending_interrupt_deque(th, blocking_timing ? INTERRUPT_ON_BLOCKING : INTERRUPT_NONE);
	  // ...
	}

![](https://cdn.uploads.micro.blog/98548/2025/threads-pending-errors.drawio.png)

We check if the dequeued interrupt is one of the flags set by `Thread#kill`/`Thread#terminate`/`Thread#exit`, all representing that the thread should be killed immediately. We set the `terminate_interrupt`, which later in the function triggers `rb_threadptr_to_kill`. This kills the thread and cannot be rescued:

	if (/* Thread#kill received */
	    err == RUBY_FATAL_THREAD_KILLED ||
	    /* Terminate thread */
	    err == RUBY_FATAL_THREAD_TERMINATED ||
	    /* Thread.exit etc. */
	    err == INT2FIX(TAG_FATAL)) {
	  terminate_interrupt = 1;
	}
	
	// ...
	// outside of the pending interrupt if statement
	if (terminate_interrupt) {
	  rb_threadptr_to_kill(th);
	}

If the error isn&#39;t one of the `Thread#kill` flags, it must be an actual Ruby exception. We make sure the thread is in a running state. Then we force it to raise an error at whatever point in the code it goes to execute next. This raises whatever error we set with `Thread#raise`:

	/* set runnable if th was slept. */
	if (th-&gt;status == THREAD_STOPPED ||
	    th-&gt;status == THREAD_STOPPED_FOREVER)
	  th-&gt;status = THREAD_RUNNABLE;
	rb_exc_raise(err);

Personally, I was surprised to find that these interrupts are stored in a queue! Can we try to prove it in our Ruby code? Let’s try:

	CatchyError = Class.new(StandardError)
	
	class ErrorCatcher
	  def self.===(exception)
	    exception.message =~ /1|2|3/
	  end
	end
	
	t = Thread.new do
	  sleep
	rescue ErrorCatcher
	  redo
	rescue CatchyError
	  raise
	end
	
	sleep 0.1
	t.raise(CatchyError.new(&#39;1&#39;))
	t.raise(CatchyError.new(&#39;2&#39;))
	t.raise(CatchyError.new(&#39;3&#39;))
	t.raise(CatchyError.new(&#39;4&#39;))
	t.join
	# =&gt; #&lt;Thread:0x0000000120961420 (irb):84 run&gt; terminated with exception (report_on_exception is true):
	#   in &#39;Kernel#sleep&#39;: 4 (CatchyError)

In the above code:

- We setup a dynamic error matcher so we can raise the same error, but catch it differently depending on the message[^12]
- We `rescue` and `redo` if we get a `CatchyError` with `1`, `2`, or `3` as the message
- Even though we `t.raise` four times, only the fourth `CatchyError` is raised. If you change the regex to match `/1|2/`, it will fail on the third error instead
- It really is running through the queue of errors!

&lt;h4 id=&#34;terminate-interrupt-mask&#34;&gt;`TERMINATE_INTERRUPT_MASK`&lt;/h4&gt;

`TERMINATE_INTERRUPT_MASK` is pretty niche. You’ll remember this code from the `Thread#kill` code earlier triggered by the `pending_interrupt_queue`:

	if (terminate_interrupt) {
	  rb_threadptr_to_kill(th);
	}

There are two ways to trigger that code:

1. Using `Thread#kill`, as we already learned
2. When a Ruby process is shutting down. As part of that shutdown, all Ractors are terminated, which set `TERMINATE_INTERRUPT_MASK` on each of their threads

&lt;h4 id=&#34;postponed-job-interrupt-mask&#34;&gt;`POSTPONED_JOB_INTERRUPT_MASK`&lt;/h4&gt;

Still in the niche-zone, we’ve got `POSTPONED_JOB_INTERRUPT_MASK`.

This mask is used when work needs to be performed, but can’t safely run in its current context. By making it an interrupt mask, the work can be inserted into a safe point for execution in the CRuby runtime:

	if (postponed_job_interrupt) {
	  rb_postponed_job_flush(th-&gt;vm);
	}

The `rb_postponed_job_flush` function iterates through work in the `postponed_job_queue`, calling each function in the queue. 

In CRuby, I can only find references to it in the `Tracepoint` source code. In concept, it seems very similar to the `interrupt_exec_tasks` used for `Ractor#require`. I’m sure there is a CRuby committer who could explain this further - I’d be curious to understand it better!

&lt;h4 id=&#34;vm-barrier-interrupt-mask&#34;&gt;`VM_BARRIER_INTERRUPT_MASK`&lt;/h4&gt;

Not to be outdone by `TERMINATE_INTERRUPT_MASK` and `POSTPONED_JOB_INTERRUPT_MASK`, we’ve got king-niche: `VM_BARRIER_INTERRUPT_MASK`. When set, it runs `RB_VM_LOCKING()` on the thread:

	if (interrupt &amp; VM_BARRIER_INTERRUPT_MASK) {
	  RB_VM_LOCKING();
	}

It’s niche, but seems to play an important role in giving the entire VM exclusive access to an operation. This appears to have been introduced with Ractors in Ruby 3. That makes sense - Ractors are the first truly parallel unit of execution in Ruby. 

Certain operations, like YJIT compiling bytecode, require exclusive access to the VM when running. For instance, when `rb_yjit_compile_iseq` is called, the first thing it does is call `rb_vm_barrier`:

	void
	rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception)
	{
	    RB_VM_LOCKING() {
	        rb_vm_barrier();

`rb_vm_barrier` sets the `VM_BARRIER_INTERRUPT_MASK` on all running threads across Ractors, then waits for each to stop at the barrier:

	// interrupts all running threads
	rb_thread_t *ith;
	ccan_list_for_each(&amp;vm-&gt;ractor.sched.running_threads, ith, sched.node.running_threads) {
	  if (ith-&gt;ractor != cr) {
	    RUBY_VM_SET_VM_BARRIER_INTERRUPT(ith-&gt;ec);
	  }
	}
	
	// wait for other ractors
	while (!ractor_sched_barrier_completed_p(vm)) {
	  ractor_sched_set_unlocked(vm, cr);
	  rb_native_cond_wait(&amp;vm-&gt;ractor.sched.barrier_complete_cond, &amp;vm-&gt;ractor.sched.lock);
	  ractor_sched_set_locked(vm, cr);
	}

———

![](https://cdn.uploads.micro.blog/98548/2025/untitled-diagram-2025-10-22-051842.png)

😮‍💨 We dug _deep_ in this one. Bitmasks, CRuby internals, Thread management - what could be next? With all this knowledge, we’re primed and ready to dig into what to do when a thread goes rogue. See you next time in “When good threads go bad” 👋🏼

[^1]:	With [Aaron Patterson&#39;s PR](https://bugs.ruby-lang.org/issues/20861) to have a configurable quantum, this can be configured now. But whatever it’s set to is still static during a programs execution, and still defaults to 100ms

[^2]:	https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs

[^3]:	It can still happen, but it’s less likely. We’ll discuss ways it can happen later in the series

[^4]:	JRuby does as well! [https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/RubyThread.java#L822](https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/RubyThread.java#L822)

[^5]:	The different number syntaxes in Ruby (binary, octal, decimal, hex) are really just sugar on the Integer class. So when you run these code snippets you’ll actually see a decimal integer rather than the binary I show in the comment

[^6]:	If you don’t want to use a `while` loop, there’s a great alternative here: [https://bsky.app/profile/jpcamara.com/post/3m2ntcgzhe22c](https://bsky.app/profile/jpcamara.com/post/3m2ntcgzhe22c)

[^7]:	1,001,102, to be exact

    See [https://jpcamara.com/2024/11/28/counting-c-method.html](https://jpcamara.com/2024/11/28/counting-c-method.html) for how I got that number

[^8]:	If we simplified the loop to be `while(true); end`, we’d actually exclusively use the `jump` instruction: [https://redgetan.cc/understanding-timeouts-in-cruby/#6-working-examples](https://redgetan.cc/understanding-timeouts-in-cruby/#6-working-examples)

[^9]:	But interesting way to think about an if statement!

[^10]:	Fun fact - if you run this example with YJIT it goes back down to five hundred thousand. YJIT seems to inline the method call and that bypasses the ints check

[^11]:	This particular check is technically linux/unix specific. On Windows, `thread_win32.c` is used and it maintains its own timer thread and priority controls specific to Windows.

[^12]:	Thanks to the Honeybadger blog for the tip on dynamic exception matchers!

    [https://www.honeybadger.io/blog/level-up-ruby-rescue-with-dynamic-exception-matchers/](https://www.honeybadger.io/blog/level-up-ruby-rescue-with-dynamic-exception-matchers/)
</source:markdown>
    </item>
    
    <item>
      <title>The /o in Ruby regex stands for “oh the humanity!”</title>
      <link>https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html</link>
      <pubDate>Sat, 02 Aug 2025 00:16:06 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2025/08/02/the-o-in-ruby-regex.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2025/2ca9b9c306.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your code using the &lt;code&gt;/o&lt;/code&gt; modifier&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&#34;https://upload.wikimedia.org/wikipedia/commons/1/1c/Hindenburg_disaster.jpg&#34;&gt;wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hi there! Do you like Regex? Do you like performance? Do you like creating confounding bugs for yourself rooted in the mechanics of the Ruby VM itself?&lt;/p&gt;
&lt;p&gt;If you said yes to all of the above, have I got a feature for you!&lt;/p&gt;
&lt;p&gt;But first, let’s start with a story.&lt;/p&gt;
&lt;h3 id=&#34;the-cliffs-of-insanity&#34;&gt;The cliffs of insanity&lt;/h3&gt;
&lt;p&gt;I was recently reviewing some code, and part of the functionality was about matching. A class took an array of strings, and you could call a method to see if an input matched part of any of the strings. Stripped down, it was effectively the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Matcher
  def initialize(matchables)
    @matchables = matchables
  end

  def matches_any?(input)
    @matchables.any? { |m| m.match?(/#{input}/io) }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;I &lt;em&gt;know&lt;/em&gt; there are some of you reading this code and thinking “does this really need a regex?”, “couldn’t it just use &lt;code&gt;include?&lt;/code&gt; and some downcasing?”, “does this even need to exist?”, etc, etc. I see you, I hear you, I’d probably think the same, and I &lt;em&gt;promise&lt;/em&gt; you the specifics of this method aren’t that important.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Functionally, the code looked &lt;em&gt;ok&lt;/em&gt; to me. I knew what &lt;code&gt;/i&lt;/code&gt; was (a regex in &lt;a href=&#34;https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Case-Insensitive+Mode&#34;&gt;case-insensitive mode&lt;/a&gt;), but I didn’t recognize &lt;code&gt;/o&lt;/code&gt;. It didn’t seem critically important to lookup yet. Tests were not exhaustive but were green, and so I went to run the code in a console:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ruby&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;something else&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;javascript&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, that seemed broken. Assuming it was a bug, I looked at the code to see what was wrong. But nothing stuck out. The code &lt;em&gt;seemed&lt;/em&gt; ok, and simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@matchables.any? |m| m.match?(/#{input/io) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So I deconstructed the code and ran it directly, outside the class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;any? { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;m&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(&lt;span style=&#34;color:#e6db74&#34;&gt;/ruby/io&lt;/span&gt;) }
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;any? { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;m&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(&lt;span style=&#34;color:#e6db74&#34;&gt;/something else/io&lt;/span&gt;) }
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Even weirder. Run directly, it ran as expected. What if I started a new console, and ran the original class in a different order?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;something else&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ruby&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Matcher&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;something else&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;matches_any?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Ruby!&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, that was interesting. It seems like whatever value I sent to &lt;code&gt;matches_any?&lt;/code&gt; was cached for every run after that point, even for &lt;em&gt;brand new objects&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I looked through the class again. There was no weird memoization. No class-level variables. No thread locals. I was instantiating the class each time I ran &lt;code&gt;matches_any?&lt;/code&gt;. I was out of ideas for the predictable things that cause unpredictable results. What else was there?&lt;/p&gt;
&lt;h3 id=&#34;o-the-humanity&#34;&gt;/o the humanity!&lt;/h3&gt;
&lt;p&gt;With nothing else to investigate, I finally looked up the docs for what the &lt;code&gt;/o&lt;/code&gt; regex modifier does. &lt;code&gt;/o&lt;/code&gt; is referred to as “Interpolation mode”, which sounded pretty harmless. The Ruby docs have a succinct section on the expected behavior: &lt;a href=&#34;https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Interpolation+Mode&#34;&gt;https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Interpolation+Mode&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifier o means that the first time a literal regexp with interpolations is encountered, the generated Regexp object is saved and used for all future evaluations of that literal regexp&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Just reading that, I still wasn’t sure I’d expect what I was seeing. It almost sounded like internally Ruby would cache &lt;em&gt;each&lt;/em&gt; different interpolation that comes through. As if it would maybe reuse an internal regex if the same string value was interpolated. They provide a code example that makes it a little clearer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def letters; sleep 5; /[A-Z][a-z]/; end
words = %w[abc def xyz]
start = Time.now
words.each {|word| word.match(/\A[#{letters}]+\z/) }
Time.now - start # =&amp;gt; 15.0174892

start = Time.now
words.each {|word| word.match(/\A[#{letters}]+\z/o) }
Time.now - start # =&amp;gt; 5.0010866
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;letters&lt;/code&gt; method in the first example is called three times. One time for each iteration, as expected, taking 15 seconds total.&lt;/p&gt;
&lt;p&gt;In the second example, the code iterates three times, but the &lt;code&gt;letters&lt;/code&gt; method is called &lt;em&gt;once&lt;/em&gt;, taking 5 seconds total.&lt;/p&gt;
&lt;p&gt;Knowing that, I went back to the original code with new eyes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Matcher&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(matchables)
    @matchables &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; matchables
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;matches_any?&lt;/span&gt;(input)
    @matchables&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;any? { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;m&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
      m&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(
        &lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;input&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/io&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# This is run once, _ever_&lt;/span&gt;
      )
    }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What that meant for me is that the regular expression inside of &lt;code&gt;matches_any?&lt;/code&gt; was interpolating the &lt;em&gt;first value it &lt;strong&gt;ever&lt;/strong&gt; receives&lt;/em&gt;. Past that point the regex &lt;em&gt;never interpolated another value&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As it turned out, the developer had read about it as a potential regex optimization. It seemed harmless enough, so they added it. Now that I knew why I was hitting the issue, I removed the &lt;code&gt;/o&lt;/code&gt; and everything started working properly.&lt;/p&gt;
&lt;p&gt;But it still was not clear to me &lt;em&gt;how&lt;/em&gt; this was possible. What in the world is Ruby doing internally? Let’s figure it out together.&lt;/p&gt;
&lt;h3 id=&#34;inside-the-vm&#34;&gt;Inside the VM&lt;/h3&gt;
&lt;p&gt;Sometimes the only way to understand a behavior in Ruby is to drop a bit lower. Let’s disassemble the code into Ruby VM byte code, to see if it gives us any clues. I&amp;rsquo;ll use the &lt;code&gt;DATA&lt;/code&gt; feature to be able to put it into a script directly (you can find more about &lt;a href=&#34;https://ruby-doc.org/3.4.1/Object.html#DATA&#34;&gt;that syntax here&lt;/a&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;puts &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;InstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;compile(&lt;span style=&#34;color:#66d9ef&#34;&gt;DATA&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disassemble
	
&lt;span style=&#34;color:#75715e&#34;&gt;__END__
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;class Matcher
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  def initialize(matchables)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    @matchables = matchables
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  end
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  def matches_any?(input)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    @matchables.any? { |m| m.match?(/#{input}/io) }
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  end
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a bunch of other instructions you’ll see if you run that code, but we’ll focus on the instructions specific to the block in the &lt;code&gt;matches_any?&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# { |m| m.match?(/#{input/io) }

== disasm: #&amp;lt;ISeq:block in matches_any?@&amp;lt;compiled&amp;gt;:7 (7,21)-(7,51)&amp;gt;
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] m@0&amp;lt;AmbiguousArg&amp;gt;
0000 getlocal_WC_0                          m@0                       (   7)[LiBc]
0002 once                                   block (2 levels) in matches_any?, &amp;lt;is:0&amp;gt;
0005 opt_send_without_block                 &amp;lt;calldata!mid:match?, argc:1, ARGS_SIMPLE&amp;gt;
0007 leave                                  [Br]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It describes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting local variable &lt;code&gt;m&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Running the… &lt;code&gt;once&lt;/code&gt; instruction?&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;m&lt;/code&gt; with &lt;code&gt;opt_send_without_block&lt;/code&gt; with a method id (&lt;code&gt;mid&lt;/code&gt;) of &lt;code&gt;match?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Then &lt;code&gt;leave&lt;/code&gt;ing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these instructions are &lt;em&gt;very&lt;/em&gt; common in the Ruby VM. All except one - the &lt;code&gt;once&lt;/code&gt; instruction. I’ve never heard of that!&lt;/p&gt;
&lt;p&gt;What is &lt;code&gt;once&lt;/code&gt;? It can’t exist almost solely for the purposes of this extremely strange regex modifier, can it? Surely this modifier is not built into the very &lt;em&gt;bones&lt;/em&gt; of Ruby?&lt;/p&gt;
&lt;h3 id=&#34;once-upon-a-time&#34;&gt;&lt;code&gt;once&lt;/code&gt; upon a time&lt;/h3&gt;
&lt;p&gt;If you are ever curious to know how Ruby VM instructions get interpreted in CRuby, there is a central file to all of it called &lt;code&gt;insns.def&lt;/code&gt;. It contains all of the available YARV (Yet Another Ruby VM) instructions in a C-esque format which is compiled into an actual C file as part of building the language.&lt;/p&gt;
&lt;p&gt;In normal program execution, without JIT optimizations applied (like YJIT, ZJIT, MJIT), you can trace how each instruction is executed by reading &lt;code&gt;insns.def&lt;/code&gt;. Let’s look at the &lt;code&gt;once&lt;/code&gt; definition to see what kind of dark magic is being invoked.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* run iseq only once */
DEFINE_INSN
once
(ISEQ iseq, ISE ise)
()
(VALUE val)
{
    val = vm_once_dispatch(ec, iseq, ise);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In essence it’s very simple - it takes the instruction and runs it using &lt;code&gt;vm_once_dispatch&lt;/code&gt;. &lt;code&gt;iseq&lt;/code&gt; is the instruction sequence, and &lt;code&gt;ise&lt;/code&gt; is an “Inline Storage Entry”, which is a place to cache an instruction result. What does &lt;code&gt;vm_once_dispatch&lt;/code&gt; do?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;vm_once_dispatch&lt;/span&gt;(rb_execution_context_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ec, ISEQ iseq, ISE is)
{
    rb_thread_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;th &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_ec_thread_ptr(ec);
    rb_thread_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; RUNNING_THREAD_ONCE_DONE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_thread_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x1&lt;/span&gt;);
	
  again:
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.running_thread &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; RUNNING_THREAD_ONCE_DONE) {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.value;
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.running_thread &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL) {
        VALUE val;
        is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.running_thread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; th;
        val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_ensure(vm_once_exec, (VALUE)iseq, vm_once_clear, (VALUE)is);
        &lt;span style=&#34;color:#75715e&#34;&gt;// ... skipped ...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.running_thread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; RUNNING_THREAD_ONCE_DONE; &lt;span style=&#34;color:#75715e&#34;&gt;/* success */&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; val;
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (is&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;once.running_thread &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; th) {
        &lt;span style=&#34;color:#75715e&#34;&gt;/* recursive once */&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; vm_once_exec((VALUE)iseq);
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
        &lt;span style=&#34;color:#75715e&#34;&gt;/* waiting for finish */&lt;/span&gt;
        RUBY_VM_CHECK_INTS(ec);
        rb_thread_schedule();
        &lt;span style=&#34;color:#66d9ef&#34;&gt;goto&lt;/span&gt; again;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It’s a little daunting at first look, but broken down it’s just some simple if statements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;is-&amp;gt;once.running_thread == RUNNING_THREAD_ONCE_DONE&lt;/code&gt;&lt;br&gt;
If the instruction cache is set to &lt;code&gt;RUNNING_THREAD_ONCE_DONE&lt;/code&gt; (a flag to identify being done vs pointing at a thread), it returns its cached value. Forever.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is-&amp;gt;once.running_thread == NULL&lt;/code&gt;&lt;br&gt;
If there is no running thread, congratulations! Your thread is the winner! You get to decide what value gets cached! Every other thread trying to run this instruction will wait until you have produced a value (by calling &lt;code&gt;vm_once_exec&lt;/code&gt;), then use whatever value you produced. Forever.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is-&amp;gt;once.running_thread == th&lt;/code&gt;&lt;br&gt;
If there is a running thread set, and it’s the current thread, we’re in a recursive &lt;code&gt;once&lt;/code&gt; (terrifying…)&lt;/li&gt;
&lt;li&gt;Otherwise you are a different thread, and you have to wait. You’ll keep checking for a value immediately, or get rescheduled and check it a little later (once some other threads have been given a slice of time)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ve broken down the logic. Now let’s break down the implications.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If you use &lt;code&gt;/o&lt;/code&gt; in your regex, the content of the regex will be evaluated &lt;em&gt;once&lt;/em&gt;, &lt;strong&gt;ever&lt;/strong&gt;. Even if it’s inside of an instance method. Even if it’s inside of a loop with a thousand iterations. It will &lt;em&gt;never&lt;/em&gt; be evaluated again. That’s the content you’ve got for your regex. This is why the code I was reviewing was so confusing - even though it lived in an object, and was only created local to that method - it &lt;strong&gt;implicitly created a constant, immutable value&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we can understand how the original code worked, by evaluating it relative to how the CRuby internals work. The first call we have no value set - past that point we will always use the value literally cached inside of the VM itself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# We haven&#39;t run the code yet, so in our starting state:
#   is-&amp;gt;once.running_thread == NULL
#   m.match?(/#{input}/io)
&amp;gt; Matcher.new([&amp;quot;Ruby!&amp;quot;]).matches_any?(&amp;quot;something else&amp;quot;)
# Now we&#39;ve run the code once, we&#39;re done!
#   is-&amp;gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
#   m.match?(/something else/io)

&amp;gt; Matcher.new([&amp;quot;Ruby!&amp;quot;]).matches_any?(&amp;quot;ruby&amp;quot;)
#   is-&amp;gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
#   m.match?(/something else/io)

&amp;gt; Matcher.new([&amp;quot;something else&amp;quot;]).matches_any?(&amp;quot;Ruby!&amp;quot;)
#   is-&amp;gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
#   m.match?(/something else/io)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;If the code has not been run yet, you’re in the starting state of &lt;code&gt;is-&amp;gt;once.running_thread == NULL&lt;/code&gt;. This means two things. One is clear - the first code to run this gets to determine the value set. Two is a little less clear - it is non-deterministic what value will win!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you’ve read any of &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;my series on Ruby concurrency&lt;/a&gt;, you’ll know I love a good thread non-determinism example. Here we create a method containing an “interpolation mode” regex. Then we create five threads, and call the method from each thread. To introduce some context switching, we sleep for random amounts (this could also be caused by IO, or long-running code, alternatively):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def once_and_for_all(input)
  /#{input}/o
end

5.times.map do |i|
  Thread.new { sleep(rand); p once_and_for_all(i) }
end.map(&amp;amp;:join)

# Run it once, it prints /3/ 5 times
# Run it again, it prints /1/ 5 times
# Run it again...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my first run, I see the regex &lt;code&gt;/3/&lt;/code&gt; printed 5 times. Each run gave me a different result. Run this code several times and you will likely see a different value printed each time. It may repeat at times, but there will be no consistency.&lt;/p&gt;
&lt;p&gt;Quite the behavior! This is pretty close to being a &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs&#34;&gt;Heisenbug&lt;/a&gt;. Non-determinism at its finest.&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;The code has been run already, but now it’s being run again before being set as &lt;code&gt;RUNNING_THREAD_ONCE_DONE&lt;/code&gt;: &lt;code&gt;is-&amp;gt;once.running_thread == th&lt;/code&gt;. This can only happen within the same thread, and it’s there to handle &lt;em&gt;recursion&lt;/em&gt;. It’s hard to imagine using the &lt;code&gt;/o&lt;/code&gt; at all, let alone &lt;em&gt;recursively&lt;/em&gt;. If you were to do that I’d only have one question for you: “Who hurt you?”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But let’s do the damn thing. Here’s a recursive case of the &lt;code&gt;/o&lt;/code&gt; modifier. Heaven help us.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def recursive_regex(n)
  puts &amp;quot;Evaluating regex for n=#{n}&amp;quot;
  if n &amp;gt; 0
    /#{recursive_regex(n - 1)}/o # This calls itself recursively
  else
    &amp;quot;base&amp;quot;
  end
end

recursive_regex(5)
# Evaluating regex for n=5
# Evaluating regex for n=4
# Evaluating regex for n=3
# Evaluating regex for n=2
# Evaluating regex for n=1
# Evaluating regex for n=0

recursive_regex(5)
# Evaluating regex for n=5

# Call it with whatever value you want, it&#39;s never running the method recursively again.
recursive_regex(500)
# Evaluating regex for n=500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I promise it hurt me more to write that than it did for you to read it.&lt;/p&gt;
&lt;p&gt;This example does bring up an interesting point though! Our examples so far have been simple values interpolated into the regex. In this case, we’re calling a method and it is still only being evaluated once. The operation you run in the interpolation is irrelevant - whatever is interpolated will never be run again with the &lt;code&gt;/o&lt;/code&gt; modifier.&lt;/p&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;If none of the other conditions are met, it means the interpolation is being evaluated by a different thread. All we can do here is wait to see what the value is.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the same concept as the threading example, but this is about the threads that lose the race. The most interesting part here is that it means this interpolation is guaranteed to be thread safe in CRuby (at least in the sense that other threads are locked out of it, not that you get a deterministic result). That will actually matter a little later, if you can believe it.&lt;/p&gt;
&lt;h3 id=&#34;why-does-it-exist&#34;&gt;Why does it exist?&lt;/h3&gt;
&lt;p&gt;I didn’t find a clear origin of this modifier. But it’s been around for 20+ years. It’s likely been around almost as long as Ruby itself. Matz, you scoundrel, you.&lt;/p&gt;
&lt;p&gt;Shockingly, every other forum or blog post I found in relation to the &lt;code&gt;/o&lt;/code&gt; modifier exclusively spoke about the performance benefits of it. I didn’t find any warnings at all. But mostly I found it used in a scripting context. In a single, simple script run, &lt;em&gt;maybe&lt;/em&gt; you’re safe. But the downside seems way worse than any potential upside. If you really need a speed boost - just cache the regex yourself?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;interpolated_once &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;first&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;
interpolated_once&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a string&amp;#34;&lt;/span&gt;)
interpolated_once&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a string 2&amp;#34;&lt;/span&gt;)
interpolated_once&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;match?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a string 3&amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#75715e&#34;&gt;# Just as performant as /o?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The earliest reference I could find to the existence of a &lt;code&gt;/o&lt;/code&gt; modifier in regex was in a &lt;a href=&#34;https://www.perlmonks.org/?node_id=256053&#34;&gt;2003 post on a &lt;strong&gt;Perl&lt;/strong&gt; forum&lt;/a&gt;. Based on this, I’m guessing Ruby borrowed it from Perl. Even in 2003, the prevailing wisdom was already clear:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The best heuristic is: Never use &lt;code&gt;/o&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I agree with that 2003 Perl person. You don’t need it. You should not use it. Step away from the modifier, and no one has to get hurt.&lt;/p&gt;
&lt;h3 id=&#34;ill-take-your-once-and-raise-you&#34;&gt;I’ll take your &lt;code&gt;once&lt;/code&gt;, and raise you…&lt;/h3&gt;
&lt;p&gt;We’re all having fun here, right? Should we take this confounding modifier, and muddy things a bit more?&lt;/p&gt;
&lt;p&gt;There &lt;em&gt;is&lt;/em&gt; actually a way to force the &lt;code&gt;/o&lt;/code&gt; modifier to re-evaluate.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;hushed whispers!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;someone in the back of the room faints!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I stumbled upon it because I was testing this code in a Rails console. I found that every time I called &lt;code&gt;reload!&lt;/code&gt;, I was able to test my method from scratch again. Why would that be?&lt;/p&gt;
&lt;p&gt;It’s because when you &lt;code&gt;reload!&lt;/code&gt;, all of your code is re-evaluated by Ruby. And when it gets re-evaluated, you get new bytecode, and a new cache! Take this for instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def one_time(input)
  /#{input}/o
end

p one_time(&amp;quot;hi there!&amp;quot;)

def one_time(input)
  /#{input}/o
end

p one_time(&amp;quot;how are you?&amp;quot;)
# /hi there!/
# /how are you?/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though we used the &lt;code&gt;/o&lt;/code&gt; modifier, we were able to re-evaluate it. That’s because we overwrote our &lt;code&gt;one_time&lt;/code&gt; implementation, which gave us a new &lt;code&gt;once&lt;/code&gt; cache.&lt;/p&gt;
&lt;p&gt;This matters because it means you can’t even &lt;em&gt;truly&lt;/em&gt; guarantee your &lt;code&gt;/o&lt;/code&gt; regex will only be run once. If a piece of monkey patch code gets loaded and overwrites your method, you will get even &lt;em&gt;more&lt;/em&gt; non-deterministic behavior.&lt;/p&gt;
&lt;h3 id=&#34;the-inmates-are-running-the-asylum&#34;&gt;The inmates are running the asylum&lt;/h3&gt;
&lt;p&gt;In a moment of pure serendipity, the day after I started writing this post I happened to read Jared Norman’s &lt;a href=&#34;https://jardo.dev/code-reloading-for-rack-apps&#34;&gt;Code Reloading for Rack Apps&lt;/a&gt;. The article teaches you how to build the same kind of code reloading Rails has, but in a standalone Rack application. You should give it a read.&lt;/p&gt;
&lt;p&gt;In it, he creates a class called &lt;code&gt;Once&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This class let’s you create an object that encapsulates a bit of code and only ever lets it run once, even if it’s called across multiple threads.&lt;/p&gt;
&lt;p&gt;-Jared Norman&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Once&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;block)
    @block &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; block
    @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;

  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;call&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;unless&lt;/span&gt; @mutex

      &lt;span style=&#34;color:#75715e&#34;&gt;# Ignore the \, it&amp;#39;s for a markdown issue...&lt;/span&gt;
      \@block&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call

      @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
o &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Once&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I should only happen once&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map { &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { o&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call } }&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:join&lt;/span&gt;)
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; I should only happen once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Any thread attempting to run this code will block on the &lt;code&gt;@mutex.synchronize&lt;/code&gt;. It runs &lt;code&gt;@block.call&lt;/code&gt;, then at the end of the &lt;code&gt;synchronize&lt;/code&gt; block, the &lt;code&gt;@mutex&lt;/code&gt; is set to &lt;code&gt;nil&lt;/code&gt;. This means that after the first thread, every other thread either exits early because &lt;code&gt;@mutex&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;, or never runs the &lt;code&gt;synchronize&lt;/code&gt; block at all because of the safe-nav. Makes sense!&lt;/p&gt;
&lt;p&gt;As if I was destined to read this by some kind of trickster regex god, what did I see at the end of the article? A reimplementation of his &lt;code&gt;Once&lt;/code&gt; class using the &lt;code&gt;/o&lt;/code&gt; modifier!&lt;/p&gt;
&lt;p&gt;A veritable devil on Jared’s shoulder, &lt;a href=&#34;https://www.johnhawthorn.com/&#34;&gt;John Hawthorn&lt;/a&gt; gave him the idea. It would make sense that John, a Ruby internals wizard, would suggest such a thing. Then Jared, author of &lt;a href=&#34;https://jardo.dev/advent-of-criminally-bad-ruby-code&#34;&gt;Advent of Criminally Bad Ruby Code&lt;/a&gt;, would actually decide to include it.&lt;/p&gt;
&lt;p&gt;Here is their ingenious abomination.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Once
  def initialize(&amp;amp;block)
    @block = block
  end

  def call
    /#{@block.call}/o # THE HORROR
  end
end

o = Once.new do
  puts &amp;quot;I should only happen once&amp;quot;
end

100.times.map { Thread.new { o.call } }.each(&amp;amp;:join)
# =&amp;gt; I should only happen once
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;/o&lt;/code&gt;, the code examples still behave correctly and only run once despite the many threads attempting to run it.&lt;/p&gt;
&lt;p&gt;It does, however, have a fatal flaw in comparison to the original code. Can you guess it?&lt;/p&gt;
&lt;p&gt;Let’s demonstrate by creating multiple instances:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;o1 = Once.new { puts &amp;quot;I should only happen once 1&amp;quot; }
o2 = Once.new { puts &amp;quot;I should only happen once 2&amp;quot; }
o3 = Once.new { puts &amp;quot;I should only happen once 3&amp;quot; }

100.times.map { Thread.new { o1.call } }.each(&amp;amp;:join)
100.times.map { Thread.new { o2.call } }.each(&amp;amp;:join)
100.times.map { Thread.new { o3.call } }.each(&amp;amp;:join)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the original, mutex based implementation, we’d see the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# =&amp;gt; I should only happen once 1
# =&amp;gt; I should only happen once 2
# =&amp;gt; I should only happen once 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;/o&lt;/code&gt; based implementation, we see this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# =&amp;gt; I should only happen once 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of the instruction-level caching, we can only have &lt;em&gt;one&lt;/em&gt; result from &lt;code&gt;Once#call&lt;/code&gt;, &lt;strong&gt;ever&lt;/strong&gt;. A class that literally can only be run reliably a &lt;em&gt;single&lt;/em&gt; time. “Once”, indeed.&lt;/p&gt;
&lt;p&gt;It’s probably more efficient than the mutex approach and &lt;strong&gt;maybe&lt;/strong&gt; in some bizarro world where you truly needed a piece of code to be thread safe and lazy evaluated and as efficient up front as possible and only ever run once… maybe…&lt;/p&gt;
&lt;p&gt;No… I cannot condone it - &lt;strong&gt;&lt;em&gt;don’t do it!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Side note: Jared also has an excellent podcast called &lt;a href=&#34;https://shows.acast.com/dead-code&#34;&gt;Dead Code&lt;/a&gt;, with loads of episodes with fantastic guests. I went on to &lt;a href=&#34;https://shows.acast.com/dead-code/episodes/violent-sleep-of-concurrency&#34;&gt;speak about concurrency - give it a listen!&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;this-is-the-end&#34;&gt;This is the &lt;code&gt;END&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Earlier, I said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is &lt;code&gt;once&lt;/code&gt;? It can’t exist almost solely for the purposes of this extremely strange regex modifier, can it? Surely this modifier is not built into the very &lt;em&gt;bones&lt;/em&gt; of Ruby?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And the truth is, &lt;code&gt;/o&lt;/code&gt; is not the &lt;em&gt;only&lt;/em&gt; reason the &lt;code&gt;once&lt;/code&gt; instruction exists. It took a little digging, but I found another piece of Ruby code that uses &lt;code&gt;once&lt;/code&gt; - the &lt;a href=&#34;https://docs.ruby-lang.org/en/3.4/syntax/miscellaneous_rdoc.html#label-BEGIN+and+END&#34;&gt;&lt;code&gt;END&lt;/code&gt; language sytax&lt;/a&gt;. I was surprised to find that &lt;code&gt;END&lt;/code&gt; existed! I had never heard of it or used it before.&lt;/p&gt;
&lt;p&gt;Not to be confused with the &lt;code&gt;end&lt;/code&gt; keyword that closes out blocks, &lt;code&gt;END&lt;/code&gt; defines a block that is run at the end of the Ruby program. For instance:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;END&lt;/span&gt; { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;do this last please&amp;#34;&lt;/span&gt; }
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; do this last please&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that if you run this code in &lt;code&gt;irb&lt;/code&gt;, the &lt;code&gt;END&lt;/code&gt; block will not run until you exit. &lt;code&gt;irb&lt;/code&gt; is just a Ruby program, and it doesn’t &lt;code&gt;END&lt;/code&gt; until &lt;code&gt;irb&lt;/code&gt; is exited.&lt;/p&gt;
&lt;p&gt;If you disassemble this code, we’ll see a familiar instruction:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;puts &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;InstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;compile(&lt;span style=&#34;color:#66d9ef&#34;&gt;DATA&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disassemble
	
&lt;span style=&#34;color:#75715e&#34;&gt;__END__
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;END { puts &amp;#34;do this last please&amp;#34; }
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;puts &amp;#34;1&amp;#34;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;puts &amp;#34;2&amp;#34;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;	
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# == disasm: #&amp;lt;ISeq:&amp;lt;compiled&amp;gt;@&amp;lt;compiled&amp;gt;:1 (1,0)-(4,8)&amp;gt;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 0000 once          block in &amp;lt;compiled&amp;gt;, &amp;lt;is:0&amp;gt;(   2)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the &lt;code&gt;END&lt;/code&gt; block, but not the &lt;code&gt;end&lt;/code&gt; of a block, uses the &lt;code&gt;once&lt;/code&gt; instruction, an instruction that is used twice in Ruby. An appropriately confusing close to an article about a fascinating feature I think you should never use - the &lt;code&gt;/o&lt;/code&gt; modifier.&lt;/p&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2025/2ca9b9c306.png)

&gt; Your code using the `/o` modifier
&gt; 
&gt; Source: [wikipedia](https://upload.wikimedia.org/wikipedia/commons/1/1c/Hindenburg_disaster.jpg)

Hi there! Do you like Regex? Do you like performance? Do you like creating confounding bugs for yourself rooted in the mechanics of the Ruby VM itself?

If you said yes to all of the above, have I got a feature for you!

But first, let’s start with a story.

### The cliffs of insanity

I was recently reviewing some code, and part of the functionality was about matching. A class took an array of strings, and you could call a method to see if an input matched part of any of the strings. Stripped down, it was effectively the following code:

	class Matcher
	  def initialize(matchables)
	    @matchables = matchables
	  end
	
	  def matches_any?(input)
	    @matchables.any? { |m| m.match?(/#{input}/io) }
	  end
	end

&gt; I _know_ there are some of you reading this code and thinking “does this really need a regex?”, “couldn’t it just use `include?` and some downcasing?”, “does this even need to exist?”, etc, etc. I see you, I hear you, I’d probably think the same, and I _promise_ you the specifics of this method aren’t that important.

Functionally, the code looked _ok_ to me. I knew what `/i` was (a regex in [case-insensitive mode](https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Case-Insensitive+Mode)), but I didn’t recognize `/o`. It didn’t seem critically important to lookup yet. Tests were not exhaustive but were green, and so I went to run the code in a console:

```ruby
Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;ruby&#34;)
=&gt; true
Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;something else&#34;)
=&gt; true
Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;javascript&#34;)
=&gt; true
```
Well, that seemed broken. Assuming it was a bug, I looked at the code to see what was wrong. But nothing stuck out. The code _seemed_ ok, and simple:

	@matchables.any? |m| m.match?(/#{input/io) }

So I deconstructed the code and ran it directly, outside the class:

```ruby
[&#34;Ruby!&#34;].any? { |m| m.match?(/ruby/io) }
=&gt; true
[&#34;Ruby!&#34;].any? { |m| m.match?(/something else/io) }
=&gt; false
```
Even weirder. Run directly, it ran as expected. What if I started a new console, and ran the original class in a different order?

```ruby
Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;something else&#34;)
=&gt; false
Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;ruby&#34;)
=&gt; false
Matcher.new([&#34;something else&#34;]).matches_any?(&#34;Ruby!&#34;)
=&gt; true
```
Well, that was interesting. It seems like whatever value I sent to `matches_any?` was cached for every run after that point, even for _brand new objects_. 

I looked through the class again. There was no weird memoization. No class-level variables. No thread locals. I was instantiating the class each time I ran `matches_any?`. I was out of ideas for the predictable things that cause unpredictable results. What else was there?

### /o the humanity!

With nothing else to investigate, I finally looked up the docs for what the `/o` regex modifier does. `/o` is referred to as “Interpolation mode”, which sounded pretty harmless. The Ruby docs have a succinct section on the expected behavior: [https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Interpolation+Mode](https://ruby-doc.org/3.4.1/Regexp.html#class-Regexp-label-Interpolation+Mode)

&gt; Modifier o means that the first time a literal regexp with interpolations is encountered, the generated Regexp object is saved and used for all future evaluations of that literal regexp

Just reading that, I still wasn’t sure I’d expect what I was seeing. It almost sounded like internally Ruby would cache _each_ different interpolation that comes through. As if it would maybe reuse an internal regex if the same string value was interpolated. They provide a code example that makes it a little clearer:

	def letters; sleep 5; /[A-Z][a-z]/; end
	words = %w[abc def xyz]
	start = Time.now
	words.each {|word| word.match(/\A[#{letters}]+\z/) }
	Time.now - start # =&gt; 15.0174892
	
	start = Time.now
	words.each {|word| word.match(/\A[#{letters}]+\z/o) }
	Time.now - start # =&gt; 5.0010866

The `letters` method in the first example is called three times. One time for each iteration, as expected, taking 15 seconds total.

In the second example, the code iterates three times, but the `letters` method is called _once_, taking 5 seconds total.

Knowing that, I went back to the original code with new eyes:

```ruby
class Matcher
  def initialize(matchables)
    @matchables = matchables
  end
	
  def matches_any?(input)
    @matchables.any? { |m|
      m.match?(
        /#{input}/io # This is run once, _ever_
      )
    }
  end
end
```
What that meant for me is that the regular expression inside of `matches_any?` was interpolating the _first value it **ever** receives_. Past that point the regex _never interpolated another value_. 

As it turned out, the developer had read about it as a potential regex optimization. It seemed harmless enough, so they added it. Now that I knew why I was hitting the issue, I removed the `/o` and everything started working properly. 

But it still was not clear to me _how_ this was possible. What in the world is Ruby doing internally? Let’s figure it out together.

### Inside the VM

Sometimes the only way to understand a behavior in Ruby is to drop a bit lower. Let’s disassemble the code into Ruby VM byte code, to see if it gives us any clues. I&#39;ll use the `DATA` feature to be able to put it into a script directly (you can find more about [that syntax here](https://ruby-doc.org/3.4.1/Object.html#DATA)).

```ruby
puts RubyVM::InstructionSequence.compile(DATA.read).disassemble
	
__END__
class Matcher
  def initialize(matchables)
    @matchables = matchables
  end
	
  def matches_any?(input)
    @matchables.any? { |m| m.match?(/#{input}/io) }
  end
end
```
There are a bunch of other instructions you’ll see if you run that code, but we’ll focus on the instructions specific to the block in the `matches_any?` method:

	# { |m| m.match?(/#{input/io) }
	
	== disasm: #&lt;ISeq:block in matches_any?@&lt;compiled&gt;:7 (7,21)-(7,51)&gt;
	local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
	[ 1] m@0&lt;AmbiguousArg&gt;
	0000 getlocal_WC_0                          m@0                       (   7)[LiBc]
	0002 once                                   block (2 levels) in matches_any?, &lt;is:0&gt;
	0005 opt_send_without_block                 &lt;calldata!mid:match?, argc:1, ARGS_SIMPLE&gt;
	0007 leave                                  [Br]
	

It describes:

- Getting local variable `m`
- Running the… `once` instruction?
- Calling `m` with `opt_send_without_block` with a method id (`mid`) of `match?`
- Then `leave`ing. 

All of these instructions are _very_ common in the Ruby VM. All except one - the `once` instruction. I’ve never heard of that!

What is `once`? It can’t exist almost solely for the purposes of this extremely strange regex modifier, can it? Surely this modifier is not built into the very _bones_ of Ruby?

### `once` upon a time

If you are ever curious to know how Ruby VM instructions get interpreted in CRuby, there is a central file to all of it called `insns.def`. It contains all of the available YARV (Yet Another Ruby VM) instructions in a C-esque format which is compiled into an actual C file as part of building the language.

In normal program execution, without JIT optimizations applied (like YJIT, ZJIT, MJIT), you can trace how each instruction is executed by reading `insns.def`. Let’s look at the `once` definition to see what kind of dark magic is being invoked.

	/* run iseq only once */
	DEFINE_INSN
	once
	(ISEQ iseq, ISE ise)
	()
	(VALUE val)
	{
	    val = vm_once_dispatch(ec, iseq, ise);
	}

In essence it’s very simple - it takes the instruction and runs it using `vm_once_dispatch`. `iseq` is the instruction sequence, and `ise` is an “Inline Storage Entry”, which is a place to cache an instruction result. What does `vm_once_dispatch` do?

```c
static VALUE
vm_once_dispatch(rb_execution_context_t *ec, ISEQ iseq, ISE is)
{
    rb_thread_t *th = rb_ec_thread_ptr(ec);
    rb_thread_t *const RUNNING_THREAD_ONCE_DONE = (rb_thread_t *)(0x1);
	
  again:
    if (is-&gt;once.running_thread == RUNNING_THREAD_ONCE_DONE) {
        return is-&gt;once.value;
    }
    else if (is-&gt;once.running_thread == NULL) {
        VALUE val;
        is-&gt;once.running_thread = th;
        val = rb_ensure(vm_once_exec, (VALUE)iseq, vm_once_clear, (VALUE)is);
        // ... skipped ...
        is-&gt;once.running_thread = RUNNING_THREAD_ONCE_DONE; /* success */
        return val;
    }
    else if (is-&gt;once.running_thread == th) {
        /* recursive once */
        return vm_once_exec((VALUE)iseq);
    }
    else {
        /* waiting for finish */
        RUBY_VM_CHECK_INTS(ec);
        rb_thread_schedule();
        goto again;
    }
}
```
It’s a little daunting at first look, but broken down it’s just some simple if statements:

1. `is-&gt;once.running_thread == RUNNING_THREAD_ONCE_DONE`  
	If the instruction cache is set to `RUNNING_THREAD_ONCE_DONE` (a flag to identify being done vs pointing at a thread), it returns its cached value. Forever. 
2. `is-&gt;once.running_thread == NULL`  
	If there is no running thread, congratulations! Your thread is the winner! You get to decide what value gets cached! Every other thread trying to run this instruction will wait until you have produced a value (by calling `vm_once_exec`), then use whatever value you produced. Forever.
3. `is-&gt;once.running_thread == th`  
	If there is a running thread set, and it’s the current thread, we’re in a recursive `once` (terrifying…)
4. Otherwise you are a different thread, and you have to wait. You’ll keep checking for a value immediately, or get rescheduled and check it a little later (once some other threads have been given a slice of time)

We’ve broken down the logic. Now let’s break down the implications.

1. If you use `/o` in your regex, the content of the regex will be evaluated _once_, **ever**. Even if it’s inside of an instance method. Even if it’s inside of a loop with a thousand iterations. It will _never_ be evaluated again. That’s the content you’ve got for your regex. This is why the code I was reviewing was so confusing - even though it lived in an object, and was only created local to that method - it **implicitly created a constant, immutable value**. 

Now we can understand how the original code worked, by evaluating it relative to how the CRuby internals work. The first call we have no value set - past that point we will always use the value literally cached inside of the VM itself:

	# We haven&#39;t run the code yet, so in our starting state:
	#   is-&gt;once.running_thread == NULL
	#   m.match?(/#{input}/io)
	&gt; Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;something else&#34;)
	# Now we&#39;ve run the code once, we&#39;re done!
	#   is-&gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
	#   m.match?(/something else/io)
	
	&gt; Matcher.new([&#34;Ruby!&#34;]).matches_any?(&#34;ruby&#34;)
	#   is-&gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
	#   m.match?(/something else/io)
	
	&gt; Matcher.new([&#34;something else&#34;]).matches_any?(&#34;Ruby!&#34;)
	#   is-&gt;once.running_thread == RUNNING_THREAD_ONCE_DONE
	#   m.match?(/something else/io)

2. If the code has not been run yet, you’re in the starting state of `is-&gt;once.running_thread == NULL`. This means two things. One is clear - the first code to run this gets to determine the value set. Two is a little less clear - it is non-deterministic what value will win!

If you’ve read any of [my series on Ruby concurrency](https://jpcamara.com/2024/06/04/your-ruby-programs.html), you’ll know I love a good thread non-determinism example. Here we create a method containing an “interpolation mode” regex. Then we create five threads, and call the method from each thread. To introduce some context switching, we sleep for random amounts (this could also be caused by IO, or long-running code, alternatively):

	def once_and_for_all(input)
	  /#{input}/o
	end
	
	5.times.map do |i|
	  Thread.new { sleep(rand); p once_and_for_all(i) }
	end.map(&amp;:join)
	
	# Run it once, it prints /3/ 5 times
	# Run it again, it prints /1/ 5 times
	# Run it again...
	

In my first run, I see the regex `/3/` printed 5 times. Each run gave me a different result. Run this code several times and you will likely see a different value printed each time. It may repeat at times, but there will be no consistency.

Quite the behavior! This is pretty close to being a [Heisenbug](https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs). Non-determinism at its finest.

3. The code has been run already, but now it’s being run again before being set as `RUNNING_THREAD_ONCE_DONE`: `is-&gt;once.running_thread == th`. This can only happen within the same thread, and it’s there to handle _recursion_. It’s hard to imagine using the `/o` at all, let alone _recursively_. If you were to do that I’d only have one question for you: “Who hurt you?”

But let’s do the damn thing. Here’s a recursive case of the `/o` modifier. Heaven help us.

	def recursive_regex(n)
	  puts &#34;Evaluating regex for n=#{n}&#34;
	  if n &gt; 0
	    /#{recursive_regex(n - 1)}/o # This calls itself recursively
	  else
	    &#34;base&#34;
	  end
	end
	
	recursive_regex(5)
	# Evaluating regex for n=5
	# Evaluating regex for n=4
	# Evaluating regex for n=3
	# Evaluating regex for n=2
	# Evaluating regex for n=1
	# Evaluating regex for n=0
	
	recursive_regex(5)
	# Evaluating regex for n=5
	
	# Call it with whatever value you want, it&#39;s never running the method recursively again.
	recursive_regex(500)
	# Evaluating regex for n=500

I promise it hurt me more to write that than it did for you to read it.

This example does bring up an interesting point though! Our examples so far have been simple values interpolated into the regex. In this case, we’re calling a method and it is still only being evaluated once. The operation you run in the interpolation is irrelevant - whatever is interpolated will never be run again with the `/o` modifier.

4. If none of the other conditions are met, it means the interpolation is being evaluated by a different thread. All we can do here is wait to see what the value is.

This is the same concept as the threading example, but this is about the threads that lose the race. The most interesting part here is that it means this interpolation is guaranteed to be thread safe in CRuby (at least in the sense that other threads are locked out of it, not that you get a deterministic result). That will actually matter a little later, if you can believe it.

### Why does it exist?

I didn’t find a clear origin of this modifier. But it’s been around for 20+ years. It’s likely been around almost as long as Ruby itself. Matz, you scoundrel, you.

Shockingly, every other forum or blog post I found in relation to the `/o` modifier exclusively spoke about the performance benefits of it. I didn’t find any warnings at all. But mostly I found it used in a scripting context. In a single, simple script run, _maybe_ you’re safe. But the downside seems way worse than any potential upside. If you really need a speed boost - just cache the regex yourself?

```ruby
interpolated_once = /#{ARGV.first}/
interpolated_once.match?(&#34;a string&#34;)
interpolated_once.match?(&#34;a string 2&#34;)
interpolated_once.match?(&#34;a string 3&#34;)
# Just as performant as /o?
```
The earliest reference I could find to the existence of a `/o` modifier in regex was in a [2003 post on a **Perl** forum](https://www.perlmonks.org/?node_id=256053). Based on this, I’m guessing Ruby borrowed it from Perl. Even in 2003, the prevailing wisdom was already clear:

&gt; The best heuristic is: Never use `/o`

I agree with that 2003 Perl person. You don’t need it. You should not use it. Step away from the modifier, and no one has to get hurt.

### I’ll take your `once`, and raise you…
We’re all having fun here, right? Should we take this confounding modifier, and muddy things a bit more?

There _is_ actually a way to force the `/o` modifier to re-evaluate. 

*hushed whispers!*

*someone in the back of the room faints!*

I stumbled upon it because I was testing this code in a Rails console. I found that every time I called `reload!`, I was able to test my method from scratch again. Why would that be?

It’s because when you `reload!`, all of your code is re-evaluated by Ruby. And when it gets re-evaluated, you get new bytecode, and a new cache! Take this for instance:

	def one_time(input)
	  /#{input}/o
	end
	
	p one_time(&#34;hi there!&#34;)
	
	def one_time(input)
	  /#{input}/o
	end
	
	p one_time(&#34;how are you?&#34;)
	# /hi there!/
	# /how are you?/

Even though we used the `/o` modifier, we were able to re-evaluate it. That’s because we overwrote our `one_time` implementation, which gave us a new `once` cache.

This matters because it means you can’t even _truly_ guarantee your `/o` regex will only be run once. If a piece of monkey patch code gets loaded and overwrites your method, you will get even _more_ non-deterministic behavior. 

### The inmates are running the asylum

In a moment of pure serendipity, the day after I started writing this post I happened to read Jared Norman’s [Code Reloading for Rack Apps](https://jardo.dev/code-reloading-for-rack-apps). The article teaches you how to build the same kind of code reloading Rails has, but in a standalone Rack application. You should give it a read.

In it, he creates a class called `Once`.

&gt; This class let’s you create an object that encapsulates a bit of code and only ever lets it run once, even if it’s called across multiple threads.
&gt; 
&gt; -Jared Norman

```ruby
class Once
  def initialize(&amp;block)
    @block = block
    @mutex = Mutex.new
  end

  def call
    @mutex&amp;.synchronize do
      return unless @mutex

      # Ignore the \, it&#39;s for a markdown issue...
      \@block.call

      @mutex = nil
    end
  end
end
	
o = Once.new do
  puts &#34;I should only happen once&#34;
end
	
100.times.map { Thread.new { o.call } }.each(&amp;:join)
# =&gt; I should only happen once
```
Any thread attempting to run this code will block on the `@mutex.synchronize`. It runs `@block.call`, then at the end of the `synchronize` block, the `@mutex` is set to `nil`. This means that after the first thread, every other thread either exits early because `@mutex` is `nil`, or never runs the `synchronize` block at all because of the safe-nav. Makes sense!

As if I was destined to read this by some kind of trickster regex god, what did I see at the end of the article? A reimplementation of his `Once` class using the `/o` modifier!

A veritable devil on Jared’s shoulder, [John Hawthorn](https://www.johnhawthorn.com/) gave him the idea. It would make sense that John, a Ruby internals wizard, would suggest such a thing. Then Jared, author of [Advent of Criminally Bad Ruby Code](https://jardo.dev/advent-of-criminally-bad-ruby-code), would actually decide to include it.

Here is their ingenious abomination.

	class Once
	  def initialize(&amp;block)
	    @block = block
	  end
	
	  def call
	    /#{@block.call}/o # THE HORROR
	  end
	end
	
	o = Once.new do
	  puts &#34;I should only happen once&#34;
	end
	
	100.times.map { Thread.new { o.call } }.each(&amp;:join)
	# =&gt; I should only happen once

Using `/o`, the code examples still behave correctly and only run once despite the many threads attempting to run it.

It does, however, have a fatal flaw in comparison to the original code. Can you guess it?

Let’s demonstrate by creating multiple instances:

	o1 = Once.new { puts &#34;I should only happen once 1&#34; }
	o2 = Once.new { puts &#34;I should only happen once 2&#34; }
	o3 = Once.new { puts &#34;I should only happen once 3&#34; }
	
	100.times.map { Thread.new { o1.call } }.each(&amp;:join)
	100.times.map { Thread.new { o2.call } }.each(&amp;:join)
	100.times.map { Thread.new { o3.call } }.each(&amp;:join)

In the original, mutex based implementation, we’d see the following:

	# =&gt; I should only happen once 1
	# =&gt; I should only happen once 2
	# =&gt; I should only happen once 3

In the `/o` based implementation, we see this:

	# =&gt; I should only happen once 1

Because of the instruction-level caching, we can only have _one_ result from `Once#call`, **ever**. A class that literally can only be run reliably a _single_ time. “Once”, indeed.

It’s probably more efficient than the mutex approach and **maybe** in some bizarro world where you truly needed a piece of code to be thread safe and lazy evaluated and as efficient up front as possible and only ever run once… maybe…

No… I cannot condone it - **_don’t do it!_**

Side note: Jared also has an excellent podcast called [Dead Code](https://shows.acast.com/dead-code), with loads of episodes with fantastic guests. I went on to [speak about concurrency - give it a listen!](https://shows.acast.com/dead-code/episodes/violent-sleep-of-concurrency)

### This is the `END`

Earlier, I said:

&gt; What is `once`? It can’t exist almost solely for the purposes of this extremely strange regex modifier, can it? Surely this modifier is not built into the very _bones_ of Ruby?

And the truth is, `/o` is not the _only_ reason the `once` instruction exists. It took a little digging, but I found another piece of Ruby code that uses `once` - the [`END` language sytax](https://docs.ruby-lang.org/en/3.4/syntax/miscellaneous_rdoc.html#label-BEGIN+and+END). I was surprised to find that `END` existed! I had never heard of it or used it before.

Not to be confused with the `end` keyword that closes out blocks, `END` defines a block that is run at the end of the Ruby program. For instance:

```ruby
END { puts &#34;do this last please&#34; }
puts &#34;1&#34;
puts &#34;2&#34;
	
# =&gt; 1
# =&gt; 2
# =&gt; do this last please
```
Note that if you run this code in `irb`, the `END` block will not run until you exit. `irb` is just a Ruby program, and it doesn’t `END` until `irb` is exited.

If you disassemble this code, we’ll see a familiar instruction:

```ruby
puts RubyVM::InstructionSequence.compile(DATA.read).disassemble
	
__END__
END { puts &#34;do this last please&#34; }
puts &#34;1&#34;
puts &#34;2&#34;
	
# == disasm: #&lt;ISeq:&lt;compiled&gt;@&lt;compiled&gt;:1 (1,0)-(4,8)&gt;
# 0000 once          block in &lt;compiled&gt;, &lt;is:0&gt;(   2)
```
So the `END` block, but not the `end` of a block, uses the `once` instruction, an instruction that is used twice in Ruby. An appropriately confusing close to an article about a fascinating feature I think you should never use - the `/o` modifier.
</source:markdown>
    </item>
    
    <item>
      <title>A silly optimization: adding opt_respond_to to the Ruby VM, part 6</title>
      <link>https://jpcamara.com/2025/01/04/a-silly-optimization-adding-optrespondto.html</link>
      <pubDate>Mon, 06 Jan 2025 23:34:49 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2025/01/04/a-silly-optimization-adding-optrespondto.html</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/12/30/defining-an-instruction-adding-optrespondto.html&#34;&gt;part 5&lt;/a&gt;, we finally got our new instruction defined and outputting as part of our bytecode. if you didn’t run it yourself, you just had to trust me that it really did run.&lt;/p&gt;
&lt;p&gt;But, I just dropped most of the implementation code in without explaining it. Let’s start off by walking through the basic version, then start planning for the true optimization.&lt;/p&gt;
&lt;h3 id=&#34;the-progress-so-far&#34;&gt;The progress so far&lt;/h3&gt;
&lt;p&gt;Here’s our sample Ruby program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First, we’ll disassemble the code using &lt;code&gt;make run&lt;/code&gt;, and run it using our C changes (you can pull the &lt;a href=&#34;https://github.com/ruby/ruby/compare/master...jpcamara:ruby:opt-respond-to&#34;&gt;work in progress here&lt;/a&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;RUNOPT0=--dump=insns make run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This gives us a new set of instructions. Most of it is the same as Ruby master, but &lt;code&gt;opt_send_without_block&lt;/code&gt; is changed to &lt;code&gt;opt_respond_to&lt;/code&gt;. The &lt;code&gt;calldata&lt;/code&gt; containing &lt;code&gt;respond_to?&lt;/code&gt; is still there, and I &lt;em&gt;think&lt;/em&gt; it’ll stay even once we finish the whole implementation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-x86&#34; data-lang=&#34;x86&#34;&gt;# == disasm: #&amp;lt;ISeq:&amp;lt;main&amp;gt;./test.rb:1 (1,0)-(1,76)&amp;gt;
0000 getglobal                :$stdout                  (   1)[Li]
0002 putobject                :write
# our new instruction!
0004 opt_respond_to           &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
0006 branchunless             14
0008 putself
0009 putchilledstring         &amp;quot;Did you know you can write to $stdout?&amp;quot;
0011 opt_send_without_block   &amp;lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&amp;gt;
0013 leave
0014 putnil
0015 leave
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our current implementation is mostly just a pass through to the normal &lt;code&gt;respond_to?&lt;/code&gt; method, with some debug information printed. Running it without the &lt;code&gt;dump=insns&lt;/code&gt; option, this is the output we get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&amp;gt; make run

symbol:File
Did you know you can write to $stdout?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;File&lt;/code&gt; is the type of the receiver, &lt;code&gt;$stdout&lt;/code&gt;, and &lt;code&gt;symbol&lt;/code&gt; is the type of the method argument, &lt;code&gt;:write&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 In previous posts, we used &lt;code&gt;make runruby&lt;/code&gt; and &lt;code&gt;make lldb-ruby&lt;/code&gt;/&lt;code&gt;make gdb-ruby&lt;/code&gt;. Based on feedback from Ruby maintainers in the know (like byroot), it seems like &lt;code&gt;make run&lt;/code&gt; and &lt;code&gt;make lldb&lt;/code&gt;/&lt;code&gt;make gdb&lt;/code&gt; are the better options in 99% of cases. These commands use “miniruby”, which is all the Ruby syntax without loading stdlib and gems, so it should run faster. If you &lt;em&gt;do&lt;/em&gt; need the stdlib and standard gems, you’ll want to continue using &lt;code&gt;make runruby&lt;/code&gt; and friends&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;breaking-down-the-changes&#34;&gt;Breaking down the changes&lt;/h3&gt;
&lt;p&gt;The last post was running pretty long, so I dumped all the code at the end without explanation. Let&amp;rsquo;s break each section down, starting with our &lt;code&gt;insns.def&lt;/code&gt; change to the virtual machine DSL:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//insns.def
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;DEFINE_INSN
&lt;span style=&#34;color:#a6e22e&#34;&gt;opt_respond_to&lt;/span&gt;
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vm_opt_respond_to(recv, mid);
    CALL_SIMPLE_METHOD();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We have some context for how a virtual machine instruction is defined from the previous post, so let’s break this down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;opt_respond_to&lt;/code&gt; is the name of the instruction&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(CALL_DATA cd)&lt;/code&gt; is the one “operand”, the call data of the method. I don’t think we’ll need this for our optimized version, but I think if we use a fallback it would still be required&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(VALUE recv, VALUE mid)&lt;/code&gt; are the values this instruction is expecting to be popped off the stack so they can be used in the call. In our sample program instructions this should correspond to &lt;code&gt;getglobal :$stdout&lt;/code&gt; and &lt;code&gt;putobject :write&lt;/code&gt;. &lt;code&gt;$stdout&lt;/code&gt; is &lt;code&gt;recv&lt;/code&gt;, or the “receiver”. &lt;code&gt;:write&lt;/code&gt; is &lt;code&gt;mid&lt;/code&gt;, or the “method id”&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(VALUE val)&lt;/code&gt; is the return value. Whatever gets set to &lt;code&gt;val&lt;/code&gt; gets pushed onto the stack at the end of the instruction. The next instruction in our example is &lt;code&gt;branchunless&lt;/code&gt;, which pops our &lt;code&gt;val&lt;/code&gt; off the stack and tests it&lt;/li&gt;
&lt;li&gt;Next is the body of the instruction:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;val = vm_opt_respond_to(recv, mid);&lt;/code&gt; here I followed the convention of other instructions which need some custom logic - they put their code inside of a &lt;code&gt;vm_&lt;/code&gt; prefixed function named after their instruction, and define it in &lt;code&gt;vm_insnhelper.c&lt;/code&gt;. My function takes the receiver and the method id, and we’ll dive into that in a bit&lt;/li&gt;
&lt;li&gt;I think &lt;code&gt;CALL_SIMPLE_METHOD();&lt;/code&gt; will use the &lt;code&gt;calldata&lt;/code&gt; to call the original method. Normally you would check the return value of the &lt;code&gt;vm_&lt;/code&gt; function to determine whether you want to pass through to the original implementation. In my case, my function is just printing some debug information so I let it always call the original&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve dug into most of the pattern matching logic in &lt;code&gt;compile.c&lt;/code&gt; in previous posts, so I’ll skip that part and focus on the instruction override:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// compile.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ci &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_respond_to);
iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; compile_data_calloc2(
  iseq, 
  iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size, 
  &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(VALUE)
);
iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (VALUE)ci;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once it&amp;rsquo;s found an instruction that matches a &lt;code&gt;send&lt;/code&gt; to &lt;code&gt;respond_to?&lt;/code&gt;, we override the current information. First we set &lt;code&gt;insn_id&lt;/code&gt; to &lt;code&gt;BIN(opt_respond_to)&lt;/code&gt;, which we know expands to the enum value &lt;code&gt;YARVINSN_opt_respond_to&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rest seems… redundant? It already had &lt;code&gt;ci&lt;/code&gt; at the first operand position, it was already an &lt;code&gt;operand_size&lt;/code&gt; of 1. It’s possible I don’t need to recompile this, but I’ll need some guidance around that. It’s probably not harmful, but possibly unnecessary.&lt;/p&gt;
&lt;p&gt;Last we’ve got our &lt;code&gt;vm_opt_respond_to&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// vm_insnhelper.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;vm_opt_respond_to&lt;/span&gt;(VALUE recv, VALUE mid)
{
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (SYMBOL_P(mid)) {
    printf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;symbol:&amp;#34;&lt;/span&gt;);
  } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (STRING_P(mid)) {
    printf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;string:&amp;#34;&lt;/span&gt;);
  }
  printf(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%s&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, rb_builtin_type_name(TYPE(recv)));
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qundef;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It’s purely a debug function right now. It prints “symbol:” if &lt;code&gt;mid&lt;/code&gt; is a symbol (&lt;code&gt;SYMBOL_P&lt;/code&gt; and &lt;code&gt;STRING_P&lt;/code&gt; are each “predicate” functions, hence the &lt;code&gt;_P&lt;/code&gt;), “string:” if we have a string. Then it prints the type of the receiver and a new line. This is how we end up with &lt;code&gt;symbol:File&lt;/code&gt; when we run our program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
&lt;span style=&#34;color:#75715e&#34;&gt;# symbol:File&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Did you know you can write to $stdout?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;whats-next&#34;&gt;What’s next?&lt;/h3&gt;
&lt;p&gt;I’m missing some things at the moment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tests&lt;/li&gt;
&lt;li&gt;Logic for handling the private/protected param&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Actual&lt;/em&gt; optimization code 😅&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;1-tests&#34;&gt;1. Tests&lt;/h3&gt;
&lt;p&gt;There should already be tests for &lt;code&gt;respond_to?&lt;/code&gt;, so I’ll start running those and rely on them for the moment.&lt;/p&gt;
&lt;p&gt;As might be expected for an entire language, there are &lt;em&gt;tons&lt;/em&gt; of tests. There is also RubySpec, which is the standard spec suite for every Ruby language implementation. It’s automatically included in the repository as well.&lt;/p&gt;
&lt;p&gt;I’ll rely on those specs for now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&amp;gt; make test-spec SPECOPTS=&amp;#34;../spec/ruby/core/kernel/respond_to_spec.rb&amp;#34;

ruby 3.5.0dev (2025-01-04T14:32:13Z opt-respond-to 5688434f63) +PRISM [arm64-darwin24]
[\ | ==================100%================== | 00:00:00]      0F      0E 

Finished in 0.007758 seconds

1 file, 13 examples, 24 expectations, 0 failures, 0 errors, 0 tagged
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As expected, it still works so far since my version is basically a pass-through. We’ll see if we need more specs later on or if the base set is enough.&lt;/p&gt;
&lt;h3 id=&#34;2-logic-for-handling-the-privateprotected-param&#34;&gt;2. Logic for handling the private/protected param&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;respond_to?&lt;/code&gt; takes a &lt;a href=&#34;https://docs.ruby-lang.org/en/master/Object.html#method-i-respond_to-3F&#34;&gt;second parameter&lt;/a&gt; - &lt;code&gt;include_all&lt;/code&gt; - which determines whether to include &lt;code&gt;private&lt;/code&gt; and &lt;code&gt;protected&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;I’ve &lt;em&gt;never&lt;/em&gt; seen someone use this second parameter, but I’m sure it’s out there somewhere 🤷‍♂️. &lt;a href=&#34;https://bsky.app/profile/chastell.net/post/3le7dx5wm2s2h&#34;&gt;Piotr Szotkowski recently told me he’s a fan&lt;/a&gt; of the &lt;a href=&#34;https://docs.ruby-lang.org/en/3.4/syntax/control_expressions_rdoc.html#label-Flip-Flop&#34;&gt;flip-flop operator&lt;/a&gt; - so the world is full of surprises 😉! Part of me wants to ignore it for optimizing and just pass through in that case, but that’s a total cop out.&lt;/p&gt;
&lt;p&gt;I think there is some VM magic I need to utilize to handle an optional argument, applying special attributes for dynamic stack pointer adjustment. For instance, &lt;code&gt;opt_send_without_block&lt;/code&gt; is defined like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;DEFINE_INSN
&lt;span style=&#34;color:#a6e22e&#34;&gt;opt_send_without_block&lt;/span&gt;
(CALL_DATA cd)
(...)
(VALUE val)
&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; attr &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; handles_sp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;span style=&#34;color:#75715e&#34;&gt;// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd-&amp;gt;ci);
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It doesn’t specify the pop values, but instead uses the syntax &lt;code&gt;(...)&lt;/code&gt; similar to argument forwarding in Ruby. It then specifies some stack pointer (“sp”) counts (those comments are actual code!), which I think allows it to handle a dynamic number of values to pop off the stack.&lt;/p&gt;
&lt;p&gt;This seems complex for my case, where I have one required and one optional argument. I’ll defer this one for the moment.&lt;/p&gt;
&lt;h3 id=&#34;3-_actual_-optimization-code&#34;&gt;3. &lt;em&gt;Actual&lt;/em&gt; optimization code&lt;/h3&gt;
&lt;p&gt;I actually don’t know if this is optimizable in a meaningful way. I’d be lying if I said I didn’t care if there’s an optimization win here - that’s the most satisfying/impactful outcome.&lt;/p&gt;
&lt;p&gt;This entire series is inspired by &lt;a href=&#34;https://byroot.github.io/ruby/json/2024/12/18/optimizing-ruby-json-part-2.html&#34;&gt;Optimizing Ruby’s JSON, Part 2&lt;/a&gt;, and one of the goals of that work was to reduce setup costs. Here’s some of the &lt;code&gt;JSON.dump&lt;/code&gt; method in its original form:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dump&lt;/span&gt;(obj, anIO &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, limit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, kwargs &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; anIO&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:to_io&lt;/span&gt;)
    anIO &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; anIO&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_io
  &lt;span style=&#34;color:#66d9ef&#34;&gt;elsif&lt;/span&gt; limit&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;nil? &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;anIO&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
    anIO, limit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, anIO
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The majority of the time, &lt;code&gt;anIO&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;, so it won’t have a &lt;code&gt;to_io&lt;/code&gt; or &lt;code&gt;write&lt;/code&gt; method. That means in a micro-benchmark running millions of times the call to &lt;code&gt;respond_to?&lt;/code&gt; is pure overhead. The solution in the post was to avoid the call when &lt;code&gt;nil&lt;/code&gt;, but how fast can we make it if we did a silly, &lt;code&gt;nil&lt;/code&gt;-specific optimization?&lt;/p&gt;
&lt;h3 id=&#34;setting-up-a-performance-baseline&#34;&gt;Setting up a performance baseline&lt;/h3&gt;
&lt;p&gt;Let’s setup a benchmark to see what our current performance is, as a baseline. In CRuby there are built-in benchmarking scripts we can use. We’ll define a new benchmark for &lt;code&gt;respond_to?&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# benchmark/object_respond_to.yml&lt;/span&gt;
&lt;span style=&#34;color:#f92672&#34;&gt;prelude&lt;/span&gt;: |&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  class Base; def foo; end end
&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  class OneTwentyEight &amp;lt; Base
&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    128.times { include(Module.new) }
&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  end
&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  obj = OneTwentyEight.new&lt;/span&gt;  
&lt;span style=&#34;color:#f92672&#34;&gt;benchmark&lt;/span&gt;:
  &lt;span style=&#34;color:#f92672&#34;&gt;respond_to_false&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;obj.respond_to?(:bar)&lt;/span&gt;
  &lt;span style=&#34;color:#f92672&#34;&gt;respond_to_true&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;obj.respond_to?(:foo)&lt;/span&gt;
  &lt;span style=&#34;color:#f92672&#34;&gt;respond_to_nil_false&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;nil.respond_to?(:bar)&lt;/span&gt;
&lt;span style=&#34;color:#f92672&#34;&gt;loop_count&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1_000_000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This YAML first sets up a &lt;code&gt;prelude&lt;/code&gt;, which is Ruby code to setup our benchmark:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It defines a &lt;code&gt;Base&lt;/code&gt; class with a &lt;code&gt;foo&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;Creates a child class called &lt;code&gt;OneTwentyEight&lt;/code&gt;, which extends the &lt;code&gt;Base&lt;/code&gt; class&lt;/li&gt;
&lt;li&gt;Includes &lt;code&gt;Module.new&lt;/code&gt; 128 times, to create alot of ancestors to search for methods&lt;/li&gt;
&lt;li&gt;Instantiates &lt;code&gt;OneTwentyEight&lt;/code&gt; to call from the benchmark&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benchmark keys specify what operations to run. &lt;code&gt;respond_to_false&lt;/code&gt; checks &lt;code&gt;respond_to?&lt;/code&gt; for a method that doesn&amp;rsquo;t exist, and &lt;code&gt;respond_to_true&lt;/code&gt; checks for a method that does exist. &lt;code&gt;respond_to_nil_false&lt;/code&gt; is unrelated to the prelude, but let&amp;rsquo;s me test how fast looking for a method on &lt;code&gt;nil&lt;/code&gt; is.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;loop_count&lt;/code&gt; is how many iterations the code will run. I believe it runs several times, and then calculates how many times per second it should be able to run. Aaron Patterson created this benchmark in a &lt;a href=&#34;https://github.com/ruby/ruby/pull/3873&#34;&gt;PR that never merged&lt;/a&gt;, so thanks to him for that!&lt;/p&gt;
&lt;p&gt;We can run the benchmark using &lt;code&gt;make benchmark ITEM=&#39;respond_to&#39;&lt;/code&gt;. I get the following output on a clean &lt;code&gt;master&lt;/code&gt; branch:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_nil_false  |     29.029M|   28.259M|
|                      |       1.03x|         -|
|respond_to_false      |     29.177M|   29.121M|
|                      |       1.00x|         -|
|respond_to_true       |     33.503M|   32.481M|
|                      |       1.03x|         -|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;compare-ruby&lt;/code&gt; is the version of Ruby the project was built with (yes, building Ruby requires Ruby 🫨). For me, that&amp;rsquo;s Ruby 3.4. &lt;code&gt;built-ruby&lt;/code&gt; is my local, built version. The differences in performance are pretty negligable - probably differences in compile flags used to build Rubies. The performance of each stays pretty close, and can flip-flip a bit between iterations.&lt;/p&gt;
&lt;p&gt;You can run alot of &lt;code&gt;respond_to?&lt;/code&gt;s in a second! The found method cases are the fastest, and the miss cases are consistently slower.&lt;/p&gt;
&lt;h3 id=&#34;a-first-silly-optimization&#34;&gt;A first silly optimization&lt;/h3&gt;
&lt;p&gt;Now that we have a baseline, let&amp;rsquo;s try two optimizations to see what our upper-limit might be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;nil&lt;/code&gt; specific check that &lt;em&gt;always&lt;/em&gt; returns false&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;nil&lt;/code&gt; specific check that has a hard-coded set of possible methods&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;First, we&amp;rsquo;ll change &lt;code&gt;opt_respond_to&lt;/code&gt; into a common pattern. Many instructions will call a method, and if the method returns &lt;code&gt;Qundef&lt;/code&gt;, they&amp;rsquo;ll revert to a base-case path. In our case right now, that&amp;rsquo;s &lt;code&gt;CALL_SIMPLE_METHOD()&lt;/code&gt;. I assume &lt;code&gt;Qundef&lt;/code&gt; exists to specify &amp;ldquo;undefined&amp;rdquo; behavior, to differentiate from &lt;code&gt;Qnil&lt;/code&gt; which could be a valid return value:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// insns.def
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;DEFINE_INSN
&lt;span style=&#34;color:#a6e22e&#34;&gt;opt_respond_to&lt;/span&gt;
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
  val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vm_opt_respond_to(recv, mid);
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (UNDEF_P(val)) {
    CALL_SIMPLE_METHOD();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here is our silliest optimization. If &lt;code&gt;recv&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;, always return false. Otherwise, return &lt;code&gt;Qundef&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// vm_insnhelper.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;vm_opt_respond_to&lt;/span&gt;(VALUE recv, VALUE mid)
{
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (NIL_P(recv)) {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qfalse;
  }

  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qundef;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s rerun our benchmark, and see what we get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&amp;gt; make benchmark ITEM=&amp;#39;respond_to&amp;#39;

# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_false      |     29.121M|   27.795M|
|                      |       1.05x|         -|
|respond_to_true       |     32.241M|   31.544M|
|                      |       1.02x|         -|
|respond_to_nil_false  |     26.872M|   57.894M|
|                      |           -|     2.15x|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh, not bad! Around a 2x improvement. But ya know, it&amp;rsquo;s totally incorrect. We can add a spec to the &lt;code&gt;respond_to_spec&lt;/code&gt; to check. It fails, as expected:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;it &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;returns true for checking for `==` on nil&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:==&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;should &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;

&lt;span style=&#34;color:#75715e&#34;&gt;# make test-spec SPECOPTS=&amp;#34;../spec/ruby/core/kernel/respond_to_spec.rb&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 1)&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Kernel#respond_to? returns true for checking for `==` on nil FAILED&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Expected false == true&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# to be truthy but was false&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# [/ | ==================100%================== | 00:00:00]      1F      0E &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Finished in 0.017146 seconds&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 1 file, 14 examples, 25 expectations, 1 failure, 0 errors, 0 tagged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;a-second-slightly-less-silly-optimization&#34;&gt;A second, slightly less silly optimization&lt;/h3&gt;
&lt;p&gt;What if I added some overhead, but not a &lt;em&gt;ton&lt;/em&gt; of overhead. First, I got every method available to me from an &lt;code&gt;irb&lt;/code&gt; session:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;methods
&lt;span style=&#34;color:#75715e&#34;&gt;# &amp;#34;rationalize&amp;#34;, &amp;#34;&amp;amp;&amp;#34;, &amp;#34;===&amp;#34;, &amp;#34;inspect&amp;#34;, &amp;#34;=~&amp;#34;, &amp;#34;to_a&amp;#34;,...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I took that and put it into an array of &lt;code&gt;char&lt;/code&gt;s in C. The first time we call our &lt;code&gt;vm_opt_respond_to&lt;/code&gt; function, it populates a &lt;code&gt;rb_id_table&lt;/code&gt; with each of the available method names using &lt;code&gt;rb_id_table_insert&lt;/code&gt;. &lt;code&gt;rb_id_table&lt;/code&gt; is an internal CRuby hashtable structure which revolves around &lt;code&gt;ID&lt;/code&gt;s, which I believe typically correspond to method names.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;recv&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;, we use &lt;code&gt;method_id_table&lt;/code&gt; to check if one of our hard-coded method names is being checked by &lt;code&gt;respond_to?&lt;/code&gt;, using &lt;code&gt;rb_id_table_lookup&lt;/code&gt;. If it returns true, we return &lt;code&gt;Qtrue&lt;/code&gt;, otherwise &lt;code&gt;Qfalse&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_id_table &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;method_id_table &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; NULL;

&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;vm_opt_respond_to&lt;/span&gt;(VALUE recv, VALUE mid)
{
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (method_id_table &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL) {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;method_names[] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rationalize&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;amp;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;===&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inspect&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;=~&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_a&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_s&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_i&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_f&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_r&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_c&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;nil?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pretty_print_cycle&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_h&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;^&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_json&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_yaml&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pretty_print&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pretty_print_instance_variables&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pretty_print_inspect&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;singleton_class&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dup&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;itself&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;methods&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;singleton_methods&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;protected_methods&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;private_methods&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public_methods&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_variables&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_variable_get&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_variable_set&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_variable_defined?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;remove_instance_variable&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_of?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;kind_of?&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;is_a?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;display&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;frozen?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;class&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;then&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yield_self&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tap&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;TypeName&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public_send&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;extend&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;clone&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;=&amp;gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pretty_inspect&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;!~&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;method&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eql?&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;respond_to?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public_method&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;singleton_method&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;define_singleton_method&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hash&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;freeze&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;object_id&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Namespace&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;send&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;to_enum&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;enum_for&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;equal?&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;!&amp;#34;&lt;/span&gt;,
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__send__&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;==&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;!=&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__id__&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_eval&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;instance_exec&amp;#34;&lt;/span&gt;
    };

    size_t method_names_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(method_names) &lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(method_names[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
    method_id_table &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_id_table_create(method_names_size);

    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (size_t i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; method_names_size; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
      ID id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_intern(method_names[i]);
      rb_id_table_insert(method_id_table, id, Qtrue);
    }
  }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (NIL_P(recv)) {
    ID id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_check_id(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;mid);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;id) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qfalse;

    VALUE val;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (rb_id_table_lookup(method_id_table, id, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;val)) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qtrue;
    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qfalse;
    }
  }

  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qundef;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How fast is this version, now that we&amp;rsquo;re doing some &lt;em&gt;actual&lt;/em&gt; work?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_false      |     29.668M|   28.738M|
|                      |       1.03x|         -|
|respond_to_true       |     33.320M|   29.829M|
|                      |       1.12x|         -|
|respond_to_nil_false  |     28.610M|   53.084M|
|                      |           -|     1.86x|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Still pretty fast! It even passes our spec now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;[| | ==================100%================== | 00:00:00]      0F      0E 
Finished in 0.008847 seconds
1 file, 14 examples, 25 expectations, 0 failures, 0 errors, 0 tagged
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;back-to-reality&#34;&gt;Back to reality&lt;/h3&gt;
&lt;p&gt;Ok - we described our base code. We walked through next steps. We ran some specs and got a feel for some benchmarks. It seems like our upper limit on performance may be about 2x how fast it currently runs - and it&amp;rsquo;s probably unattainable. But it&amp;rsquo;s nice to know the potential ceiling on performance from where things currently are.&lt;/p&gt;
&lt;p&gt;Next time we&amp;rsquo;ll dig into some previous optimization improvements to &lt;code&gt;respond_to?&lt;/code&gt; in older PRs, how &lt;code&gt;respond_to?&lt;/code&gt; works currently, and hopefully make our first &lt;em&gt;real&lt;/em&gt; optimization improvement. See you next time!&lt;/p&gt;
&lt;p&gt;PS - You can find the code changes made in the branch &lt;a href=&#34;https://github.com/ruby/ruby/commit/e6820ada262582d99b95dbcbcea38a935706da5e&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>In [part 5](https://jpcamara.com/2024/12/30/defining-an-instruction-adding-optrespondto.html), we finally got our new instruction defined and outputting as part of our bytecode. if you didn’t run it yourself, you just had to trust me that it really did run.


But, I just dropped most of the implementation code in without explaining it. Let’s start off by walking through the basic version, then start planning for the true optimization.


### The progress so far


Here’s our sample Ruby program:


```rb
puts &#34;Did you know you can write to $stdout?&#34; if $stdout.respond_to?(:write)
```
First, we’ll disassemble the code using `make run`, and run it using our C changes (you can pull the [work in progress here](https://github.com/ruby/ruby/compare/master...jpcamara:ruby:opt-respond-to)):


```text
RUNOPT0=--dump=insns make run
```
This gives us a new set of instructions. Most of it is the same as Ruby master, but `opt_send_without_block` is changed to `opt_respond_to`. The `calldata` containing `respond_to?` is still there, and I _think_ it’ll stay even once we finish the whole implementation:


```x86
# == disasm: #&lt;ISeq:&lt;main&gt;./test.rb:1 (1,0)-(1,76)&gt;
0000 getglobal                :$stdout                  (   1)[Li]
0002 putobject                :write
# our new instruction!
0004 opt_respond_to           &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
0006 branchunless             14
0008 putself
0009 putchilledstring         &#34;Did you know you can write to $stdout?&#34;
0011 opt_send_without_block   &lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&gt;
0013 leave
0014 putnil
0015 leave
```
Our current implementation is mostly just a pass through to the normal `respond_to?` method, with some debug information printed. Running it without the `dump=insns` option, this is the output we get:


```text
&gt; make run

symbol:File
Did you know you can write to $stdout?
```
`File` is the type of the receiver, `$stdout`, and `symbol` is the type of the method argument, `:write`.


&gt; 📝 In previous posts, we used `make runruby` and `make lldb-ruby`/`make gdb-ruby`. Based on feedback from Ruby maintainers in the know (like byroot), it seems like `make run` and `make lldb`/`make gdb` are the better options in 99% of cases. These commands use “miniruby”, which is all the Ruby syntax without loading stdlib and gems, so it should run faster. If you _do_ need the stdlib and standard gems, you’ll want to continue using `make runruby` and friends


### Breaking down the changes


The last post was running pretty long, so I dumped all the code at the end without explanation. Let&#39;s break each section down, starting with our `insns.def` change to the virtual machine DSL:


```c
//insns.def
DEFINE_INSN
opt_respond_to
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
    val = vm_opt_respond_to(recv, mid);
    CALL_SIMPLE_METHOD();
}
```
We have some context for how a virtual machine instruction is defined from the previous post, so let’s break this down:


- `opt_respond_to` is the name of the instruction
- `(CALL_DATA cd)` is the one “operand”, the call data of the method. I don’t think we’ll need this for our optimized version, but I think if we use a fallback it would still be required
- `(VALUE recv, VALUE mid)` are the values this instruction is expecting to be popped off the stack so they can be used in the call. In our sample program instructions this should correspond to `getglobal :$stdout` and `putobject :write`. `$stdout` is `recv`, or the “receiver”. `:write` is `mid`, or the “method id”
- `(VALUE val)` is the return value. Whatever gets set to `val` gets pushed onto the stack at the end of the instruction. The next instruction in our example is `branchunless`, which pops our `val` off the stack and tests it
- Next is the body of the instruction:
	- `val = vm_opt_respond_to(recv, mid);` here I followed the convention of other instructions which need some custom logic - they put their code inside of a `vm_` prefixed function named after their instruction, and define it in `vm_insnhelper.c`. My function takes the receiver and the method id, and we’ll dive into that in a bit
	- I think `CALL_SIMPLE_METHOD();` will use the `calldata` to call the original method. Normally you would check the return value of the `vm_` function to determine whether you want to pass through to the original implementation. In my case, my function is just printing some debug information so I let it always call the original 


We’ve dug into most of the pattern matching logic in `compile.c` in previous posts, so I’ll skip that part and focus on the instruction override:


```c
// compile.c
const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0);
//...
iobj-&gt;insn_id = BIN(opt_respond_to);
iobj-&gt;operand_size = 1;
iobj-&gt;operands = compile_data_calloc2(
  iseq, 
  iobj-&gt;operand_size, 
  sizeof(VALUE)
);
iobj-&gt;operands[0] = (VALUE)ci;
```
Once it&#39;s found an instruction that matches a `send` to `respond_to?`, we override the current information. First we set `insn_id` to `BIN(opt_respond_to)`, which we know expands to the enum value `YARVINSN_opt_respond_to`.


The rest seems… redundant? It already had `ci` at the first operand position, it was already an `operand_size` of 1. It’s possible I don’t need to recompile this, but I’ll need some guidance around that. It’s probably not harmful, but possibly unnecessary.


Last we’ve got our `vm_opt_respond_to` function:


```c
// vm_insnhelper.c
static VALUE
vm_opt_respond_to(VALUE recv, VALUE mid)
{
  if (SYMBOL_P(mid)) {
    printf(&#34;symbol:&#34;);
  } else if (STRING_P(mid)) {
    printf(&#34;string:&#34;);
  }
  printf(&#34;%s\n&#34;, rb_builtin_type_name(TYPE(recv)));
  return Qundef;
}
```
It’s purely a debug function right now. It prints “symbol:” if `mid` is a symbol (`SYMBOL_P` and `STRING_P` are each “predicate” functions, hence the `_P`), “string:” if we have a string. Then it prints the type of the receiver and a new line. This is how we end up with `symbol:File` when we run our program:


```rb
puts &#34;Did you know you can write to $stdout?&#34; if $stdout.respond_to?(:write)
# symbol:File
# Did you know you can write to $stdout?
```
### What’s next?


I’m missing some things at the moment:


1. Tests
1. Logic for handling the private/protected param
1. _Actual_ optimization code 😅


### 1. Tests


There should already be tests for `respond_to?`, so I’ll start running those and rely on them for the moment.


As might be expected for an entire language, there are _tons_ of tests. There is also RubySpec, which is the standard spec suite for every Ruby language implementation. It’s automatically included in the repository as well.


I’ll rely on those specs for now:


```text
&gt; make test-spec SPECOPTS=&#34;../spec/ruby/core/kernel/respond_to_spec.rb&#34;

ruby 3.5.0dev (2025-01-04T14:32:13Z opt-respond-to 5688434f63) +PRISM [arm64-darwin24]
[\ | ==================100%================== | 00:00:00]      0F      0E 

Finished in 0.007758 seconds

1 file, 13 examples, 24 expectations, 0 failures, 0 errors, 0 tagged
```
As expected, it still works so far since my version is basically a pass-through. We’ll see if we need more specs later on or if the base set is enough.


### 2. Logic for handling the private/protected param


`respond_to?` takes a [second parameter](https://docs.ruby-lang.org/en/master/Object.html#method-i-respond_to-3F) - `include_all` - which determines whether to include `private` and `protected` methods.


I’ve _never_ seen someone use this second parameter, but I’m sure it’s out there somewhere 🤷‍♂️. [Piotr Szotkowski recently told me he’s a fan](https://bsky.app/profile/chastell.net/post/3le7dx5wm2s2h) of the [flip-flop operator](https://docs.ruby-lang.org/en/3.4/syntax/control_expressions_rdoc.html#label-Flip-Flop) - so the world is full of surprises 😉! Part of me wants to ignore it for optimizing and just pass through in that case, but that’s a total cop out.


I think there is some VM magic I need to utilize to handle an optional argument, applying special attributes for dynamic stack pointer adjustment. For instance, `opt_send_without_block` is defined like this:


```c
DEFINE_INSN
opt_send_without_block
(CALL_DATA cd)
(...)
(VALUE val)
// attr bool handles_sp = true;
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd-&gt;ci);
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
{
  //...
}
```
It doesn’t specify the pop values, but instead uses the syntax `(...)` similar to argument forwarding in Ruby. It then specifies some stack pointer (“sp”) counts (those comments are actual code!), which I think allows it to handle a dynamic number of values to pop off the stack.


This seems complex for my case, where I have one required and one optional argument. I’ll defer this one for the moment.


### 3. _Actual_ optimization code


I actually don’t know if this is optimizable in a meaningful way. I’d be lying if I said I didn’t care if there’s an optimization win here - that’s the most satisfying/impactful outcome.


This entire series is inspired by [Optimizing Ruby’s JSON, Part 2](https://byroot.github.io/ruby/json/2024/12/18/optimizing-ruby-json-part-2.html), and one of the goals of that work was to reduce setup costs. Here’s some of the `JSON.dump` method in its original form:


```rb
def dump(obj, anIO = nil, limit = nil, kwargs = nil)
  #...
  if anIO.respond_to?(:to_io)
    anIO = anIO.to_io
  elsif limit.nil? &amp;&amp; !anIO.respond_to?(:write)
    anIO, limit = nil, anIO
  end
  #...
end
```
The majority of the time, `anIO` is `nil`, so it won’t have a `to_io` or `write` method. That means in a micro-benchmark running millions of times the call to `respond_to?` is pure overhead. The solution in the post was to avoid the call when `nil`, but how fast can we make it if we did a silly, `nil`-specific optimization?


### Setting up a performance baseline


Let’s setup a benchmark to see what our current performance is, as a baseline. In CRuby there are built-in benchmarking scripts we can use. We’ll define a new benchmark for `respond_to?`:


```yml
# benchmark/object_respond_to.yml
prelude: |
  class Base; def foo; end end
  class OneTwentyEight &lt; Base
    128.times { include(Module.new) }
  end
  obj = OneTwentyEight.new
benchmark:
  respond_to_false: obj.respond_to?(:bar)
  respond_to_true: obj.respond_to?(:foo)
  respond_to_nil_false: nil.respond_to?(:bar)
loop_count: 1_000_000
```
This YAML first sets up a `prelude`, which is Ruby code to setup our benchmark:


- It defines a `Base` class with a `foo` method
- Creates a child class called `OneTwentyEight`, which extends the `Base` class
- Includes `Module.new` 128 times, to create alot of ancestors to search for methods
- Instantiates `OneTwentyEight` to call from the benchmark


The benchmark keys specify what operations to run. `respond_to_false` checks `respond_to?` for a method that doesn&#39;t exist, and `respond_to_true` checks for a method that does exist. `respond_to_nil_false` is unrelated to the prelude, but let&#39;s me test how fast looking for a method on `nil` is.


The `loop_count` is how many iterations the code will run. I believe it runs several times, and then calculates how many times per second it should be able to run. Aaron Patterson created this benchmark in a [PR that never merged](https://github.com/ruby/ruby/pull/3873), so thanks to him for that!


We can run the benchmark using `make benchmark ITEM=&#39;respond_to&#39;`. I get the following output on a clean `master` branch:


```text
# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_nil_false  |     29.029M|   28.259M|
|                      |       1.03x|         -|
|respond_to_false      |     29.177M|   29.121M|
|                      |       1.00x|         -|
|respond_to_true       |     33.503M|   32.481M|
|                      |       1.03x|         -|
```
`compare-ruby` is the version of Ruby the project was built with (yes, building Ruby requires Ruby 🫨). For me, that&#39;s Ruby 3.4. `built-ruby` is my local, built version. The differences in performance are pretty negligable - probably differences in compile flags used to build Rubies. The performance of each stays pretty close, and can flip-flip a bit between iterations.


You can run alot of `respond_to?`s in a second! The found method cases are the fastest, and the miss cases are consistently slower.


### A first silly optimization


Now that we have a baseline, let&#39;s try two optimizations to see what our upper-limit might be:


1. A `nil` specific check that _always_ returns false
1. A `nil` specific check that has a hard-coded set of possible methods


First, we&#39;ll change `opt_respond_to` into a common pattern. Many instructions will call a method, and if the method returns `Qundef`, they&#39;ll revert to a base-case path. In our case right now, that&#39;s `CALL_SIMPLE_METHOD()`. I assume `Qundef` exists to specify &#34;undefined&#34; behavior, to differentiate from `Qnil` which could be a valid return value:


```c
// insns.def
DEFINE_INSN
opt_respond_to
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
  val = vm_opt_respond_to(recv, mid);
  if (UNDEF_P(val)) {
    CALL_SIMPLE_METHOD();
  }
}
```
And here is our silliest optimization. If `recv` is `nil`, always return false. Otherwise, return `Qundef`:


```c
// vm_insnhelper.c
static VALUE
vm_opt_respond_to(VALUE recv, VALUE mid)
{
  if (NIL_P(recv)) {
    return Qfalse;
  }

  return Qundef;
}
```
Let&#39;s rerun our benchmark, and see what we get:


```text
&gt; make benchmark ITEM=&#39;respond_to&#39;

# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_false      |     29.121M|   27.795M|
|                      |       1.05x|         -|
|respond_to_true       |     32.241M|   31.544M|
|                      |       1.02x|         -|
|respond_to_nil_false  |     26.872M|   57.894M|
|                      |           -|     2.15x|
```
Oh, not bad! Around a 2x improvement. But ya know, it&#39;s totally incorrect. We can add a spec to the `respond_to_spec` to check. It fails, as expected:


```rb
it &#34;returns true for checking for `==` on nil&#34; do
  nil.respond_to?(:==).should == true
end

# make test-spec SPECOPTS=&#34;../spec/ruby/core/kernel/respond_to_spec.rb&#34;
# 1)
# Kernel#respond_to? returns true for checking for `==` on nil FAILED
# Expected false == true
# to be truthy but was false
# [/ | ==================100%================== | 00:00:00]      1F      0E 
# Finished in 0.017146 seconds
# 1 file, 14 examples, 25 expectations, 1 failure, 0 errors, 0 tagged
```
### A second, slightly less silly optimization


What if I added some overhead, but not a _ton_ of overhead. First, I got every method available to me from an `irb` session:


```rb
nil.methods
# &#34;rationalize&#34;, &#34;&amp;&#34;, &#34;===&#34;, &#34;inspect&#34;, &#34;=~&#34;, &#34;to_a&#34;,...
```
Then I took that and put it into an array of `char`s in C. The first time we call our `vm_opt_respond_to` function, it populates a `rb_id_table` with each of the available method names using `rb_id_table_insert`. `rb_id_table` is an internal CRuby hashtable structure which revolves around `ID`s, which I believe typically correspond to method names.


If the `recv` is `nil`, we use `method_id_table` to check if one of our hard-coded method names is being checked by `respond_to?`, using `rb_id_table_lookup`. If it returns true, we return `Qtrue`, otherwise `Qfalse`.


```c
static struct rb_id_table *method_id_table = NULL;

static VALUE
vm_opt_respond_to(VALUE recv, VALUE mid)
{
  if (method_id_table == NULL) {
    const char *method_names[] = {
      &#34;rationalize&#34;, &#34;&amp;&#34;, &#34;===&#34;, &#34;inspect&#34;, &#34;=~&#34;, &#34;to_a&#34;, &#34;to_s&#34;, &#34;to_i&#34;, &#34;to_f&#34;, &#34;to_r&#34;,
      &#34;to_c&#34;, &#34;nil?&#34;, &#34;pretty_print_cycle&#34;, &#34;|&#34;, &#34;to_h&#34;, &#34;^&#34;, &#34;to_json&#34;, &#34;to_yaml&#34;,
      &#34;pretty_print&#34;, &#34;pretty_print_instance_variables&#34;, &#34;pretty_print_inspect&#34;, &#34;singleton_class&#34;,
      &#34;dup&#34;, &#34;itself&#34;, &#34;methods&#34;, &#34;singleton_methods&#34;, &#34;protected_methods&#34;, &#34;private_methods&#34;,
      &#34;public_methods&#34;, &#34;instance_variables&#34;, &#34;instance_variable_get&#34;, &#34;instance_variable_set&#34;,
      &#34;instance_variable_defined?&#34;, &#34;remove_instance_variable&#34;, &#34;instance_of?&#34;, &#34;kind_of?&#34;,
      &#34;is_a?&#34;, &#34;display&#34;, &#34;frozen?&#34;, &#34;class&#34;, &#34;then&#34;, &#34;yield_self&#34;, &#34;tap&#34;, &#34;TypeName&#34;,
      &#34;public_send&#34;, &#34;extend&#34;, &#34;clone&#34;, &#34;&lt;=&gt;&#34;, &#34;pretty_inspect&#34;, &#34;!~&#34;, &#34;method&#34;, &#34;eql?&#34;,
      &#34;respond_to?&#34;, &#34;public_method&#34;, &#34;singleton_method&#34;, &#34;define_singleton_method&#34;, &#34;hash&#34;,
      &#34;freeze&#34;, &#34;object_id&#34;, &#34;Namespace&#34;, &#34;send&#34;, &#34;to_enum&#34;, &#34;enum_for&#34;, &#34;equal?&#34;, &#34;!&#34;,
      &#34;__send__&#34;, &#34;==&#34;, &#34;!=&#34;, &#34;__id__&#34;, &#34;instance_eval&#34;, &#34;instance_exec&#34;
    };

    size_t method_names_size = sizeof(method_names) / sizeof(method_names[0]);
    method_id_table = rb_id_table_create(method_names_size);

    for (size_t i = 0; i &lt; method_names_size; i++) {
      ID id = rb_intern(method_names[i]);
      rb_id_table_insert(method_id_table, id, Qtrue);
    }
  }
  if (NIL_P(recv)) {
    ID id = rb_check_id(&amp;mid);
    if (!id) return Qfalse;

    VALUE val;
    if (rb_id_table_lookup(method_id_table, id, &amp;val)) {
      return Qtrue;
    } else {
      return Qfalse;
    }
  }

  return Qundef;
}
```
How fast is this version, now that we&#39;re doing some _actual_ work?


```text
# Iteration per second (i/s)
|                      |compare-ruby|built-ruby|
|:---------------------|-----------:|---------:|
|respond_to_false      |     29.668M|   28.738M|
|                      |       1.03x|         -|
|respond_to_true       |     33.320M|   29.829M|
|                      |       1.12x|         -|
|respond_to_nil_false  |     28.610M|   53.084M|
|                      |           -|     1.86x|
```
Still pretty fast! It even passes our spec now:


```text
[| | ==================100%================== | 00:00:00]      0F      0E 
Finished in 0.008847 seconds
1 file, 14 examples, 25 expectations, 0 failures, 0 errors, 0 tagged
```
### Back to reality


Ok - we described our base code. We walked through next steps. We ran some specs and got a feel for some benchmarks. It seems like our upper limit on performance may be about 2x how fast it currently runs - and it&#39;s probably unattainable. But it&#39;s nice to know the potential ceiling on performance from where things currently are.


Next time we&#39;ll dig into some previous optimization improvements to `respond_to?` in older PRs, how `respond_to?` works currently, and hopefully make our first _real_ optimization improvement. See you next time!


PS - You can find the code changes made in the branch [here](https://github.com/ruby/ruby/commit/e6820ada262582d99b95dbcbcea38a935706da5e).
</source:markdown>
    </item>
    
    <item>
      <title>Defining an instruction: adding opt_respond_to to the Ruby VM, part 5</title>
      <link>https://jpcamara.com/2024/12/30/defining-an-instruction-adding-optrespondto.html</link>
      <pubDate>Tue, 31 Dec 2024 22:43:30 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/30/defining-an-instruction-adding-optrespondto.html</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;#&#34;&gt;Peephole optimizations: adding &lt;code&gt;opt_respond_to&lt;/code&gt; to the Ruby VM, part 4&lt;/a&gt;, we dug deep. We found the connection between prism compilation and the specialization we need for our new bytecode, called “peephole optimization”. We learned how to debug and step through C code in the Ruby runtime, and we added some logic for matching the “pattern” of the instruction we want to change.&lt;/p&gt;
&lt;p&gt;Now that we know where the specialization needs to go and how to match what needs to be specialized - what do we actually replace it with? How do we get the virtual machine to recognize &lt;code&gt;opt_respond_to&lt;/code&gt;?&lt;/p&gt;
&lt;h3 id=&#34;pattern-matching-bytecode-instructions&#34;&gt;Pattern matching bytecode instructions&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;opt_ary_freeze&lt;/code&gt; has been a great learning tool - let’s see what it teaches us about adding a new instruction name.&lt;/p&gt;
&lt;p&gt;Here’s a refresher on how &lt;code&gt;iseq_peephole_optimize&lt;/code&gt; matches on &lt;code&gt;newarray&lt;/code&gt;, and then replaces it with &lt;code&gt;opt_ary_freeze&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (
  IS_INSN_ID(iobj, newarray)
) {
  LINK_ELEMENT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;link.next;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (
    IS_INSN(next) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    IS_INSN_ID(next, send)
  ) {
    &lt;span style=&#34;color:#75715e&#34;&gt;//... more if statements
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;it first checks if the instruction id of &lt;code&gt;iobj&lt;/code&gt; is &lt;code&gt;newarray&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it grabs the next element and checks if its an instruction, then checks if the instruction is &lt;code&gt;send&lt;/code&gt; (it also checks if the method id is “freeze”, not shown above)&lt;/li&gt;
&lt;li&gt;if those checks match, it replaces the instruction id with &lt;code&gt;opt_ary_freeze&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s pretty reasonable to follow. But how do &lt;code&gt;IS_INSN&lt;/code&gt; and &lt;code&gt;IS_INSN_ID&lt;/code&gt; work? What is &lt;code&gt;BIN&lt;/code&gt;? What type is &lt;code&gt;opt_ary_freeze&lt;/code&gt; - where is it defined? How do we add new instructions ourselves?&lt;/p&gt;
&lt;h3 id=&#34;macros-and-enums&#34;&gt;Macros and enums&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;BIN&lt;/code&gt;, &lt;code&gt;IS_INSN&lt;/code&gt; and &lt;code&gt;IS_INSN_ID&lt;/code&gt; are all C macros that revolve around interacting with virtual machine instructions.&lt;/p&gt;
&lt;p&gt;Macros in C get embedded directly into your code in a preprocessing step before being compiled, so you can write things that look pretty odd compared to a typical C-like syntax. Here’s the definition for &lt;code&gt;BIN&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define BIN(n) YARVINSN_##n
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 &lt;code&gt;BIN&lt;/code&gt; probably stands for “Binary INstruction”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That ## is kind of like string interpolation, but the result is a static part of your actual code. This means that anywhere &lt;code&gt;BIN&lt;/code&gt; is called, it’s kind of like saying &lt;code&gt;YARVINSN_#{n}&lt;/code&gt; in Ruby. So this code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Gets transformed into this, right before the program is compiled:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; YARVINSN_opt_ary_freeze;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here’s the definition for &lt;code&gt;IS_INSN_ID&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == BIN(insn))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Based on our understanding of macros and &lt;code&gt;BIN&lt;/code&gt;, so far, it gets transformed into:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == YARVINSN_##insn)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here’s the definition for &lt;code&gt;INSN_OF&lt;/code&gt;, it just casts &lt;code&gt;insn&lt;/code&gt; to an &lt;code&gt;INSN&lt;/code&gt; type, and accesses its instruction id:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define INSN_OF(insn) \
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  (((INSN*)(insn))-&amp;gt;insn_id)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That means the expanded version of &lt;code&gt;IS_INSN_ID&lt;/code&gt; is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define IS_INSN_ID(iobj, insn) \
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  (((INSN*)(insn))-&amp;gt;insn_id == YARVINSN_##insn)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here’s the definition for &lt;code&gt;IS_INSN&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define IS_INSN(link) ((link)-&amp;gt;type == ISEQ_ELEMENT_INSN)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we combined all of it together, and manually inline it, here’s what our original pattern matching code looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (
  (((INSN&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)(iobj))&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; YARVINSN_newarray)
) {
  LINK_ELEMENT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;link.next;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (
    (next)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;type &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; ISEQ_ELEMENT_INSN &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    (((INSN&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)(next))&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; YARVINSN_send)
  ) {
    &lt;span style=&#34;color:#75715e&#34;&gt;//... more if statements
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; YARVINSN_opt_ary_freeze;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’m glad CRuby adds those macros… this expanded code is a lot less readable.&lt;/p&gt;
&lt;p&gt;Why did I expand it and make that original code less clear? I wanted to think through what the instructions &lt;em&gt;really&lt;/em&gt; look like at runtime, and why. The reason I can infer, is that all of these macros let you focus on a syntax that looks just like our VM instructions, while making sure there are no name collisions behind the scenes.&lt;/p&gt;
&lt;p&gt;Ok, I still haven’t shown where the instruction comes from. Here’s the file you can actually find an &lt;code&gt;enum&lt;/code&gt; containing the entire list of vm instructions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// insns.inc
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; ruby_vminsn_type {
  BIN(nop),
  BIN(getlocal),
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  BIN(opt_ary_freeze),
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}

&lt;span style=&#34;color:#75715e&#34;&gt;// or, expanded by the preprocessor!
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; ruby_vminsn_type {
  YARVINSN_nop,
  YARVINSN_getlocal,
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  YARVINSN_opt_ary_freeze,
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;insns.inc&lt;/code&gt; gets included anywhere we need instruction checks, like in &lt;code&gt;compile.c&lt;/code&gt;. These &lt;code&gt;enum&lt;/code&gt; values are globally available anywhere this file is included. Thanks to &lt;code&gt;BIN&lt;/code&gt; prepending all of their names with &lt;code&gt;YARVINSN_&lt;/code&gt;, we can use them in a convenient syntax without having any collisions.&lt;/p&gt;
&lt;p&gt;So if I search the CRuby repo for &lt;code&gt;insns.inc&lt;/code&gt;, where can I find it? Hmmm, I can’t 🤔. &lt;code&gt;insns.inc&lt;/code&gt; is a generated file! I can only see it locally, after compiling the entire project. Where does that file get generated from?&lt;/p&gt;
&lt;h3 id=&#34;a-virtual-machine-dsl&#34;&gt;A virtual machine DSL&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;insns.inc&lt;/code&gt; tells us the name of each instruction available, the file it is generated from defines every instruction available in the Ruby virtual machine, &lt;em&gt;and&lt;/em&gt; how it should respond to that instruction. It’s called &lt;code&gt;insns.def&lt;/code&gt;. The file looks a lot like C, but it’s actually a kind of &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Domain-specific_language&#34;&gt;DSL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It lets you define a simplified set of information for the instruction. That simplified format is then compiled into a more comprehensive, C compatible version.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s compilers all the way down… 😵‍💫&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The top of the file defines the format. I don’t &lt;em&gt;fully&lt;/em&gt; understand it, but let’s walk through it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;DEFINE_INSN
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;instruction_name
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;(type operand, type operand, ..)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;(pop_values, ..)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;(return values ..)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// attr type name contents..
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;{
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  .. // insn body
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;}
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An instructions consists of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A name ✅&lt;/li&gt;
&lt;li&gt;Operands - like our “call data”. I’m not sure what other types of operands are typically used for, but I know &lt;code&gt;opt_ary_freezs&lt;/code&gt; puts the frozen array here as well&lt;/li&gt;
&lt;li&gt;Values to pop off the virtual machine stack so we can operate on them. This should be values that we’ve seen pushed onto the stack in previous instructions&lt;/li&gt;
&lt;li&gt;A return value&lt;/li&gt;
&lt;li&gt;(I don’t fully understand the value of &lt;code&gt;attr type&lt;/code&gt; but it seems to influence what code gets generated by the instruction definition)&lt;/li&gt;
&lt;li&gt;A C-compatible body&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s a lot, baked into a small interface. Let’s look at a very simple example. Here’s one of the simplest instructions available, &lt;code&gt;putnil&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;DEFINE_INSN
&lt;span style=&#34;color:#a6e22e&#34;&gt;putnil&lt;/span&gt;
()
()
(VALUE val)
{
    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Qnil;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Looks… pointless? The&lt;code&gt;putnil&lt;/code&gt; instruction takes no arguments, and has a return value of &lt;code&gt;val&lt;/code&gt;. The only thing the code block does is set &lt;code&gt;val&lt;/code&gt; equal to &lt;code&gt;Qnil&lt;/code&gt;, which is a special value in CRuby representing Ruby’s &lt;code&gt;nil&lt;/code&gt;. What does that accomplish?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 &lt;code&gt;VALUE&lt;/code&gt; is a special type in CRuby that points at a Ruby object, usually located on the &lt;a href=&#34;https://byroot.github.io/ruby/json/2024/12/29/optimizing-ruby-json-part-4.html#stack-and-heap&#34;&gt;heap&lt;/a&gt;. When you see &lt;code&gt;VALUE&lt;/code&gt;, this often means we’re looking at a value you’d use in a Ruby program.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This file is compiled into regular C code, and the context of this simple instruction becomes clearer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// vm.inc
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/* insn putnil()()(val) */&lt;/span&gt;
INSN_ENTRY(putnil)
{
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  VALUE val;
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#   define NAME_OF_CURRENT_INSN putnil
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#   line 331 &amp;#34;../insns.def&amp;#34;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
  val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Qnil;
}
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  INC_SP(INSN_ATTR(sp_inc));
  TOPN(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; val;
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;The return value &lt;code&gt;VALUE val&lt;/code&gt; is declared&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s set to &lt;code&gt;val = Qnil&lt;/code&gt;, the instruction we saw in &lt;code&gt;insns.def&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INC_SP&lt;/code&gt; is called, which I believe &amp;ldquo;increments&amp;rdquo; the &amp;ldquo;stack pointer&amp;rdquo;, giving us extra space on the stack to push onto?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TOPN(0) = val&lt;/code&gt; sets &lt;code&gt;val&lt;/code&gt; to the top of the stack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think I’ll dig more into that next time. But let&amp;rsquo;s get back to the task at hand - it’s time to try and get our &lt;code&gt;respond_to?&lt;/code&gt; call replaced with &lt;code&gt;opt_respond_to&lt;/code&gt;!&lt;/p&gt;
&lt;h3 id=&#34;adding-to-the-dsl&#34;&gt;Adding to the DSL&lt;/h3&gt;
&lt;p&gt;It took me a bit of banging my head against a wall, but here is the working instruction and specialization, in a basic form:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// insns.def
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;DEFINE_INSN
&lt;span style=&#34;color:#a6e22e&#34;&gt;opt_respond_to&lt;/span&gt;
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
    val &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; vm_opt_respond_to(recv, mid);
    CALL_SIMPLE_METHOD();
}

&lt;span style=&#34;color:#75715e&#34;&gt;// compile.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (IS_INSN_ID(iobj, send)) {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ci &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);

  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idRespond_to) {
      iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_respond_to);
      iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
      iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; compile_data_calloc2(iseq, iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(VALUE));
      iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (VALUE)ci;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we dump the instructions, we finally see our new instruction &lt;code&gt;opt_respond_to&lt;/code&gt;. It&amp;rsquo;s not really doing anything yet, but it&amp;rsquo;s there!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)

&lt;span style=&#34;color:#75715e&#34;&gt;# &amp;gt; RUNOPT0=--dump=insns make run&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# == disasm: #&amp;lt;ISeq:&amp;lt;main&amp;gt;./test.rb:1 (1,0)-(1,76)&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0000 getglobal                :$stdout                  (   1)[Li]&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0002 putobject                :write&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0004 opt_respond_to           &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0006 branchunless             14&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0008 putself&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0009 putchilledstring         &amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0011 opt_send_without_block   &amp;lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0013 leave&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0014 putnil&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 0015 leave&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sorry to just dump the code here, and split. We&amp;rsquo;ll dig into it more, and expand on it next time! There is lots more to do, and to explain, but i&amp;rsquo;m excited about this milestone! See you next time! 👋🏼&lt;/p&gt;
&lt;p&gt;PS - since something is working now, i&amp;rsquo;ve pushed up my basic code so far, &lt;a href=&#34;https://github.com/jpcamara/ruby/commit/5688434f63b0456e0250796b58efbfb00ade916c&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>In [Peephole optimizations: adding `opt_respond_to` to the Ruby VM, part 4](#), we dug deep. We found the connection between prism compilation and the specialization we need for our new bytecode, called “peephole optimization”. We learned how to debug and step through C code in the Ruby runtime, and we added some logic for matching the “pattern” of the instruction we want to change.


Now that we know where the specialization needs to go and how to match what needs to be specialized - what do we actually replace it with? How do we get the virtual machine to recognize `opt_respond_to`?


### Pattern matching bytecode instructions
`opt_ary_freeze` has been a great learning tool - let’s see what it teaches us about adding a new instruction name.


Here’s a refresher on how `iseq_peephole_optimize` matches on `newarray`, and then replaces it with `opt_ary_freeze`:


```c
if (
  IS_INSN_ID(iobj, newarray)
) {
  LINK_ELEMENT *next = iobj-&gt;link.next;
  if (
    IS_INSN(next) &amp;&amp;
    IS_INSN_ID(next, send)
  ) {
    //... more if statements
    iobj-&gt;insn_id = BIN(opt_ary_freeze);
```
- it first checks if the instruction id of `iobj` is `newarray`
- it grabs the next element and checks if its an instruction, then checks if the instruction is `send` (it also checks if the method id is “freeze”, not shown above)
- if those checks match, it replaces the instruction id with `opt_ary_freeze`


That’s pretty reasonable to follow. But how do `IS_INSN` and `IS_INSN_ID` work? What is `BIN`? What type is `opt_ary_freeze` - where is it defined? How do we add new instructions ourselves?


### Macros and enums
`BIN`, `IS_INSN` and `IS_INSN_ID` are all C macros that revolve around interacting with virtual machine instructions.


Macros in C get embedded directly into your code in a preprocessing step before being compiled, so you can write things that look pretty odd compared to a typical C-like syntax. Here’s the definition for `BIN`:


```c
#define BIN(n) YARVINSN_##n
```
&gt; 📝 `BIN` probably stands for “Binary INstruction”


That ## is kind of like string interpolation, but the result is a static part of your actual code. This means that anywhere `BIN` is called, it’s kind of like saying `YARVINSN_#{n}` in Ruby. So this code:


```c
iobj-&gt;insn_id = BIN(opt_ary_freeze);
```
Gets transformed into this, right before the program is compiled:


```c
iobj-&gt;insn_id = YARVINSN_opt_ary_freeze;
```
Here’s the definition for `IS_INSN_ID`:


```c
#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == BIN(insn))
```
Based on our understanding of macros and `BIN`, so far, it gets transformed into:


```c
#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == YARVINSN_##insn)
```
Here’s the definition for `INSN_OF`, it just casts `insn` to an `INSN` type, and accesses its instruction id:


```c
#define INSN_OF(insn) \
  (((INSN*)(insn))-&gt;insn_id)
```
That means the expanded version of `IS_INSN_ID` is:


```c
#define IS_INSN_ID(iobj, insn) \
  (((INSN*)(insn))-&gt;insn_id == YARVINSN_##insn)
```
Here’s the definition for `IS_INSN`:


```c
#define IS_INSN(link) ((link)-&gt;type == ISEQ_ELEMENT_INSN)
```
If we combined all of it together, and manually inline it, here’s what our original pattern matching code looks like:


```c
if (
  (((INSN*)(iobj))-&gt;insn_id == YARVINSN_newarray)
) {
  LINK_ELEMENT *next = iobj-&gt;link.next;
  if (
    (next)-&gt;type == ISEQ_ELEMENT_INSN &amp;&amp;
    (((INSN*)(next))-&gt;insn_id == YARVINSN_send)
  ) {
    //... more if statements
    iobj-&gt;insn_id = YARVINSN_opt_ary_freeze;
```
I’m glad CRuby adds those macros… this expanded code is a lot less readable.


Why did I expand it and make that original code less clear? I wanted to think through what the instructions _really_ look like at runtime, and why. The reason I can infer, is that all of these macros let you focus on a syntax that looks just like our VM instructions, while making sure there are no name collisions behind the scenes.


Ok, I still haven’t shown where the instruction comes from. Here’s the file you can actually find an `enum` containing the entire list of vm instructions:


```c
// insns.inc
enum ruby_vminsn_type {
  BIN(nop),
  BIN(getlocal),
  //...
  BIN(opt_ary_freeze),
  //...
}

// or, expanded by the preprocessor!
enum ruby_vminsn_type {
  YARVINSN_nop,
  YARVINSN_getlocal,
  //...
  YARVINSN_opt_ary_freeze,
  //...
}
```
`insns.inc` gets included anywhere we need instruction checks, like in `compile.c`. These `enum` values are globally available anywhere this file is included. Thanks to `BIN` prepending all of their names with `YARVINSN_`, we can use them in a convenient syntax without having any collisions.


So if I search the CRuby repo for `insns.inc`, where can I find it? Hmmm, I can’t 🤔. `insns.inc` is a generated file! I can only see it locally, after compiling the entire project. Where does that file get generated from?


### A virtual machine DSL
While `insns.inc` tells us the name of each instruction available, the file it is generated from defines every instruction available in the Ruby virtual machine, _and_ how it should respond to that instruction. It’s called `insns.def`. The file looks a lot like C, but it’s actually a kind of [DSL](https://en.m.wikipedia.org/wiki/Domain-specific_language).


It lets you define a simplified set of information for the instruction. That simplified format is then compiled into a more comprehensive, C compatible version.


&gt; It’s compilers all the way down… 😵‍💫


The top of the file defines the format. I don’t _fully_ understand it, but let’s walk through it:


```c
/*
DEFINE_INSN
instruction_name
(type operand, type operand, ..)
(pop_values, ..)
(return values ..)
// attr type name contents..
{
  .. // insn body
}
*/
```
An instructions consists of:


- A name ✅
- Operands - like our “call data”. I’m not sure what other types of operands are typically used for, but I know `opt_ary_freezs` puts the frozen array here as well
- Values to pop off the virtual machine stack so we can operate on them. This should be values that we’ve seen pushed onto the stack in previous instructions
- A return value
- (I don’t fully understand the value of `attr type` but it seems to influence what code gets generated by the instruction definition)
- A C-compatible body


That’s a lot, baked into a small interface. Let’s look at a very simple example. Here’s one of the simplest instructions available, `putnil`:


```c
DEFINE_INSN
putnil
()
()
(VALUE val)
{
    val = Qnil;
}
```
Looks… pointless? The`putnil` instruction takes no arguments, and has a return value of `val`. The only thing the code block does is set `val` equal to `Qnil`, which is a special value in CRuby representing Ruby’s `nil`. What does that accomplish?


&gt; 📝 `VALUE` is a special type in CRuby that points at a Ruby object, usually located on the [heap](https://byroot.github.io/ruby/json/2024/12/29/optimizing-ruby-json-part-4.html#stack-and-heap). When you see `VALUE`, this often means we’re looking at a value you’d use in a Ruby program.


This file is compiled into regular C code, and the context of this simple instruction becomes clearer:


```c
// vm.inc
/* insn putnil()()(val) */
INSN_ENTRY(putnil)
{
  //...
  VALUE val;
  //...
#   define NAME_OF_CURRENT_INSN putnil
#   line 331 &#34;../insns.def&#34;
{
  val = Qnil;
}
  //...
  INC_SP(INSN_ATTR(sp_inc));
  TOPN(0) = val;
  //...
}
```
- The return value `VALUE val` is declared
- It&#39;s set to `val = Qnil`, the instruction we saw in `insns.def`
- `INC_SP` is called, which I believe &#34;increments&#34; the &#34;stack pointer&#34;, giving us extra space on the stack to push onto?
- `TOPN(0) = val` sets `val` to the top of the stack


I think I’ll dig more into that next time. But let&#39;s get back to the task at hand - it’s time to try and get our `respond_to?` call replaced with `opt_respond_to`!


### Adding to the DSL


It took me a bit of banging my head against a wall, but here is the working instruction and specialization, in a basic form:


```c
// insns.def
DEFINE_INSN
opt_respond_to
(CALL_DATA cd)
(VALUE recv, VALUE mid)
(VALUE val)
{
    val = vm_opt_respond_to(recv, mid);
    CALL_SIMPLE_METHOD();
}

// compile.c
if (IS_INSN_ID(iobj, send)) {
  const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0);
  const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(iobj, 1);

  if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 1 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idRespond_to) {
      iobj-&gt;insn_id = BIN(opt_respond_to);
      iobj-&gt;operand_size = 1;
      iobj-&gt;operands = compile_data_calloc2(iseq, iobj-&gt;operand_size, sizeof(VALUE));
      iobj-&gt;operands[0] = (VALUE)ci;
  }
}
```
If we dump the instructions, we finally see our new instruction `opt_respond_to`. It&#39;s not really doing anything yet, but it&#39;s there!


```rb
puts &#34;Did you know you can write to $stdout?&#34; if $stdout.respond_to?(:write)

# &gt; RUNOPT0=--dump=insns make run
# == disasm: #&lt;ISeq:&lt;main&gt;./test.rb:1 (1,0)-(1,76)&gt;
# 0000 getglobal                :$stdout                  (   1)[Li]
# 0002 putobject                :write
# 0004 opt_respond_to           &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
# 0006 branchunless             14
# 0008 putself
# 0009 putchilledstring         &#34;Did you know you can write to $stdout?&#34;
# 0011 opt_send_without_block   &lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&gt;
# 0013 leave
# 0014 putnil
# 0015 leave
```
Sorry to just dump the code here, and split. We&#39;ll dig into it more, and expand on it next time! There is lots more to do, and to explain, but i&#39;m excited about this milestone! See you next time! 👋🏼


PS - since something is working now, i&#39;ve pushed up my basic code so far, [here](https://github.com/jpcamara/ruby/commit/5688434f63b0456e0250796b58efbfb00ade916c).

</source:markdown>
    </item>
    
    <item>
      <title>Peephole optimizations: adding `opt_respond_to` to the Ruby VM, part 4</title>
      <link>https://jpcamara.com/2024/12/27/peephole-optimizations-adding-optrespondto-to.html</link>
      <pubDate>Fri, 27 Dec 2024 23:36:19 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/27/peephole-optimizations-adding-optrespondto-to.html</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html&#34;&gt;The Ruby Syntax Holy Grail: adding &lt;code&gt;opt_respond_to&lt;/code&gt; to the Ruby VM, part 3&lt;/a&gt;, I found what I referred to as the &amp;ldquo;Holy Grail&amp;rdquo; of Ruby syntax. I&amp;rsquo;m way overstating it, but it&amp;rsquo;s a readable, sequential way of viewing how a large portion of the Ruby syntax is compiled. Here&amp;rsquo;s a snippet of it as a reminder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// prism_compile.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_compile_node&lt;/span&gt;(rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;node, LINK_ANCHOR &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; ret, &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; popped, pm_scope_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;scope_node)
{
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_parser_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; scope_node&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parser;
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (PM_NODE_TYPE(node)) {
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_ARRAY_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// [foo, bar, baz]
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_array_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;cast &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_array_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node;
        pm_compile_array_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) cast, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cast&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;elements, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;location, ret, popped, scope_node);
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_MODULE_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// module Foo; end
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The file that code lives in, &lt;code&gt;prism_compile.c&lt;/code&gt;, is &lt;em&gt;enormous&lt;/em&gt;. &lt;code&gt;pm_compile_node&lt;/code&gt; itself is 1800+ lines, and the overall file is 11 &lt;em&gt;thousand&lt;/em&gt; lines. It&amp;rsquo;s daunting to say the least, but there are some obvious directions I can ignore - i&amp;rsquo;m trying to optimize a method call to &lt;code&gt;respond_to?&lt;/code&gt;, so I can sidestep a majority of the Ruby syntax.&lt;/p&gt;
&lt;p&gt;Still, where I do go, specifically?&lt;/p&gt;
&lt;h3 id=&#34;sage-wisdom&#34;&gt;Sage wisdom&lt;/h3&gt;
&lt;p&gt;Helpfully, I got two identical sets of direction based on &lt;a href=&#34;https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html&#34;&gt;part 3&lt;/a&gt;. One from &lt;a href=&#34;https://x.com/kddnewton&#34;&gt;Kevin Newton&lt;/a&gt;, creator of &lt;a href=&#34;https://github.com/ruby/prism&#34;&gt;Prism&lt;/a&gt;:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-26-at-12.15.03pm.png&#34; width=&#34;600&#34; height=&#34;143&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://x.com/kddnewton/status/1872280281409105925?s=46&#34;&gt;https://x.com/kddnewton/status/1872280281409105925?s=46&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And one from &lt;a href=&#34;https://bsky.app/profile/byroot.bsky.social&#34;&gt;byroot&lt;/a&gt;, who inspired this whole series:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-26-at-12.24.24pm.png&#34; width=&#34;600&#34; height=&#34;211&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://bsky.app/profile/byroot.bsky.social/post/3le6xypzykc2x&#34;&gt;https://bsky.app/profile/byroot.bsky.social/post/3le6xypzykc2x&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t want to jump to conclusions, but I think I need to look at the peephole optimizer 😆.&lt;/p&gt;
&lt;p&gt;And exactly what &lt;em&gt;is&lt;/em&gt; a &amp;ldquo;peephole optimizer&amp;rdquo;? Kevin described the process as &amp;ldquo;specialization comes after compilation&amp;rdquo;. From &lt;a href=&#34;https://en.wikipedia.org/wiki/Peephole_optimization&#34;&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Peephole optimization is an optimization technique performed on a small set of compiler-generated instructions, known as a peephole or window, that involves replacing the instructions with a logically equivalent set that has better performance.
&lt;a href=&#34;https://en.wikipedia.org/wiki/Peephole_optimization&#34;&gt;https://en.wikipedia.org/wiki/Peephole_optimization&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This seems to fit my goal pretty well. I want to replace the current &lt;code&gt;opt_send_without_block&lt;/code&gt; instruction with a specialized &lt;code&gt;opt_respond_to&lt;/code&gt; instruction, optimized for &lt;code&gt;respond_to?&lt;/code&gt; method calls.&lt;/p&gt;
&lt;h3 id=&#34;finding-the-optimizer&#34;&gt;Finding the optimizer&lt;/h3&gt;
&lt;p&gt;So where are peephole optimizations happening in CRuby today? In &lt;a href=&#34;https://github.com/etiennebarrie&#34;&gt;Étienne&lt;/a&gt;&amp;rsquo;s &lt;a href=&#34;https://github.com/ruby/ruby/pull/11406&#34;&gt;PR&lt;/a&gt;, he added optimization code to a function called&amp;hellip; &lt;code&gt;iseq_peephole_optimize&lt;/code&gt;. A little on the nose, don&amp;rsquo;t you think? Kevin&amp;rsquo;s comment &lt;em&gt;also&lt;/em&gt; mentioned &lt;code&gt;iseq_peephole_optimize&lt;/code&gt; - seems like the winner.&lt;/p&gt;
&lt;p&gt;I want to make the link between &lt;code&gt;iseq_peephole_optimize&lt;/code&gt; and where we left off at &lt;code&gt;pm_compile_node&lt;/code&gt;. Let&amp;rsquo;s dig into some code!&lt;/p&gt;
&lt;h3 id=&#34;disassembling-an-existing-optimization&#34;&gt;Disassembling an existing optimization&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m going to use Étienne&amp;rsquo;s frozen array optimization to get to the optimizer and see how it relates. If you want to follow along, start with the setup instructions from &lt;a href=&#34;https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html#getting-your-own-environment-setup&#34;&gt;part 3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;His optimization only applies to array and hash literals being frozen. So we&amp;rsquo;ll write a teensy Ruby program to demonstrate, and put it in &lt;code&gt;test.rb&lt;/code&gt; at the root of our CRuby project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# test.rb&lt;/span&gt;
pp &lt;span style=&#34;color:#f92672&#34;&gt;[].&lt;/span&gt;freeze
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The best way to run &lt;code&gt;test.rb&lt;/code&gt; here is to use &lt;code&gt;make&lt;/code&gt;. It will not only run the file, but also make sure things like C files get recompiled as necessary when you make changes. Let&amp;rsquo;s run our file, but dump the instructions it would generate for the Ruby VM:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;RUNOPT0=--dump=insns make runruby
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;RUNOPT0&lt;/code&gt; lets us add an option to the &lt;code&gt;ruby&lt;/code&gt; call, so it&amp;rsquo;s &lt;em&gt;effectively&lt;/em&gt; &lt;code&gt;ruby --dump=insns test.rb&lt;/code&gt;. Here&amp;rsquo;s the instructions we see - we can confirm that we are getting the optimized &lt;code&gt;opt_ary_freeze&lt;/code&gt; instruction from Étienne PR:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;== disasm: #&amp;lt;ISeq:&amp;lt;main&amp;gt;./test.rb:3 (3,0)-(3,12)&amp;gt;
0000 putself                      (   3)[Li]
0001 opt_ary_freeze               [], &amp;lt;calldata!mid:freeze, argc:0, ARGS_SIMPLE&amp;gt;
0004 opt_send_without_block       &amp;lt;calldata!mid:pp, argc:1, FCALL|ARGS_SIMPLE&amp;gt;
0006 leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You never know what code is truly doing until you run it. So far, I&amp;rsquo;ve just been reading and navigating the CRuby source. &lt;code&gt;iseq_peephole_optimize&lt;/code&gt; lives in &lt;code&gt;compile.c&lt;/code&gt; - let&amp;rsquo;s set a breakpoint and take a look 🕵🏼‍♂️.&lt;/p&gt;
&lt;h3 id=&#34;using-the-debugger&#34;&gt;Using the debugger&lt;/h3&gt;
&lt;p&gt;We can debug C code in CRuby &lt;em&gt;almost&lt;/em&gt; as easily as we can use a &lt;code&gt;debugger&lt;/code&gt;/&lt;code&gt;binding.pry&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For MacOS, you can use &lt;a href=&#34;https://lldb.llvm.org/&#34;&gt;&lt;code&gt;lldb&lt;/code&gt;&lt;/a&gt;, and for Docker/Linux, you can use &lt;a href=&#34;https://sourceware.org/gdb/&#34;&gt;&lt;code&gt;gdb&lt;/code&gt;&lt;/a&gt;. I&amp;rsquo;m going to do everything in &lt;code&gt;lldb&lt;/code&gt; to start, but I&amp;rsquo;ll show some equivalent commands for &lt;code&gt;gdb&lt;/code&gt; after.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by looking at the peephole optimization code for &lt;code&gt;[].freeze&lt;/code&gt;, inside of &lt;code&gt;iseq_peephole_optimize&lt;/code&gt;. I&amp;rsquo;ll add comments above each line to explain what I think it&amp;rsquo;s doing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// compile.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;iseq_peephole_optimize&lt;/span&gt;(rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq, LINK_ELEMENT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;list, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; do_tailcallopt)
{
         &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;// if the instruction is a `newarray` of zero length
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3469&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (IS_INSN_ID(iobj, newarray) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; INT2FIX(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)) {
             &lt;span style=&#34;color:#75715e&#34;&gt;// grab the next element after the current instruction
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3470&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;        LINK_ELEMENT &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;link.next;
             &lt;span style=&#34;color:#75715e&#34;&gt;// if `next` is an instruction, and the instruction is `send`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3471&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (IS_INSN(next) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; (IS_INSN_ID(next, send))) {
&lt;span style=&#34;color:#ae81ff&#34;&gt;3472&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ci &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(next, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;span style=&#34;color:#ae81ff&#34;&gt;3473&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(next, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;span style=&#34;color:#ae81ff&#34;&gt;3474&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
                 &lt;span style=&#34;color:#75715e&#34;&gt;// if the callinfo is &amp;#34;simple&amp;#34;, with zero arguments,
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;                 &lt;span style=&#34;color:#75715e&#34;&gt;// and there isn&amp;#39;t a block provided(?), and the method id (mid) is `freeze`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;                 &lt;span style=&#34;color:#75715e&#34;&gt;// which is represented by `idFreeze`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3475&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idFreeze) {
                     &lt;span style=&#34;color:#75715e&#34;&gt;// change the instruction to `opt_ary_freeze`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;                iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
                     &lt;span style=&#34;color:#75715e&#34;&gt;// remove the `send` instruction, we don&amp;#39;t need it anymore
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3481&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;                ELEM_REMOVE(next);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now i&amp;rsquo;ll use &lt;code&gt;lldb&lt;/code&gt; to see where this code runs in relation to our prism compilation. In CRuby, to debug you run &lt;code&gt;make lldb-ruby&lt;/code&gt; instead of &lt;code&gt;make runruby&lt;/code&gt;. You&amp;rsquo;ll see some setup code run, and then you&amp;rsquo;ll be left at a prompt, prefixed by &lt;code&gt;(lldb)&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&amp;gt; make lldb-ruby
lldb  -o &amp;#39;command script import -r ../misc/lldb_cruby.py&amp;#39; ruby --  ../test.rb
(lldb) target create &amp;#34;ruby&amp;#34;
Current executable set to &amp;#39;/Users/johncamara/Projects/ruby/build/ruby&amp;#39; (arm64).
(lldb) settings set -- target.run-args  &amp;#34;../test.rb&amp;#34;
(lldb) command script import -r ../misc/lldb_cruby.py
lldb scripts for ruby has been installed.
(lldb)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, we haven&amp;rsquo;t actually run anything. We can now set our breakpoint, then run the program. I&amp;rsquo;ll add a breakpoint right after all &lt;code&gt;if&lt;/code&gt; statements have succeeded:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;(lldb) break set --file compile.c --line 3476
Breakpoint 1: where = ruby`iseq_peephole_optimize + 2276 at compile.c:3476:17
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With our breakpoint set, we call &lt;code&gt;run&lt;/code&gt; to run the program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;(lldb) run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll see something like the following. It ran the program until it hit our breakpoint, right after identifying a frozen array literal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) run
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;50923&lt;/span&gt; launched: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;ruby&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;build&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt; (arm64)
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;50923&lt;/span&gt; stopped
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3473&lt;/span&gt;             &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(next, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3474&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3475&lt;/span&gt;             &lt;span style=&#34;color:#a6e22e&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idFreeze) {
&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3477&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3478&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; compile_data_calloc2(iseq, iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(VALUE));
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3479&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_cArray_empty_frozen;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I want to see where we are in relation to all our prism compilation code. We can use &lt;code&gt;bt&lt;/code&gt; to get the backtrace:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) bt
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;
  &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;29&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;4352&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_setup_insn(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1619&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;10139&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt_try(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1029&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_protect(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1033&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1082&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_new_child_iseq(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1271&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;9458&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;9911&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_compile_scope_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;6598&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;9784&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;10122&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt_try(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1029&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_protect(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1033&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1082&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_top(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;906&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;load_iseq_eval(...) at load.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;756&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;require_internal(...) at load.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1296&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;21&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;19&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_require_string_internal(...) at load.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1402&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_require_string(...) at load.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1388&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;21&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_f_require(...) at load.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1029&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ractor_safe_call_cfunc_1(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3624&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;23&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_cfunc_with_frame_(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3801&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;24&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_cfunc_with_frame(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3847&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;25&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_cfunc_other(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3873&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_cfunc(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3955&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;27&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_method_each_type(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;4779&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;28&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_method(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;4916&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;29&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_call_general(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;4949&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_sendish(...) at vm_insnhelper.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;5968&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;15&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;31&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;vm_exec_core(...) at insns.def:&lt;span style=&#34;color:#ae81ff&#34;&gt;898&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_vm_exec(...) at vm.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2595&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;22&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;33&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_iseq_eval(...) at vm.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2850&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;34&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_load_with_builtin_functions(...) at builtin.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;54&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;35&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;Init_builtin_features at builtin.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;74&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;36&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_init_prelude at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1750&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;37&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_opt_init(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1811&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;prism_script(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2215&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;13&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;39&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2538&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3169&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;41&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_options(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;117&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;42&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;43&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;43&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;68&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whoa. That thing is huge! This is not the backtrace I was expecting! Seems like I missed a codepath in my earlier explorations. I got it right, up until &lt;code&gt;prism_script&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;which calls &lt;code&gt;rb_main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;which calls &lt;code&gt;ruby_options&lt;/code&gt;, then &lt;code&gt;ruby_process_options&lt;/code&gt;, then &lt;code&gt;process_options&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;which calls &lt;code&gt;prism_script&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The next instruction I expected was &lt;code&gt;pm_iseq_new_main&lt;/code&gt;, but instead we head into &lt;code&gt;ruby_opt_init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;which calls &lt;code&gt;Init_builtin_features&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This path seems to go through some gem preloading logic, which is why we see the &lt;code&gt;rb_require&lt;/code&gt; calls:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;Init_builtin_features&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
{
    rb_load_with_builtin_functions(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gem_prelude&amp;#34;&lt;/span&gt;, NULL);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default CRuby loads &lt;code&gt;gem_prelude&lt;/code&gt;, which lives in &lt;code&gt;ruby/gem_prelude.rb&lt;/code&gt;. Here&amp;rsquo;s that file, shortened for brevity:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rubygems&amp;#39;&lt;/span&gt;
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;error_highlight&amp;#39;&lt;/span&gt;
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;did_you_mean&amp;#39;&lt;/span&gt;
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;syntax_suggest/core_ext&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;compiling-on-the-fly&#34;&gt;Compiling on-the-fly&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s something i&amp;rsquo;ve learned here that seems obvious in hindsight, but I hadn&amp;rsquo;t considered. Ruby will only compile what is actually &lt;em&gt;loaded&lt;/em&gt;, and only at the point it gets loaded. If I never load a particular piece of code, it never gets compiled. Or if I defer loading it until later, it does not get compiled until later.&lt;/p&gt;
&lt;p&gt;We can actually demonstrate this by deferring a require:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;

require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we run this this using &lt;code&gt;make lldb-ruby&lt;/code&gt;, we can see the delayed compilation in action:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;(lldb) break set --file ruby.c --line 2616
(lldb) run
// hits our prism compile code
(lldb) next
(lldb) break set --file compile.c --line 3476
(lldb) continue
// waits 10 seconds, then compiles the contents of &amp;#34;net/http&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;getting-to-our-testrb-file&#34;&gt;Getting to our test.rb file&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;d rather see just my code in &lt;code&gt;test.rb&lt;/code&gt; get compiled, so I&amp;rsquo;m going to set a breakpoint directly on &lt;code&gt;pm_iseq_new_main&lt;/code&gt;, which for me is in &lt;code&gt;ruby.c&lt;/code&gt; on line &lt;code&gt;2616&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; set &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;file ruby.c &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;line &lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;
(lldb) run
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;32534&lt;/span&gt; launched: &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;ruby&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;build&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt; (arm64)
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;32534&lt;/span&gt; stopped
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2613&lt;/span&gt;         &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;result.ast) {
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2614&lt;/span&gt;             pm_parse_result_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pm &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;result.prism;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2615&lt;/span&gt;             &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; error_state;
&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;             iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm_iseq_new_main(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;pm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;node, opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;script_name, path, parent, optimize, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;error_state);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2617&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2618&lt;/span&gt;             &lt;span style=&#34;color:#a6e22e&#34;&gt;pm_parse_result_free&lt;/span&gt;(pm);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;2619&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when we run the backtrace I am seeing what I expected, because we&amp;rsquo;ve skipped the &lt;code&gt;gem_prelude&lt;/code&gt; compilation. This is the exact flow I walked through in &lt;a href=&#34;https://jpcamara.com/2024/12/22/finding-the-compiler.html&#34;&gt;part 2&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) bt
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;1.1&lt;/span&gt;
  &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3169&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_options(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;117&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;43&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;68&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here, we can set our &lt;code&gt;iseq_peephole_optimize&lt;/code&gt; breakpoint and see only our specific code get compiled. Since we&amp;rsquo;re already in the running program, we call &lt;code&gt;continue&lt;/code&gt; to keep executing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; set &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;file compile.c &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;line &lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;
Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; where &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2276&lt;/span&gt; at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;55336&lt;/span&gt; resuming
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;55336&lt;/span&gt; stopped
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;2.1&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize() at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3473&lt;/span&gt;             &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(next, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3474&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3475&lt;/span&gt;             &lt;span style=&#34;color:#a6e22e&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idFreeze) {
&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3477&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3478&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; compile_data_calloc2(iseq, iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operand_size, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(VALUE));
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3479&lt;/span&gt;                 iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;operands[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_cArray_empty_frozen;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we call &lt;code&gt;bt&lt;/code&gt; from here to get the backtrace, we finally see the connection between &lt;code&gt;prism_compile.c&lt;/code&gt; and &lt;code&gt;compile.c&lt;/code&gt;. &lt;code&gt;pm_iseq_compile_node&lt;/code&gt; calls &lt;code&gt;iseq_setup_insn&lt;/code&gt;, which runs the optimization logic. In the previous post, I saw &lt;code&gt;iseq_setup_insn&lt;/code&gt;, but I didn&amp;rsquo;t know what it meant or what it did. Now we know. This is what Kevin Newton referred to earlier: specialization comes after compilation. Prism compiles the node in the standard way, then the peephole optimization layer - the specialization - is applied after:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) bt
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;2.1&lt;/span&gt;
  &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;4352&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_setup_insn(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1619&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_compile_node(...) at prism_compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;10139&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt_try(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1029&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_protect(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1033&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_with_opt(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;1082&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;pm_iseq_new_main(...) at iseq.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;930&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_process_options(...) at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3169&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;ruby_options(...) at eval.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;117&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;16&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;11&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;rb_main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;43&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;26&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;main(...) at main.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;68&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;12&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here, we can inspect and see the current instruction using &lt;code&gt;expr&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) expr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;(iobj)
(INSN) &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
  link &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
    type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ISEQ_ELEMENT_INSN
    next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x000000011f6568d0&lt;/span&gt;
    prev &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x000000011f656850&lt;/span&gt;
  }
  insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; YARVINSN_newarray
  operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
  sc_state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x000000011f640118&lt;/span&gt;
  insn_info &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (line_no &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, node_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, events &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We see that &lt;code&gt;iobj&lt;/code&gt; contains a link to a subsequent instruction, as well as an &lt;code&gt;insn_id&lt;/code&gt; and some other metadata. The instruction is currently &lt;code&gt;YARVINSN_newarray&lt;/code&gt;. If we run &lt;code&gt;next&lt;/code&gt;, that should run &lt;code&gt;iobj-&amp;gt;insn_id = BIN(opt_ary_freeze);&lt;/code&gt;, and our instruction should change:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) next
(lldb) expr &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;(iobj)
(INSN) &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; YARVINSN_opt_ary_freeze
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It does! The instruction was changed from &lt;code&gt;newarray&lt;/code&gt; to &lt;code&gt;opt_ary_freeze&lt;/code&gt;! The optimization is at least partially complete (i&amp;rsquo;m not sure if more is involved, yet).&lt;/p&gt;
&lt;h3 id=&#34;making-one-small-step-towards-opt_respond_to&#34;&gt;Making one small step towards &lt;code&gt;opt_respond_to&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is already the longest and densest post in the series. But i&amp;rsquo;d love to make some actual progress towards a new instruction. Let&amp;rsquo;s pattern match on &lt;code&gt;respond_to?&lt;/code&gt; in the peephole optimizer.&lt;/p&gt;
&lt;p&gt;Here is our sample program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run with &lt;code&gt;RUNOPT0=--dump=insns make runruby&lt;/code&gt;, we get the following instructions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;== disasm: #&amp;lt;ISeq:&amp;lt;main&amp;gt;./test.rb:1 (1,0)-(1,76)&amp;gt;
0000 getglobal                              :$stdout                  (   1)[Li]
0002 putobject                              :write
0004 opt_send_without_block                 &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
0006 branchunless                           14
0008 putself
0009 putchilledstring                       &amp;#34;Did you know you can write to $stdout?&amp;#34;
0011 opt_send_without_block                 &amp;lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&amp;gt;
0013 leave
0014 putnil
0015 leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I want to match on this line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;0004 opt_send_without_block       &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s my attempt. I&amp;rsquo;m going to copy what the &lt;code&gt;newarray&lt;/code&gt; &lt;code&gt;freeze&lt;/code&gt; optimization is doing, and just try changing a few things to match my example. Right underneath the code we&amp;rsquo;ve been debugging for &lt;code&gt;newarray&lt;/code&gt;, i&amp;rsquo;m adding this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// If the instruction is `send_without_block`, ie `0004 opt_send_without_block`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (IS_INSN_ID(iobj, send_without_block)) {
    &lt;span style=&#34;color:#75715e&#34;&gt;// Pull the same info the `newarray` optimization does
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ci &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; rb_callinfo &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);

    &lt;span style=&#34;color:#75715e&#34;&gt;// &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 1. We have ARGS_SIMPLE, which is probably what `vm_ci_simple(ci)` checks for
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 2. We have argc:1, which should match `vm_ci_argc(ci) == 1`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 3. We send without a block, hence blockiseq == NULL
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 4. The method id (mid) for `vm_ci_mid(ci)` matches `idRespond_to`. I searched around for names
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//    that seemed similar to idFreeze, but replacing `idFreeze` with `idRespond` and found `idRespond_to`
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idRespond_to) {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now i&amp;rsquo;ll follow the same debugging as before, but i&amp;rsquo;ll add a breakpoint in &lt;code&gt;compile.c&lt;/code&gt; where I added my new code. Specifically, I&amp;rsquo;m setting a breakpoint at the &lt;code&gt;int i = 0;&lt;/code&gt; so I am inside the &lt;code&gt;if&lt;/code&gt; statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; set &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;file ruby.c &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;line &lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;
Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; where &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;process_options &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4068&lt;/span&gt; at ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;38&lt;/span&gt;
(lldb) run
(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; set &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;file compile.c &lt;span style=&#34;color:#f92672&#34;&gt;--&lt;/span&gt;line &lt;span style=&#34;color:#ae81ff&#34;&gt;3491&lt;/span&gt;
Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; where &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2536&lt;/span&gt; at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3491&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
(lldb) &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;61925&lt;/span&gt; resuming
Process &lt;span style=&#34;color:#ae81ff&#34;&gt;61925&lt;/span&gt; stopped
&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;com.apple.main&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;thread&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;, stop reason &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;2.1&lt;/span&gt;
    frame &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;`&lt;/span&gt;iseq_peephole_optimize(...) at compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3491&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;17&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3488&lt;/span&gt;         &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)OPERAND_AT(iobj, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3489&lt;/span&gt;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3490&lt;/span&gt;         &lt;span style=&#34;color:#a6e22e&#34;&gt;if&lt;/span&gt; (vm_ci_simple(ci) &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_argc(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; blockiseq &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; NULL &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; vm_ci_mid(ci) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; idRespond_to) {
&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3491&lt;/span&gt;             &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3492&lt;/span&gt;         }
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3493&lt;/span&gt;     }
   &lt;span style=&#34;color:#ae81ff&#34;&gt;3494&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I think it worked! It pattern matched on the characteristics of the &lt;code&gt;respond_to?&lt;/code&gt; call, and hit the breakpoint set on &lt;code&gt;int i = 0;&lt;/code&gt;. It&amp;rsquo;s a tiny step, but it&amp;rsquo;s a first step in the direction of adding the optimization.&lt;/p&gt;
&lt;h3 id=&#34;using-gdb&#34;&gt;Using &lt;code&gt;gdb&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;For anyone wanting to do the same work using &lt;code&gt;gdb&lt;/code&gt;, it&amp;rsquo;s pretty similar. Let&amp;rsquo;s start off by creating a &lt;code&gt;breakpoints.gdb&lt;/code&gt; file in the root of your project. This will set you up with your initial breakpoint, similar to how we ran &lt;code&gt;lldb&lt;/code&gt;, and set the breakpoint before calling &lt;code&gt;run&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;break ruby.c:2616
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you run &lt;code&gt;make gdb-ruby&lt;/code&gt;, you can use the same backtrace command, &lt;code&gt;bt&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; make gdb&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;ruby
Thread &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ruby&amp;#34;&lt;/span&gt; hit Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, process_options (...) at ..&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;ruby.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;
&lt;span style=&#34;color:#ae81ff&#34;&gt;2616&lt;/span&gt;	            iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm_iseq_new_main(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;pm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;node, opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;script_name, path, parent, optimize, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;error_state);
(gdb) bt
&lt;span style=&#34;color:#75715e&#34;&gt;#0  process_options (...) at ../ruby.c:2616
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#1  in ruby_process_options (...) at ../ruby.c:3169
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#2  in ruby_options (...) at ../eval.c:117
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#3  in rb_main (...) at ../main.c:43
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#4  in main (...) at ../main.c:68
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;(gdb)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here, you can set your next breakpoint so that you can see the compilation solely for the &lt;code&gt;newarray&lt;/code&gt; instruction from our &lt;code&gt;test.rb&lt;/code&gt; program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(gdb) &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;
Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; at &lt;span style=&#34;color:#ae81ff&#34;&gt;0xaaaabaa22f14&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; file ..&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;compile.c, line &lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;
(gdb) &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
Continuing.

Thread &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ruby&amp;#34;&lt;/span&gt; hit Breakpoint &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, iseq_peephole_optimize (...) at ..&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;compile.c:&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;
&lt;span style=&#34;color:#ae81ff&#34;&gt;3476&lt;/span&gt;	                iobj&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; BIN(opt_ary_freeze);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Similar to the &lt;code&gt;lldb&lt;/code&gt; command &lt;code&gt;expr&lt;/code&gt;, we can inspect the contents of locals using &lt;code&gt;p&lt;/code&gt; or &lt;code&gt;print&lt;/code&gt; in &lt;code&gt;gdb&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;(gdb) p &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;(iobj)
&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {link &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ISEQ_ELEMENT_INSN, next &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xaaaace797ef0&lt;/span&gt;, prev &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xaaaace797e70&lt;/span&gt;}, insn_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; YARVINSN_newarray,
  operand_size &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, sc_state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, operands &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0xaaaace796ac8&lt;/span&gt;, insn_info &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {line_no &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, node_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, events &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;}}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;finishing-up&#34;&gt;Finishing up&lt;/h3&gt;
&lt;p&gt;Ok, this went pretty long. Good on you for sticking in there with me! We&amp;rsquo;ve found the optimizer, and we&amp;rsquo;ve pattern matched our way to a &lt;code&gt;respond_to?&lt;/code&gt; call. Next, we need to add the new instruction definition and try to actually replace the &lt;code&gt;send&lt;/code&gt; with our new instruction. See you next time! 👋🏼&lt;/p&gt;
</description>
      <source:markdown>In [The Ruby Syntax Holy Grail: adding `opt_respond_to` to the Ruby VM, part 3](https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html), I found what I referred to as the &#34;Holy Grail&#34; of Ruby syntax. I&#39;m way overstating it, but it&#39;s a readable, sequential way of viewing how a large portion of the Ruby syntax is compiled. Here&#39;s a snippet of it as a reminder:


```c
// prism_compile.c
static void
pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node)
{
    const pm_parser_t *parser = scope_node-&gt;parser;
    //...
    switch (PM_NODE_TYPE(node)) {
      //...
      case PM_ARRAY_NODE: {
        // [foo, bar, baz]
        // ^^^^^^^^^^^^^^^
        const pm_array_node_t *cast = (const pm_array_node_t *) node;
        pm_compile_array_node(iseq, (const pm_node_t *) cast, &amp;cast-&gt;elements, &amp;location, ret, popped, scope_node);
        return;
      }
      //...
      case PM_MODULE_NODE: {
        // module Foo; end
        //...
      }
      //...
}
```
The file that code lives in, `prism_compile.c`, is _enormous_. `pm_compile_node` itself is 1800+ lines, and the overall file is 11 _thousand_ lines. It&#39;s daunting to say the least, but there are some obvious directions I can ignore - i&#39;m trying to optimize a method call to `respond_to?`, so I can sidestep a majority of the Ruby syntax.


Still, where I do go, specifically?


### Sage wisdom


Helpfully, I got two identical sets of direction based on [part 3](https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html). One from [Kevin Newton](https://x.com/kddnewton), creator of [Prism](https://github.com/ruby/prism):


&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-26-at-12.15.03pm.png&#34; width=&#34;600&#34; height=&#34;143&#34; alt=&#34;&#34;&gt;


&gt; https://x.com/kddnewton/status/1872280281409105925?s=46


And one from [byroot](https://bsky.app/profile/byroot.bsky.social), who inspired this whole series:


&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-26-at-12.24.24pm.png&#34; width=&#34;600&#34; height=&#34;211&#34; alt=&#34;&#34;&gt;


&gt; https://bsky.app/profile/byroot.bsky.social/post/3le6xypzykc2x


I don&#39;t want to jump to conclusions, but I think I need to look at the peephole optimizer 😆.


And exactly what _is_ a &#34;peephole optimizer&#34;? Kevin described the process as &#34;specialization comes after compilation&#34;. From [Wikipedia](https://en.wikipedia.org/wiki/Peephole_optimization):


&gt; Peephole optimization is an optimization technique performed on a small set of compiler-generated instructions, known as a peephole or window, that involves replacing the instructions with a logically equivalent set that has better performance.
&gt; https://en.wikipedia.org/wiki/Peephole_optimization


This seems to fit my goal pretty well. I want to replace the current `opt_send_without_block` instruction with a specialized `opt_respond_to` instruction, optimized for `respond_to?` method calls.


### Finding the optimizer


So where are peephole optimizations happening in CRuby today? In [Étienne](https://github.com/etiennebarrie)&#39;s [PR](https://github.com/ruby/ruby/pull/11406), he added optimization code to a function called... `iseq_peephole_optimize`. A little on the nose, don&#39;t you think? Kevin&#39;s comment _also_ mentioned `iseq_peephole_optimize` - seems like the winner.


I want to make the link between `iseq_peephole_optimize` and where we left off at `pm_compile_node`. Let&#39;s dig into some code!


### Disassembling an existing optimization


I&#39;m going to use Étienne&#39;s frozen array optimization to get to the optimizer and see how it relates. If you want to follow along, start with the setup instructions from [part 3](https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html#getting-your-own-environment-setup).


His optimization only applies to array and hash literals being frozen. So we&#39;ll write a teensy Ruby program to demonstrate, and put it in `test.rb` at the root of our CRuby project:


```rb
# test.rb
pp [].freeze
```
The best way to run `test.rb` here is to use `make`. It will not only run the file, but also make sure things like C files get recompiled as necessary when you make changes. Let&#39;s run our file, but dump the instructions it would generate for the Ruby VM:


```
RUNOPT0=--dump=insns make runruby
```
`RUNOPT0` lets us add an option to the `ruby` call, so it&#39;s _effectively_ `ruby --dump=insns test.rb`. Here&#39;s the instructions we see - we can confirm that we are getting the optimized `opt_ary_freeze` instruction from Étienne PR:


```text
== disasm: #&lt;ISeq:&lt;main&gt;./test.rb:3 (3,0)-(3,12)&gt;
0000 putself                      (   3)[Li]
0001 opt_ary_freeze               [], &lt;calldata!mid:freeze, argc:0, ARGS_SIMPLE&gt;
0004 opt_send_without_block       &lt;calldata!mid:pp, argc:1, FCALL|ARGS_SIMPLE&gt;
0006 leave
```

You never know what code is truly doing until you run it. So far, I&#39;ve just been reading and navigating the CRuby source. `iseq_peephole_optimize` lives in `compile.c` - let&#39;s set a breakpoint and take a look 🕵🏼‍♂️.


### Using the debugger


We can debug C code in CRuby _almost_ as easily as we can use a `debugger`/`binding.pry`.


For MacOS, you can use [`lldb`](https://lldb.llvm.org/), and for Docker/Linux, you can use [`gdb`](https://sourceware.org/gdb/). I&#39;m going to do everything in `lldb` to start, but I&#39;ll show some equivalent commands for `gdb` after.


Let&#39;s start by looking at the peephole optimization code for `[].freeze`, inside of `iseq_peephole_optimize`. I&#39;ll add comments above each line to explain what I think it&#39;s doing:


```c
// compile.c
static int
iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
{
         // ...
         // if the instruction is a `newarray` of zero length
3469:    if (IS_INSN_ID(iobj, newarray) &amp;&amp; iobj-&gt;operands[0] == INT2FIX(0)) {
             // grab the next element after the current instruction
3470:        LINK_ELEMENT *next = iobj-&gt;link.next;
             // if `next` is an instruction, and the instruction is `send`
3471:        if (IS_INSN(next) &amp;&amp; (IS_INSN_ID(next, send))) {
3472:            const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(next, 0);
3473:            const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1);
3474:
                 // if the callinfo is &#34;simple&#34;, with zero arguments,
                 // and there isn&#39;t a block provided(?), and the method id (mid) is `freeze`
                 // which is represented by `idFreeze`
3475:            if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 0 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idFreeze) {
                     // change the instruction to `opt_ary_freeze`
3476:                iobj-&gt;insn_id = BIN(opt_ary_freeze);
                     // remove the `send` instruction, we don&#39;t need it anymore
3481:                ELEM_REMOVE(next);
```
Now i&#39;ll use `lldb` to see where this code runs in relation to our prism compilation. In CRuby, to debug you run `make lldb-ruby` instead of `make runruby`. You&#39;ll see some setup code run, and then you&#39;ll be left at a prompt, prefixed by `(lldb)`:


```text
&gt; make lldb-ruby
lldb  -o &#39;command script import -r ../misc/lldb_cruby.py&#39; ruby --  ../test.rb
(lldb) target create &#34;ruby&#34;
Current executable set to &#39;/Users/johncamara/Projects/ruby/build/ruby&#39; (arm64).
(lldb) settings set -- target.run-args  &#34;../test.rb&#34;
(lldb) command script import -r ../misc/lldb_cruby.py
lldb scripts for ruby has been installed.
(lldb)
```
At this point, we haven&#39;t actually run anything. We can now set our breakpoint, then run the program. I&#39;ll add a breakpoint right after all `if` statements have succeeded:


```text
(lldb) break set --file compile.c --line 3476
Breakpoint 1: where = ruby`iseq_peephole_optimize + 2276 at compile.c:3476:17
```
With our breakpoint set, we call `run` to run the program:


```text
(lldb) run
```
You&#39;ll see something like the following. It ran the program until it hit our breakpoint, right after identifying a frozen array literal:


```c
(lldb) run
Process 50923 launched: &#39;/ruby/build/ruby&#39; (arm64)
Process 50923 stopped
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 1.1
    frame #0: ruby`iseq_peephole_optimize(...) at compile.c:3476:17
   3473             const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1);
   3474
   3475             if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 0 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idFreeze) {
-&gt; 3476                 iobj-&gt;insn_id = BIN(opt_ary_freeze);
   3477                 iobj-&gt;operand_size = 2;
   3478                 iobj-&gt;operands = compile_data_calloc2(iseq, iobj-&gt;operand_size, sizeof(VALUE));
   3479                 iobj-&gt;operands[0] = rb_cArray_empty_frozen;
```
I want to see where we are in relation to all our prism compilation code. We can use `bt` to get the backtrace:


```c
(lldb) bt
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 1.1
  * frame #0: ruby`iseq_peephole_optimize(...) at compile.c:3476:29
    frame #1: ruby`iseq_optimize(...) at compile.c:4352:17
    frame #2: ruby`iseq_setup_insn(...) at compile.c:1619:5
    frame #3: ruby`pm_iseq_compile_node(...) at prism_compile.c:10139:5
    frame #4: ruby`pm_iseq_new_with_opt_try(...) at iseq.c:1029:5
    frame #5: ruby`rb_protect(...) at eval.c:1033:18
    frame #6: ruby`pm_iseq_new_with_opt(...) at iseq.c:1082:5
    frame #7: ruby`pm_new_child_iseq(...) at prism_compile.c:1271:27
    frame #8: ruby`pm_compile_node(...) at prism_compile.c:9458:40
    frame #9: ruby`pm_compile_node(...) at prism_compile.c:9911:17
    frame #10: ruby`pm_compile_scope_node(...) at prism_compile.c:6598:13
    frame #11: ruby`pm_compile_node(...) at prism_compile.c:9784:9
    frame #12: ruby`pm_iseq_compile_node(...) at prism_compile.c:10122:9
    frame #13: ruby`pm_iseq_new_with_opt_try(...) at iseq.c:1029:5
    frame #14: ruby`rb_protect(...) at eval.c:1033:18
    frame #15: ruby`pm_iseq_new_with_opt(...) at iseq.c:1082:5
    frame #16: ruby`pm_iseq_new_top(...) at iseq.c:906:12
    frame #17: ruby`load_iseq_eval(...) at load.c:756:24
    frame #18: ruby`require_internal(...) at load.c:1296:21
    frame #19: ruby`rb_require_string_internal(...) at load.c:1402:22
    frame #20: ruby`rb_require_string(...) at load.c:1388:12
    frame #21: ruby`rb_f_require(...) at load.c:1029:12
    frame #22: ruby`ractor_safe_call_cfunc_1(...) at vm_insnhelper.c:3624:12
    frame #23: ruby`vm_call_cfunc_with_frame_(...) at vm_insnhelper.c:3801:11
    frame #24: ruby`vm_call_cfunc_with_frame(...) at vm_insnhelper.c:3847:12
    frame #25: ruby`vm_call_cfunc_other(...) at vm_insnhelper.c:3873:16
    frame #26: ruby`vm_call_cfunc(...) at vm_insnhelper.c:3955:12
    frame #27: ruby`vm_call_method_each_type(...) at vm_insnhelper.c:4779:16
    frame #28: ruby`vm_call_method(...) at vm_insnhelper.c:4916:20
    frame #29: ruby`vm_call_general(...) at vm_insnhelper.c:4949:12
    frame #30: ruby`vm_sendish(...) at vm_insnhelper.c:5968:15
    frame #31: ruby`vm_exec_core(...) at insns.def:898:11
    frame #32: ruby`rb_vm_exec(...) at vm.c:2595:22
    frame #33: ruby`rb_iseq_eval(...) at vm.c:2850:11
    frame #34: ruby`rb_load_with_builtin_functions(...) at builtin.c:54:5
    frame #35: ruby`Init_builtin_features at builtin.c:74:5
    frame #36: ruby`ruby_init_prelude at ruby.c:1750:5
    frame #37: ruby`ruby_opt_init(...) at ruby.c:1811:5
    frame #38: ruby`prism_script(...) at ruby.c:2215:13
    frame #39: ruby`process_options(...) at ruby.c:2538:9
    frame #40: ruby`ruby_process_options(...) at ruby.c:3169:12
    frame #41: ruby`ruby_options(...) at eval.c:117:16
    frame #42: ruby`rb_main(...) at main.c:43:26
    frame #43: ruby`main(...) at main.c:68:12
```
Whoa. That thing is huge! This is not the backtrace I was expecting! Seems like I missed a codepath in my earlier explorations. I got it right, up until `prism_script`:


- `main`
- which calls `rb_main`
- which calls `ruby_options`, then `ruby_process_options`, then `process_options`
- which calls `prism_script`
- The next instruction I expected was `pm_iseq_new_main`, but instead we head into `ruby_opt_init`
- which calls `Init_builtin_features`


This path seems to go through some gem preloading logic, which is why we see the `rb_require` calls:


```c
void
Init_builtin_features(void)
{
    rb_load_with_builtin_functions(&#34;gem_prelude&#34;, NULL);
}
```
By default CRuby loads `gem_prelude`, which lives in `ruby/gem_prelude.rb`. Here&#39;s that file, shortened for brevity:


```rb
require &#39;rubygems&#39;
require &#39;error_highlight&#39;
require &#39;did_you_mean&#39;
require &#39;syntax_suggest/core_ext&#39;
```
### Compiling on-the-fly


There&#39;s something i&#39;ve learned here that seems obvious in hindsight, but I hadn&#39;t considered. Ruby will only compile what is actually _loaded_, and only at the point it gets loaded. If I never load a particular piece of code, it never gets compiled. Or if I defer loading it until later, it does not get compiled until later.


We can actually demonstrate this by deferring a require:


```rb
sleep 10

require &#34;net/http&#34;
```
If we run this this using `make lldb-ruby`, we can see the delayed compilation in action:


```text
(lldb) break set --file ruby.c --line 2616
(lldb) run
// hits our prism compile code
(lldb) next
(lldb) break set --file compile.c --line 3476
(lldb) continue
// waits 10 seconds, then compiles the contents of &#34;net/http&#34;
```
### Getting to our test.rb file


I&#39;d rather see just my code in `test.rb` get compiled, so I&#39;m going to set a breakpoint directly on `pm_iseq_new_main`, which for me is in `ruby.c` on line `2616`:


```c
(lldb) break set --file ruby.c --line 2616
(lldb) run
Process 32534 launched: &#39;/ruby/build/ruby&#39; (arm64)
Process 32534 stopped
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 1.1
    frame #0: ruby`process_options(...) at ruby.c:2616:38
   2613         if (!result.ast) {
   2614             pm_parse_result_t *pm = &amp;result.prism;
   2615             int error_state;
-&gt; 2616             iseq = pm_iseq_new_main(&amp;pm-&gt;node, opt-&gt;script_name, path, parent, optimize, &amp;error_state);
   2617
   2618             pm_parse_result_free(pm);
   2619
```
Now when we run the backtrace I am seeing what I expected, because we&#39;ve skipped the `gem_prelude` compilation. This is the exact flow I walked through in [part 2](https://jpcamara.com/2024/12/22/finding-the-compiler.html):


```c
(lldb) bt
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 1.1
  * frame #0: ruby`process_options(...) at ruby.c:2616:38
    frame #1: ruby`ruby_process_options(...) at ruby.c:3169:12
    frame #2: ruby`ruby_options(...) at eval.c:117:16
    frame #3: ruby`rb_main(...) at main.c:43:26
    frame #4: ruby`main(...) at main.c:68:12
```
From here, we can set our `iseq_peephole_optimize` breakpoint and see only our specific code get compiled. Since we&#39;re already in the running program, we call `continue` to keep executing:


```c
(lldb) break set --file compile.c --line 3476
Breakpoint 2: where = ruby`iseq_peephole_optimize + 2276 at compile.c:3476:17
(lldb) continue
Process 55336 resuming
Process 55336 stopped
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 2.1
    frame #0: ruby`iseq_peephole_optimize() at compile.c:3476:17
   3473             const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(next, 1);
   3474
   3475             if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 0 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idFreeze) {
-&gt; 3476                 iobj-&gt;insn_id = BIN(opt_ary_freeze);
   3477                 iobj-&gt;operand_size = 2;
   3478                 iobj-&gt;operands = compile_data_calloc2(iseq, iobj-&gt;operand_size, sizeof(VALUE));
   3479                 iobj-&gt;operands[0] = rb_cArray_empty_frozen;
```
If we call `bt` from here to get the backtrace, we finally see the connection between `prism_compile.c` and `compile.c`. `pm_iseq_compile_node` calls `iseq_setup_insn`, which runs the optimization logic. In the previous post, I saw `iseq_setup_insn`, but I didn&#39;t know what it meant or what it did. Now we know. This is what Kevin Newton referred to earlier: specialization comes after compilation. Prism compiles the node in the standard way, then the peephole optimization layer - the specialization - is applied after:


```c
(lldb) bt
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 2.1
  * frame #0: ruby`iseq_peephole_optimize(...) at compile.c:3476:17
    frame #1: ruby`iseq_optimize(...) at compile.c:4352:17
    frame #2: ruby`iseq_setup_insn(...) at compile.c:1619:5
    frame #3: ruby`pm_iseq_compile_node(...) at prism_compile.c:10139:5
    frame #4: ruby`pm_iseq_new_with_opt_try(...) at iseq.c:1029:5
    frame #5: ruby`rb_protect(...) at eval.c:1033:18
    frame #6: ruby`pm_iseq_new_with_opt(...) at iseq.c:1082:5
    frame #7: ruby`pm_iseq_new_main(...) at iseq.c:930:12
    frame #8: ruby`process_options(...) at ruby.c:2616:20
    frame #9: ruby`ruby_process_options(...) at ruby.c:3169:12
    frame #10: ruby`ruby_options(...) at eval.c:117:16
    frame #11: ruby`rb_main(...) at main.c:43:26
    frame #12: ruby`main(...) at main.c:68:12
```
From here, we can inspect and see the current instruction using `expr`:


```c
(lldb) expr *(iobj)
(INSN) $4 = {
  link = {
    type = ISEQ_ELEMENT_INSN
    next = 0x000000011f6568d0
    prev = 0x000000011f656850
  }
  insn_id = YARVINSN_newarray
  operand_size = 1
  sc_state = 0
  operands = 0x000000011f640118
  insn_info = (line_no = 1, node_id = 3, events = 0)
}
```
We see that `iobj` contains a link to a subsequent instruction, as well as an `insn_id` and some other metadata. The instruction is currently `YARVINSN_newarray`. If we run `next`, that should run `iobj-&gt;insn_id = BIN(opt_ary_freeze);`, and our instruction should change:


```c
(lldb) next
(lldb) expr *(iobj)
(INSN) $5 = {
  //...
  insn_id = YARVINSN_opt_ary_freeze
  //...
}
```
It does! The instruction was changed from `newarray` to `opt_ary_freeze`! The optimization is at least partially complete (i&#39;m not sure if more is involved, yet).


### Making one small step towards `opt_respond_to`


This is already the longest and densest post in the series. But i&#39;d love to make some actual progress towards a new instruction. Let&#39;s pattern match on `respond_to?` in the peephole optimizer.


Here is our sample program:


```ruby
puts &#34;Did you know you can write to $stdout?&#34; if $stdout.respond_to?(:write)
```
Run with `RUNOPT0=--dump=insns make runruby`, we get the following instructions:


```text
== disasm: #&lt;ISeq:&lt;main&gt;./test.rb:1 (1,0)-(1,76)&gt;
0000 getglobal                              :$stdout                  (   1)[Li]
0002 putobject                              :write
0004 opt_send_without_block                 &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
0006 branchunless                           14
0008 putself
0009 putchilledstring                       &#34;Did you know you can write to $stdout?&#34;
0011 opt_send_without_block                 &lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&gt;
0013 leave
0014 putnil
0015 leave
```
I want to match on this line:


```text
0004 opt_send_without_block       &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
```
Here&#39;s my attempt. I&#39;m going to copy what the `newarray` `freeze` optimization is doing, and just try changing a few things to match my example. Right underneath the code we&#39;ve been debugging for `newarray`, i&#39;m adding this:


```c
// If the instruction is `send_without_block`, ie `0004 opt_send_without_block`
if (IS_INSN_ID(iobj, send_without_block)) {
    // Pull the same info the `newarray` optimization does
    const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0);
    const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(iobj, 1);

    // &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
    // 1. We have ARGS_SIMPLE, which is probably what `vm_ci_simple(ci)` checks for
    // 2. We have argc:1, which should match `vm_ci_argc(ci) == 1`
    // 3. We send without a block, hence blockiseq == NULL
    // 4. The method id (mid) for `vm_ci_mid(ci)` matches `idRespond_to`. I searched around for names
    //    that seemed similar to idFreeze, but replacing `idFreeze` with `idRespond` and found `idRespond_to`
    if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 1 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idRespond_to) {
        int i = 0;
    }
}
```
Now i&#39;ll follow the same debugging as before, but i&#39;ll add a breakpoint in `compile.c` where I added my new code. Specifically, I&#39;m setting a breakpoint at the `int i = 0;` so I am inside the `if` statement:


```c
(lldb) break set --file ruby.c --line 2616
Breakpoint 1: where = ruby`process_options + 4068 at ruby.c:2616:38
(lldb) run
(lldb) break set --file compile.c --line 3491
Breakpoint 2: where = ruby`iseq_peephole_optimize + 2536 at compile.c:3491:17
(lldb) continue
Process 61925 resuming
Process 61925 stopped
* thread #1, queue = &#39;com.apple.main-thread&#39;, stop reason = breakpoint 2.1
    frame #0: ruby`iseq_peephole_optimize(...) at compile.c:3491:17
   3488         const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(iobj, 1);
   3489
   3490         if (vm_ci_simple(ci) &amp;&amp; vm_ci_argc(ci) == 1 &amp;&amp; blockiseq == NULL &amp;&amp; vm_ci_mid(ci) == idRespond_to) {
-&gt; 3491             int i = 0;
   3492         }
   3493     }
   3494
```
I think it worked! It pattern matched on the characteristics of the `respond_to?` call, and hit the breakpoint set on `int i = 0;`. It&#39;s a tiny step, but it&#39;s a first step in the direction of adding the optimization.


### Using `gdb`


For anyone wanting to do the same work using `gdb`, it&#39;s pretty similar. Let&#39;s start off by creating a `breakpoints.gdb` file in the root of your project. This will set you up with your initial breakpoint, similar to how we ran `lldb`, and set the breakpoint before calling `run`:


```text
break ruby.c:2616
```
When you run `make gdb-ruby`, you can use the same backtrace command, `bt`:


```c
&gt; make gdb-ruby
Thread 1 &#34;ruby&#34; hit Breakpoint 4, process_options (...) at ../ruby.c:2616
2616	            iseq = pm_iseq_new_main(&amp;pm-&gt;node, opt-&gt;script_name, path, parent, optimize, &amp;error_state);
(gdb) bt
#0  process_options (...) at ../ruby.c:2616
#1  in ruby_process_options (...) at ../ruby.c:3169
#2  in ruby_options (...) at ../eval.c:117
#3  in rb_main (...) at ../main.c:43
#4  in main (...) at ../main.c:68
(gdb)
```
From here, you can set your next breakpoint so that you can see the compilation solely for the `newarray` instruction from our `test.rb` program:


```c
(gdb) break compile.c:3476
Breakpoint 5 at 0xaaaabaa22f14: file ../compile.c, line 3476
(gdb) continue
Continuing.

Thread 1 &#34;ruby&#34; hit Breakpoint 5, iseq_peephole_optimize (...) at ../compile.c:3476
3476	                iobj-&gt;insn_id = BIN(opt_ary_freeze);
```
Similar to the `lldb` command `expr`, we can inspect the contents of locals using `p` or `print` in `gdb`:


```c
(gdb) p *(iobj)
$2 = {link = {type = ISEQ_ELEMENT_INSN, next = 0xaaaace797ef0, prev = 0xaaaace797e70}, insn_id = YARVINSN_newarray,
  operand_size = 1, sc_state = 0, operands = 0xaaaace796ac8, insn_info = {line_no = 1, node_id = 3, events = 0}}
```
### Finishing up


Ok, this went pretty long. Good on you for sticking in there with me! We&#39;ve found the optimizer, and we&#39;ve pattern matched our way to a `respond_to?` call. Next, we need to add the new instruction definition and try to actually replace the `send` with our new instruction. See you next time! 👋🏼

</source:markdown>
    </item>
    
    <item>
      <title>The Ruby Syntax Holy Grail: adding `opt_respond_to` to the Ruby VM, part 3</title>
      <link>https://jpcamara.com/2024/12/25/the-ruby-syntax-holy-grail.html</link>
      <pubDate>Wed, 25 Dec 2024 23:37:06 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/25/the-ruby-syntax-holy-grail.html</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/12/22/finding-the-compiler.html&#34;&gt;Finding the compiler: adding &lt;code&gt;opt_respond_to&lt;/code&gt; to the Ruby VM, part 2&lt;/a&gt;, I found the entrypoint into the compiler! It takes the root of our abstract syntax tree - &lt;code&gt;pm-&amp;gt;node&lt;/code&gt; - and produces a &lt;code&gt;rb_iseq_t&lt;/code&gt;. &lt;code&gt;rb_iseq_t&lt;/code&gt; is an &amp;ldquo;InstructionSequence&amp;rdquo;, which represents our virtual machine bytecode. Here&amp;rsquo;s the code where we left off:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ruby.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;process_options&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv, ruby_cmdline_options_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;opt)
{
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    pm_parse_result_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pm &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;result.prism;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; error_state;
    iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm_iseq_new_main(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;pm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;node, opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;script_name, path, parent, optimize, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;error_state);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing about this code screams &amp;ldquo;I&amp;rsquo;m the compiler!&amp;rdquo;. But I am taking an educated guess, since:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The compiler should produce instruction sequences, or &lt;code&gt;iseq&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;We know this is the function that returns our program&amp;rsquo;s &lt;code&gt;iseq&lt;/code&gt; when &lt;code&gt;main.c&lt;/code&gt; is called&lt;/li&gt;
&lt;li&gt;This is the only line that produces an &lt;code&gt;iseq&lt;/code&gt; in this function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all that lining up, I&amp;rsquo;m confident this is the place we need to investigate further. Now let&amp;rsquo;s find what needs to change to add our new bytecode. Stepping into &lt;code&gt;pm_iseq_new_main&lt;/code&gt;, there are a few layers I need to wade through to get to something that seems promising.&lt;/p&gt;
&lt;h3 id=&#34;getting-your-own-environment-setup&#34;&gt;Getting your own environment setup&lt;/h3&gt;
&lt;p&gt;Before I dig in further, let&amp;rsquo;s take a quick step back. In case you want to join in at home, here are some simple(ish) steps for doing that.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out my guides on how to setup your development environment to hack on CRuby. I have a &lt;a href=&#34;https://jpcamara.com/2024/11/27/my-docker-setup.html&#34;&gt;docker&lt;/a&gt; guide and a &lt;a href=&#34;https://jpcamara.com/2024/12/02/my-macos-setup.html&#34;&gt;MacOS&lt;/a&gt; guide. The one thing I didn&amp;rsquo;t add to them was cloning the repo. So you&amp;rsquo;ll need to &lt;code&gt;git clone&lt;/code&gt; the &lt;a href=&#34;https://github.com/ruby/ruby&#34;&gt;Ruby&lt;/a&gt; repository.&lt;/li&gt;
&lt;li&gt;Building CRuby can take a few minutes the first time you run it. After you&amp;rsquo;ve built everything, you can easily test your local setup using a file named &lt;code&gt;test.rb&lt;/code&gt;. Create a &lt;code&gt;test.rb&lt;/code&gt; file in the root of your CRuby folder.&lt;/li&gt;
&lt;li&gt;You can run &lt;code&gt;make runruby&lt;/code&gt;, and it will run whatever is inside your &lt;code&gt;test.rb&lt;/code&gt; file. You can even use &lt;em&gt;debug&lt;/em&gt; tools to debug the C code you&amp;rsquo;re running and inspecting - we&amp;rsquo;ll talk more about those later.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;back-to-the-investigation&#34;&gt;Back to the investigation&lt;/h3&gt;
&lt;p&gt;First we&amp;rsquo;ve got the function &lt;code&gt;pm_iseq_new_main&lt;/code&gt;, which seems to set us up as the &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; &lt;code&gt;rb_iseq_t&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// iseq.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_iseq_new_main&lt;/span&gt;(pm_scope_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;node, VALUE path, VALUE realpath, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;parent, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; opt, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;error_state)
{
    iseq_new_setup_coverage(path, (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) (node&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parser&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;newline_list.size &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;));

    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; pm_iseq_new_with_opt(node, rb_fstring_lit(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;lt;main&amp;gt;&amp;#34;&lt;/span&gt;),
                                path, realpath, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
                                parent, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, ISEQ_TYPE_MAIN, opt &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;COMPILE_OPTION_DEFAULT : &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;COMPILE_OPTION_FALSE, error_state);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This looked immediately familiar to me. It sticks out because i&amp;rsquo;ve seen that &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; before. Let&amp;rsquo;s run a simple Ruby program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;begin&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;rescue&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; e
  puts e&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;backtrace
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All our program does it &lt;code&gt;raise&lt;/code&gt; an error, &lt;code&gt;rescue&lt;/code&gt; the error, then &lt;code&gt;puts&lt;/code&gt; the backtrace. What does that backtrace look like?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;../test.rb:2:in &amp;#39;&amp;lt;main&amp;gt;&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oh yea! We are executing our code at the top-level of the program. And that top level is referred to as &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. I &lt;em&gt;think&lt;/em&gt; that&amp;rsquo;s being named by our &lt;code&gt;pm_iseq_new_with_opt(node, rb_fstring_lit(&amp;quot;&amp;lt;main&amp;gt;&amp;quot;)...&lt;/code&gt; call - neat!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;iseq_new_setup_coverage&lt;/code&gt; just sets up some optional coverage information, so let&amp;rsquo;s move to &lt;code&gt;pm_iseq_new_with_opt&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_iseq_new_with_opt&lt;/span&gt;(pm_scope_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;node, VALUE name, VALUE path, VALUE realpath,
                     &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; first_lineno, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;parent, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; isolated_depth,
                     &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; rb_iseq_type type, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; rb_compile_option_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;option, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;error_state)
{
    rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iseq_alloc();
    ISEQ_BODY(iseq)&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;prism &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; pm_iseq_new_with_opt_data data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
        .iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; iseq,
        .node &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; node
    };
    rb_protect(pm_iseq_new_with_opt_try, (VALUE)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;data, error_state);

    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;error_state) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; NULL;

    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; iseq_translate(iseq);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code allocates (&lt;code&gt;iseq_alloc&lt;/code&gt;) an &lt;code&gt;rb_iseq_t&lt;/code&gt; struct and sets it as being part of prism. I believe &lt;code&gt;rb_protect&lt;/code&gt; is to allow handling of errors that might be raised while running a particular function? Looking at the git blame I see &lt;a href=&#34;https://github.com/peterzhu2118&#34;&gt;Peter Zhu&lt;/a&gt; added it to &lt;a href=&#34;https://github.com/ruby/ruby/commit/51ffef281996727c60571771cd07c1459ba58cd2&#34;&gt;catch errors&lt;/a&gt;, so confirmed ✅. Not alot is happening here otherwise, so let&amp;rsquo;s jump into &lt;code&gt;pm_iseq_new_with_opt_try&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_iseq_new_with_opt_try&lt;/span&gt;(VALUE d)
{
    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; pm_iseq_new_with_opt_data &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; pm_iseq_new_with_opt_data &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)d;

    &lt;span style=&#34;color:#75715e&#34;&gt;// This can compile child iseqs, which can raise syntax errors
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    pm_iseq_compile_node(data&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;iseq, data&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;node);

    &lt;span style=&#34;color:#75715e&#34;&gt;// This raises an exception if there is a syntax error
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    finish_iseq_build(data&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;iseq);

    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qundef;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the most promising piece of code so far. It&amp;rsquo;s the first thing that kindly tells me in explicit terms: &amp;ldquo;I am going to compile something&amp;rdquo;. Presumably &lt;code&gt;pm_iseq_compile_node&lt;/code&gt; compiles &lt;code&gt;data-&amp;gt;node&lt;/code&gt; into the &lt;code&gt;data-&amp;gt;iseq&lt;/code&gt;. It&amp;rsquo;s in a new file called &lt;code&gt;prism_compile.c&lt;/code&gt;. Let&amp;rsquo;s check it out!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// prism_compile.c
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_iseq_compile_node&lt;/span&gt;(rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq, pm_scope_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;node)
{
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (pm_iseq_pre_execution_p(iseq)) {
        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        pm_compile_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node, body, false, node);
        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        pm_compile_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node, ret, false, node);
    }

    CHECK(iseq_setup_insn(iseq, ret));
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; iseq_setup(iseq, ret);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;😮‍💨. There are many layers to this compilation. Primarily, this function seems to do two things: &amp;ldquo;compile&amp;rdquo; the node, then &amp;ldquo;setup&amp;rdquo; the iseq. I don&amp;rsquo;t know why the &lt;code&gt;iseq&lt;/code&gt; &amp;ldquo;setup&amp;rdquo; is required yet. Let&amp;rsquo;s start with &lt;code&gt;pm_compile_node&lt;/code&gt; and I&amp;rsquo;ll come back to the rest:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;pm_compile_node&lt;/span&gt;(rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;node, LINK_ANCHOR &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; ret, &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; popped, pm_scope_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;scope_node)
{
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_parser_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;parser &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; scope_node&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;parser;
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (PM_NODE_TYPE(node)) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_ALIAS_GLOBAL_VARIABLE_NODE:
        &lt;span style=&#34;color:#75715e&#34;&gt;// alias $foo $bar
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        pm_compile_alias_global_variable_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_alias_global_variable_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;location, ret, popped, scope_node);
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_ARRAY_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// [foo, bar, baz]
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^^^^^^^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_array_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;cast &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_array_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node;
        pm_compile_array_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) cast, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cast&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;elements, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;location, ret, popped, scope_node);
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_FLIP_FLOP_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// if foo .. bar; end
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//    ^^^^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_flip_flop_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;cast &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_flip_flop_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node;
        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        pm_compile_flip_flop(cast, else_label, then_label, iseq, location.line, ret, popped, scope_node);
        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_IT_LOCAL_VARIABLE_READ_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// -&amp;gt; { it }
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//      ^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;popped) {
            PUSH_GETLOCAL(ret, location, scope_node&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;local_table_for_iseq_size, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
        }

        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_MODULE_NODE: {
        &lt;span style=&#34;color:#75715e&#34;&gt;// module Foo; end
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      }
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pm_compile_node&lt;/code&gt; puts the &amp;ldquo;fun&amp;rdquo; in &amp;ldquo;function&amp;rdquo;. It&amp;rsquo;s really cool! This 1800+ line monster seems to cover a huge swath of Ruby syntax. Maybe all of it? The &lt;code&gt;prism_compile.c&lt;/code&gt; file is 11 &lt;em&gt;thousand&lt;/em&gt; lines long, as each &lt;code&gt;case&lt;/code&gt; of this &lt;code&gt;switch&lt;/code&gt; statement branches off into more granular node compilations, like &lt;code&gt;pm_compile_array_node&lt;/code&gt; and &lt;code&gt;pm_compile_flip_flop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With that in mind, it is also an utterly daunting file to consider for the &lt;code&gt;opt_respond_to&lt;/code&gt; instruction. Do I edit this file? Where would I even start? I need to swap out a method call to &lt;code&gt;respond_to?&lt;/code&gt; - there is code that seems to handle method calls:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; PM_CALL_NODE:
    &lt;span style=&#34;color:#75715e&#34;&gt;// foo
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// foo.bar
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;//
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// foo.bar() {}
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ^^^^^^^^^^^^
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    pm_compile_call_node(iseq, (&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; pm_call_node_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;) node, ret, popped, scope_node);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Maybe that&amp;rsquo;s it?&lt;/p&gt;
&lt;p&gt;I think I need to use a cheat code here to give me some direction. In the previous post, I mentioned &lt;a href=&#34;https://github.com/etiennebarrie&#34;&gt;Étienne Barrié&lt;/a&gt;&amp;rsquo;s PR to &lt;a href=&#34;https://github.com/ruby/ruby/pull/11406&#34;&gt;add optimized instructions for frozen literal Hash and Array&lt;/a&gt;. I&amp;rsquo;ve been mostly ignoring it so far, but I think it&amp;rsquo;s time I use that for a bit of direction on where to go from here.&lt;/p&gt;
&lt;p&gt;I think we&amp;rsquo;re close! So far, I&amp;rsquo;ve navigated the code manually. In the next post, we&amp;rsquo;re going to actually run and debug some code, and dig a bit into Étienne&amp;rsquo;s work. See you then! 👋🏼&lt;/p&gt;
</description>
      <source:markdown>In [Finding the compiler: adding `opt_respond_to` to the Ruby VM, part 2](https://jpcamara.com/2024/12/22/finding-the-compiler.html), I found the entrypoint into the compiler! It takes the root of our abstract syntax tree - `pm-&gt;node` - and produces a `rb_iseq_t`. `rb_iseq_t` is an &#34;InstructionSequence&#34;, which represents our virtual machine bytecode. Here&#39;s the code where we left off:


```c
// ruby.c
static VALUE
process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
{
    //...
    pm_parse_result_t *pm = &amp;result.prism;
    int error_state;
    iseq = pm_iseq_new_main(&amp;pm-&gt;node, opt-&gt;script_name, path, parent, optimize, &amp;error_state);
```
Nothing about this code screams &#34;I&#39;m the compiler!&#34;. But I am taking an educated guess, since:


- The compiler should produce instruction sequences, or `iseq`s
- We know this is the function that returns our program&#39;s `iseq` when `main.c` is called
- This is the only line that produces an `iseq` in this function


With all that lining up, I&#39;m confident this is the place we need to investigate further. Now let&#39;s find what needs to change to add our new bytecode. Stepping into `pm_iseq_new_main`, there are a few layers I need to wade through to get to something that seems promising.


### Getting your own environment setup


Before I dig in further, let&#39;s take a quick step back. In case you want to join in at home, here are some simple(ish) steps for doing that.


1. Check out my guides on how to setup your development environment to hack on CRuby. I have a [docker](https://jpcamara.com/2024/11/27/my-docker-setup.html) guide and a [MacOS](https://jpcamara.com/2024/12/02/my-macos-setup.html) guide. The one thing I didn&#39;t add to them was cloning the repo. So you&#39;ll need to `git clone` the [Ruby](https://github.com/ruby/ruby) repository.
1. Building CRuby can take a few minutes the first time you run it. After you&#39;ve built everything, you can easily test your local setup using a file named `test.rb`. Create a `test.rb` file in the root of your CRuby folder.
1. You can run `make runruby`, and it will run whatever is inside your `test.rb` file. You can even use _debug_ tools to debug the C code you&#39;re running and inspecting - we&#39;ll talk more about those later.


### Back to the investigation


First we&#39;ve got the function `pm_iseq_new_main`, which seems to set us up as the `&lt;main&gt;` `rb_iseq_t`.


```c
// iseq.c
rb_iseq_t *
pm_iseq_new_main(pm_scope_node_t *node, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt, int *error_state)
{
    iseq_new_setup_coverage(path, (int) (node-&gt;parser-&gt;newline_list.size - 1));

    return pm_iseq_new_with_opt(node, rb_fstring_lit(&#34;&lt;main&gt;&#34;),
                                path, realpath, 0,
                                parent, 0, ISEQ_TYPE_MAIN, opt ? &amp;COMPILE_OPTION_DEFAULT : &amp;COMPILE_OPTION_FALSE, error_state);
}
```
This looked immediately familiar to me. It sticks out because i&#39;ve seen that `&lt;main&gt;` before. Let&#39;s run a simple Ruby program:


```ruby
begin
  raise
rescue =&gt; e
  puts e.backtrace
end
```
All our program does it `raise` an error, `rescue` the error, then `puts` the backtrace. What does that backtrace look like?


```text
../test.rb:2:in &#39;&lt;main&gt;&#39;
```
Oh yea! We are executing our code at the top-level of the program. And that top level is referred to as `&lt;main&gt;`. I _think_ that&#39;s being named by our `pm_iseq_new_with_opt(node, rb_fstring_lit(&#34;&lt;main&gt;&#34;)...` call - neat!


`iseq_new_setup_coverage` just sets up some optional coverage information, so let&#39;s move to `pm_iseq_new_with_opt`:


```c
rb_iseq_t *
pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpath,
                     int first_lineno, const rb_iseq_t *parent, int isolated_depth,
                     enum rb_iseq_type type, const rb_compile_option_t *option, int *error_state)
{
    rb_iseq_t *iseq = iseq_alloc();
    ISEQ_BODY(iseq)-&gt;prism = true;
    //...
    struct pm_iseq_new_with_opt_data data = {
        .iseq = iseq,
        .node = node
    };
    rb_protect(pm_iseq_new_with_opt_try, (VALUE)&amp;data, error_state);

    if (*error_state) return NULL;

    return iseq_translate(iseq);
}
```
This code allocates (`iseq_alloc`) an `rb_iseq_t` struct and sets it as being part of prism. I believe `rb_protect` is to allow handling of errors that might be raised while running a particular function? Looking at the git blame I see [Peter Zhu](https://github.com/peterzhu2118) added it to [catch errors](https://github.com/ruby/ruby/commit/51ffef281996727c60571771cd07c1459ba58cd2), so confirmed ✅. Not alot is happening here otherwise, so let&#39;s jump into `pm_iseq_new_with_opt_try`:


```c
VALUE
pm_iseq_new_with_opt_try(VALUE d)
{
    struct pm_iseq_new_with_opt_data *data = (struct pm_iseq_new_with_opt_data *)d;

    // This can compile child iseqs, which can raise syntax errors
    pm_iseq_compile_node(data-&gt;iseq, data-&gt;node);

    // This raises an exception if there is a syntax error
    finish_iseq_build(data-&gt;iseq);

    return Qundef;
}
```
This is the most promising piece of code so far. It&#39;s the first thing that kindly tells me in explicit terms: &#34;I am going to compile something&#34;. Presumably `pm_iseq_compile_node` compiles `data-&gt;node` into the `data-&gt;iseq`. It&#39;s in a new file called `prism_compile.c`. Let&#39;s check it out!


```c
// prism_compile.c
VALUE
pm_iseq_compile_node(rb_iseq_t *iseq, pm_scope_node_t *node)
{
    //...
    if (pm_iseq_pre_execution_p(iseq)) {
        //...
        pm_compile_node(iseq, (const pm_node_t *) node, body, false, node);
        //...
    }
    else {
        //...
        pm_compile_node(iseq, (const pm_node_t *) node, ret, false, node);
    }

    CHECK(iseq_setup_insn(iseq, ret));
    return iseq_setup(iseq, ret);
}
```
😮‍💨. There are many layers to this compilation. Primarily, this function seems to do two things: &#34;compile&#34; the node, then &#34;setup&#34; the iseq. I don&#39;t know why the `iseq` &#34;setup&#34; is required yet. Let&#39;s start with `pm_compile_node` and I&#39;ll come back to the rest:


```c
static void
pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node)
{
    const pm_parser_t *parser = scope_node-&gt;parser;
    //...
    switch (PM_NODE_TYPE(node)) {
      case PM_ALIAS_GLOBAL_VARIABLE_NODE:
        // alias $foo $bar
        // ^^^^^^^^^^^^^^^
        pm_compile_alias_global_variable_node(iseq, (const pm_alias_global_variable_node_t *) node, &amp;location, ret, popped, scope_node);
        return;
      //...
      case PM_ARRAY_NODE: {
        // [foo, bar, baz]
        // ^^^^^^^^^^^^^^^
        const pm_array_node_t *cast = (const pm_array_node_t *) node;
        pm_compile_array_node(iseq, (const pm_node_t *) cast, &amp;cast-&gt;elements, &amp;location, ret, popped, scope_node);
        return;
      }
      //...
      case PM_FLIP_FLOP_NODE: {
        // if foo .. bar; end
        //    ^^^^^^^^^^
        const pm_flip_flop_node_t *cast = (const pm_flip_flop_node_t *) node;
        //...
        pm_compile_flip_flop(cast, else_label, then_label, iseq, location.line, ret, popped, scope_node);
        //...
      }
      //...
      case PM_IT_LOCAL_VARIABLE_READ_NODE: {
        // -&gt; { it }
        //      ^^
        if (!popped) {
            PUSH_GETLOCAL(ret, location, scope_node-&gt;local_table_for_iseq_size, 0);
        }

        return;
      }
      //...
      case PM_MODULE_NODE: {
        // module Foo; end
        //...
      }
      //...
}
```
`pm_compile_node` puts the &#34;fun&#34; in &#34;function&#34;. It&#39;s really cool! This 1800+ line monster seems to cover a huge swath of Ruby syntax. Maybe all of it? The `prism_compile.c` file is 11 _thousand_ lines long, as each `case` of this `switch` statement branches off into more granular node compilations, like `pm_compile_array_node` and `pm_compile_flip_flop`.


With that in mind, it is also an utterly daunting file to consider for the `opt_respond_to` instruction. Do I edit this file? Where would I even start? I need to swap out a method call to `respond_to?` - there is code that seems to handle method calls:


```c
case PM_CALL_NODE:
    // foo
    // ^^^
    //
    // foo.bar
    // ^^^^^^^
    //
    // foo.bar() {}
    // ^^^^^^^^^^^^
    pm_compile_call_node(iseq, (const pm_call_node_t *) node, ret, popped, scope_node);
    return;
```
Maybe that&#39;s it?

I think I need to use a cheat code here to give me some direction. In the previous post, I mentioned [Étienne Barrié](https://github.com/etiennebarrie)&#39;s PR to [add optimized instructions for frozen literal Hash and Array](https://github.com/ruby/ruby/pull/11406). I&#39;ve been mostly ignoring it so far, but I think it&#39;s time I use that for a bit of direction on where to go from here.


I think we&#39;re close! So far, I&#39;ve navigated the code manually. In the next post, we&#39;re going to actually run and debug some code, and dig a bit into Étienne&#39;s work. See you then! 👋🏼

</source:markdown>
    </item>
    
    <item>
      <title>Finding the compiler: adding `opt_respond_to` to the Ruby VM, part 2</title>
      <link>https://jpcamara.com/2024/12/22/finding-the-compiler.html</link>
      <pubDate>Mon, 23 Dec 2024 00:41:51 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/22/finding-the-compiler.html</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/12/22/adding-optrespondto-to.html&#34;&gt;Adding &lt;code&gt;opt_respond_to&lt;/code&gt; to the Ruby VM: part 1&lt;/a&gt;, inspired by recent JSON gem optimizations, I setup my goal: I want to add a new bytecode instruction to the Ruby VM which optimizes &lt;code&gt;respond_to?&lt;/code&gt; calls. I took this Ruby code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And identified what bytecode instructions matter most (I think):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;== disasm: #&amp;lt;ISeq:&amp;lt;compiled&amp;gt;@&amp;lt;compiled&amp;gt;:1 (1,0)-(3,3)&amp;gt;
# ...
0002 putobject                 :write
0004 opt_send_without_block    &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This seems pretty low-level! But it&amp;rsquo;s still very high-level in terms of what I need to actually &lt;em&gt;do&lt;/em&gt;. I know what instructions matter, but how can I &lt;em&gt;change&lt;/em&gt; them?&lt;/p&gt;
&lt;h3 id=&#34;a-little-help-from-git&#34;&gt;A little help from Git&lt;/h3&gt;
&lt;p&gt;Thankfully, &lt;a href=&#34;https://bsky.app/profile/byroot.bsky.social&#34;&gt;@byroot&lt;/a&gt; gave me some helpful direction in the form of a recent PR. &lt;a href=&#34;https://github.com/etiennebarrie&#34;&gt;Étienne Barrié&lt;/a&gt; recently merged a PR to &lt;a href=&#34;https://github.com/ruby/ruby/pull/11406&#34;&gt;add optimized instructions for frozen literal Hash and Array&lt;/a&gt;. It adds special handling for frozen, empty arrays and hashes so that when you use them in your code, calls like &lt;code&gt;[].freeze&lt;/code&gt; will not result in any additional object allocations. Cool! A neat enhancement, and kind of perfect for me to analyze.&lt;/p&gt;
&lt;p&gt;I think the use-case is simpler than what i&amp;rsquo;d need for &lt;code&gt;opt_respond_to&lt;/code&gt;, but looking at that PR I can see an important part of adding a new bytecode instruction is in &lt;code&gt;compile.c&lt;/code&gt;. I&amp;rsquo;ll eventually need to add some new logic there, but what steps does CRuby take to &lt;em&gt;get&lt;/em&gt; to &lt;code&gt;compile.c&lt;/code&gt;?&lt;/p&gt;
&lt;h3 id=&#34;starting-from-main&#34;&gt;Starting from main&lt;/h3&gt;
&lt;p&gt;Knowing a bit about the CRuby source, and how C programs start, I know there is a &lt;code&gt;main&lt;/code&gt; function that kicks everything off. In CRuby, it&amp;rsquo;s helpfully in &lt;code&gt;main.c&lt;/code&gt;, at the root of the project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv)
{
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; rb_main(argc, argv);
}

&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;rb_main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv)
{
    RUBY_INIT_STACK;
    ruby_init();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; ruby_run_node(ruby_options(argc, argv));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m going to guess at not needing &lt;code&gt;RUBY_INIT_STACK&lt;/code&gt; or &lt;code&gt;ruby_init&lt;/code&gt; for adding a new instruction. I took a peek at it and it all seems related to setting up the runtime, and creating data structures needed for the Ruby virtual machine. Past that, there are only two function calls: &lt;code&gt;ruby_options&lt;/code&gt; and &lt;code&gt;ruby_run_node&lt;/code&gt;. &lt;code&gt;ruby_options&lt;/code&gt; sounds like it would just get the options needed for the program. Maybe we need to go into &lt;code&gt;ruby_run_node&lt;/code&gt;?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_run_node&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;n)
{
    rb_execution_context_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GET_EC();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; status;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;ruby_executable_node(n, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;status)) {
        rb_ec_cleanup(ec, (NIL_P(ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;errinfo) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; TAG_NONE : TAG_RAISE));
        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; status;
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; rb_ec_cleanup(ec, rb_ec_exec_node(ec, n));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Maybe? It looks like if the &amp;ldquo;node&amp;rdquo; &lt;code&gt;n&lt;/code&gt; isn&amp;rsquo;t executable, it fails. I&amp;rsquo;ll concentrate instead on the success path on the last line. &lt;code&gt;rb_ec_exec_node&lt;/code&gt; will run first, followed by &lt;code&gt;rb_ec_cleanup&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 All this &lt;code&gt;ec_*&lt;/code&gt; stuff seems to stand for &lt;code&gt;execution_context&lt;/code&gt;, which presumably is the state of the runtime at any given point?&lt;/p&gt;
&lt;p&gt;📝 I&amp;rsquo;m not Mr. C programmer - so I didn&amp;rsquo;t know what &lt;code&gt;void *n&lt;/code&gt; meant. Looking it up, this seems to be a way of specifying a generic pointer type that can point to any data type&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s start by checking &lt;code&gt;rb_ec_exec_node&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;rb_ec_exec_node&lt;/span&gt;(rb_execution_context_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ec, &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;n)
{
    &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; state;
    rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (rb_iseq_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)n;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;n) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;

    EC_PUSH_TAG(ec);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; EC_EXEC_TAG()) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; TAG_NONE) {
        rb_iseq_eval_main(iseq);
    }
    EC_POP_TAG();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; state;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Hmmmm. This is the first place where I don&amp;rsquo;t like what i&amp;rsquo;m seeing. My primary concern is that &lt;code&gt;void *n&lt;/code&gt; is getting cast to &lt;code&gt;rb_iseq_t&lt;/code&gt;. The class we used to &lt;code&gt;compile&lt;/code&gt; our Ruby sample in part 1 - &lt;code&gt;RubyVM::InstructionSequence&lt;/code&gt; - is defined in a C file called &lt;code&gt;iseq.c&lt;/code&gt;. So in CRuby, &lt;code&gt;iseq&lt;/code&gt; stands for &amp;ldquo;InstructionSequence&amp;rdquo;. If we already have an &lt;code&gt;iseq&lt;/code&gt;, I think it means our code has already been compiled and we&amp;rsquo;ve gone too far.&lt;/p&gt;
&lt;h3 id=&#34;stepping-back-to-ruby_options&#34;&gt;Stepping back to &lt;code&gt;ruby_options&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ruby_run_node&lt;/code&gt; doesn&amp;rsquo;t do much aside from calling &lt;code&gt;rb_ec_exec_node&lt;/code&gt;. So if &lt;code&gt;ruby_run_node&lt;/code&gt; and &lt;code&gt;rb_ec_exec_node&lt;/code&gt; are not the right functions&amp;hellip; that only leaves &lt;code&gt;ruby_options&lt;/code&gt;. Not what I would expect, but let&amp;rsquo;s check:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_options&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv)
{
    rb_execution_context_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ec &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; GET_EC();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;enum&lt;/span&gt; ruby_tag_type state;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;

    EC_PUSH_TAG(ec);
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ((state &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; EC_EXEC_TAG()) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; TAG_NONE) {
        iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ruby_process_options(argc, argv);
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
        rb_ec_clear_current_thread_trace_func(ec);
        &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; exitcode &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; error_handle(ec, ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;errinfo, state);
        ec&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;errinfo &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Qnil; &lt;span style=&#34;color:#75715e&#34;&gt;/* just been handled */&lt;/span&gt;
        iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)INT2FIX(exitcode);
    }
    EC_POP_TAG();
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; iseq;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a lot going on in here, but I&amp;rsquo;m drawn to the &lt;code&gt;iseq = ruby_process_options(argc, argv)&lt;/code&gt; line. Let&amp;rsquo;s dig into &lt;code&gt;ruby_process_options&lt;/code&gt;. This is a big one:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;ruby_process_options&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv)
{
    ruby_cmdline_options_t opt;
    VALUE iseq;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;script_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (argc &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; argv[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]) &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; argv[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; ruby_engine;

    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;origarg.argv &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; origarg.argc &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
        origarg.argc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; argc;
        origarg.argv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; argv;
    }
    set_progname(external_str_new_cstr(script_name));  &lt;span style=&#34;color:#75715e&#34;&gt;/* for the time being */&lt;/span&gt;
    rb_argv0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_str_new4(rb_progname);
    rb_vm_register_global_object(rb_argv0);

&lt;span style=&#34;color:#75715e&#34;&gt;#ifndef HAVE_SETPROCTITLE
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    ruby_init_setproctitle(argc, argv);
&lt;span style=&#34;color:#75715e&#34;&gt;#endif
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
    iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; process_options(argc, argv, cmdline_options_init(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;opt));

    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; RData&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)iseq;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Most of this function seems to be VM setup. But I think we&amp;rsquo;re getting closer with &lt;code&gt;iseq = process_options(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Checking &lt;code&gt;process_options&lt;/code&gt;&amp;hellip; whoa, this is a ~350 line function! It&amp;rsquo;s a bit much to all paste in here, but scanning the code, I think we&amp;rsquo;re on the right track. There are all sorts of option initializations here:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;process_options&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv, ruby_cmdline_options_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;opt)
{
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (FEATURE_SET_P(opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;features, yjit)) {
        &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; rb_yjit_option_disable(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;);
        opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;yjit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;rb_yjit_option_disable(); &lt;span style=&#34;color:#75715e&#34;&gt;// set opt-&amp;gt;yjit for Init_ruby_description() and calling rb_yjit_init()
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    }
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    ruby_mn_threads_params();
    Init_ruby_description(opt);
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    ruby_gc_set_params();
    ruby_init_loadpath();
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Among many other things, it sets up options for yjit, &lt;a href=&#34;https://bugs.ruby-lang.org/issues/19842&#34;&gt;mn threads&lt;/a&gt;, the program description, garbage collection params, and the loadpath. That&amp;rsquo;s just scratching the surface of this function. Then around 240 lines into the function, I see a very promising &lt;code&gt;if&lt;/code&gt; statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; VALUE
&lt;span style=&#34;color:#a6e22e&#34;&gt;process_options&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; argc, &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt;argv, ruby_cmdline_options_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;opt)
{
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
        rb_ast_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ast;
        pm_parse_result_t prism;
    } result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;};
    &lt;span style=&#34;color:#75715e&#34;&gt;// ... ~240 lines of option handling
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;rb_ruby_prism_p()) {
        ast_value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; process_script(opt);
        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;(result.ast &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rb_ruby_ast_data_get(ast_value))) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; Qfalse;
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
        prism_script(opt, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;result.prism);
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The beginning of the function sets up a struct that contains either a &lt;code&gt;rb_ast_t&lt;/code&gt;, or a &lt;code&gt;pm_parse_result_t&lt;/code&gt;. &lt;a href=&#34;https://github.com/ruby/prism&#34;&gt;Prism&lt;/a&gt; is the new default Ruby parser as of Ruby 3.4, so we&amp;rsquo;re getting close. &lt;code&gt;rb_ast_t&lt;/code&gt; must be the format for the prior CRuby parser.&lt;/p&gt;
&lt;p&gt;From a naming perspective, I would &lt;em&gt;never&lt;/em&gt; have guessed that &lt;code&gt;ruby_options&lt;/code&gt; is the place that parses our Ruby code. In principle I guess this is all preamble to actually running the program, so it &lt;em&gt;kind of&lt;/em&gt; relates.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t dig into &lt;code&gt;prism_script&lt;/code&gt;, since it would create our &lt;a href=&#34;https://en.wikipedia.org/wiki/Abstract_syntax_tree&#34;&gt;Abstract Syntax Tree (AST)&lt;/a&gt;, which I expect later will be used by the compiler:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;typedef&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/** The resulting scope node that will hold the generated AST. */&lt;/span&gt;
    pm_scope_node_t node;
    &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;} pm_parse_result_t;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ok, here we go! I think we&amp;rsquo;ve got it with this next section! The &lt;code&gt;pm_scope_node_t node&lt;/code&gt; (which should be set by &lt;code&gt;prism_script&lt;/code&gt;) is used to create our &lt;code&gt;rb_iseq_t *iseq&lt;/code&gt; inside of &lt;code&gt;pm_iseq_new_main&lt;/code&gt;!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ~320 lines into the function
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;pm_parse_result_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;pm &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;result.prism;
&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; error_state;
iseq &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pm_iseq_new_main(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;pm&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;node, opt&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;script_name, path, parent, optimize, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;error_state);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We now have the entrypoint into creating our &lt;code&gt;InstructionSequence&lt;/code&gt; (our &lt;code&gt;iseq&lt;/code&gt;, or &lt;code&gt;rb_iseq_t&lt;/code&gt;). I wanted to start digging into the actual compiler, but I think I&amp;rsquo;ll stop here for today.&lt;/p&gt;
&lt;p&gt;Now that we know the entrypoint into the compiler, we can start figuring out what code might need to change to add a new bytecode instruction. Next up, i&amp;rsquo;m hoping we can find the appropriate area that needs that change. See you then! 👋🏼&lt;/p&gt;
</description>
      <source:markdown>In [Adding `opt_respond_to` to the Ruby VM: part 1](https://jpcamara.com/2024/12/22/adding-optrespondto-to.html), inspired by recent JSON gem optimizations, I setup my goal: I want to add a new bytecode instruction to the Ruby VM which optimizes `respond_to?` calls. I took this Ruby code:


```ruby
if $stdout.respond_to?(:write)
  puts &#34;Did you know you can write to $stdout?&#34;
end
```
And identified what bytecode instructions matter most (I think):


```text
== disasm: #&lt;ISeq:&lt;compiled&gt;@&lt;compiled&gt;:1 (1,0)-(3,3)&gt;
# ...
0002 putobject                 :write
0004 opt_send_without_block    &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
```
This seems pretty low-level! But it&#39;s still very high-level in terms of what I need to actually _do_. I know what instructions matter, but how can I _change_ them?


### A little help from Git


Thankfully, [@byroot](https://bsky.app/profile/byroot.bsky.social) gave me some helpful direction in the form of a recent PR. [Étienne Barrié](https://github.com/etiennebarrie) recently merged a PR to [add optimized instructions for frozen literal Hash and Array](https://github.com/ruby/ruby/pull/11406). It adds special handling for frozen, empty arrays and hashes so that when you use them in your code, calls like `[].freeze` will not result in any additional object allocations. Cool! A neat enhancement, and kind of perfect for me to analyze.


I think the use-case is simpler than what i&#39;d need for `opt_respond_to`, but looking at that PR I can see an important part of adding a new bytecode instruction is in `compile.c`. I&#39;ll eventually need to add some new logic there, but what steps does CRuby take to _get_ to `compile.c`?


### Starting from main


Knowing a bit about the CRuby source, and how C programs start, I know there is a `main` function that kicks everything off. In CRuby, it&#39;s helpfully in `main.c`, at the root of the project:


```c
int
main(int argc, char **argv)
{
    //...
    return rb_main(argc, argv);
}

static int
rb_main(int argc, char **argv)
{
    RUBY_INIT_STACK;
    ruby_init();
    return ruby_run_node(ruby_options(argc, argv));
}
```
I&#39;m going to guess at not needing `RUBY_INIT_STACK` or `ruby_init` for adding a new instruction. I took a peek at it and it all seems related to setting up the runtime, and creating data structures needed for the Ruby virtual machine. Past that, there are only two function calls: `ruby_options` and `ruby_run_node`. `ruby_options` sounds like it would just get the options needed for the program. Maybe we need to go into `ruby_run_node`?


```c
int
ruby_run_node(void *n)
{
    rb_execution_context_t *ec = GET_EC();
    int status;
    if (!ruby_executable_node(n, &amp;status)) {
        rb_ec_cleanup(ec, (NIL_P(ec-&gt;errinfo) ? TAG_NONE : TAG_RAISE));
        return status;
    }
    return rb_ec_cleanup(ec, rb_ec_exec_node(ec, n));
}
```
Maybe? It looks like if the &#34;node&#34; `n` isn&#39;t executable, it fails. I&#39;ll concentrate instead on the success path on the last line. `rb_ec_exec_node` will run first, followed by `rb_ec_cleanup`.


&gt; 📝 All this `ec_*` stuff seems to stand for `execution_context`, which presumably is the state of the runtime at any given point?
&gt;
&gt; 📝 I&#39;m not Mr. C programmer - so I didn&#39;t know what `void *n` meant. Looking it up, this seems to be a way of specifying a generic pointer type that can point to any data type


Let&#39;s start by checking `rb_ec_exec_node`:


```c
static int
rb_ec_exec_node(rb_execution_context_t *ec, void *n)
{
    volatile int state;
    rb_iseq_t *iseq = (rb_iseq_t *)n;
    if (!n) return 0;

    EC_PUSH_TAG(ec);
    if ((state = EC_EXEC_TAG()) == TAG_NONE) {
        rb_iseq_eval_main(iseq);
    }
    EC_POP_TAG();
    return state;
}
```
Hmmmm. This is the first place where I don&#39;t like what i&#39;m seeing. My primary concern is that `void *n` is getting cast to `rb_iseq_t`. The class we used to `compile` our Ruby sample in part 1 - `RubyVM::InstructionSequence` - is defined in a C file called `iseq.c`. So in CRuby, `iseq` stands for &#34;InstructionSequence&#34;. If we already have an `iseq`, I think it means our code has already been compiled and we&#39;ve gone too far.


### Stepping back to `ruby_options`


`ruby_run_node` doesn&#39;t do much aside from calling `rb_ec_exec_node`. So if `ruby_run_node` and `rb_ec_exec_node` are not the right functions... that only leaves `ruby_options`. Not what I would expect, but let&#39;s check:


```c
void *
ruby_options(int argc, char **argv)
{
    rb_execution_context_t *ec = GET_EC();
    enum ruby_tag_type state;
    void *volatile iseq = 0;

    EC_PUSH_TAG(ec);
    if ((state = EC_EXEC_TAG()) == TAG_NONE) {
        iseq = ruby_process_options(argc, argv);
    }
    else {
        rb_ec_clear_current_thread_trace_func(ec);
        int exitcode = error_handle(ec, ec-&gt;errinfo, state);
        ec-&gt;errinfo = Qnil; /* just been handled */
        iseq = (void *)INT2FIX(exitcode);
    }
    EC_POP_TAG();
    return iseq;
}
```

There&#39;s a lot going on in here, but I&#39;m drawn to the `iseq = ruby_process_options(argc, argv)` line. Let&#39;s dig into `ruby_process_options`. This is a big one:

```c
void *
ruby_process_options(int argc, char **argv)
{
    ruby_cmdline_options_t opt;
    VALUE iseq;
    const char *script_name = (argc &gt; 0 &amp;&amp; argv[0]) ? argv[0] : ruby_engine;

    if (!origarg.argv || origarg.argc &lt;= 0) {
        origarg.argc = argc;
        origarg.argv = argv;
    }
    set_progname(external_str_new_cstr(script_name));  /* for the time being */
    rb_argv0 = rb_str_new4(rb_progname);
    rb_vm_register_global_object(rb_argv0);

#ifndef HAVE_SETPROCTITLE
    ruby_init_setproctitle(argc, argv);
#endif

    iseq = process_options(argc, argv, cmdline_options_init(&amp;opt));

    //...

    return (void*)(struct RData*)iseq;
}
```
Most of this function seems to be VM setup. But I think we&#39;re getting closer with `iseq = process_options(...)`.


Checking `process_options`... whoa, this is a ~350 line function! It&#39;s a bit much to all paste in here, but scanning the code, I think we&#39;re on the right track. There are all sorts of option initializations here:


```c
static VALUE
process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
{
    //...
    if (FEATURE_SET_P(opt-&gt;features, yjit)) {
        bool rb_yjit_option_disable(void);
        opt-&gt;yjit = !rb_yjit_option_disable(); // set opt-&gt;yjit for Init_ruby_description() and calling rb_yjit_init()
    }
    //...
    ruby_mn_threads_params();
    Init_ruby_description(opt);
    //...
    ruby_gc_set_params();
    ruby_init_loadpath();
    //...
}
```
Among many other things, it sets up options for yjit, [mn threads](https://bugs.ruby-lang.org/issues/19842), the program description, garbage collection params, and the loadpath. That&#39;s just scratching the surface of this function. Then around 240 lines into the function, I see a very promising `if` statement:


```c
static VALUE
process_options(int argc, char **argv, ruby_cmdline_options_t *opt)
{
    //...
    struct {
        rb_ast_t *ast;
        pm_parse_result_t prism;
    } result = {0};
    // ... ~240 lines of option handling
    if (!rb_ruby_prism_p()) {
        ast_value = process_script(opt);
        if (!(result.ast = rb_ruby_ast_data_get(ast_value))) return Qfalse;
    }
    else {
        prism_script(opt, &amp;result.prism);
    }
```
The beginning of the function sets up a struct that contains either a `rb_ast_t`, or a `pm_parse_result_t`. [Prism](https://github.com/ruby/prism) is the new default Ruby parser as of Ruby 3.4, so we&#39;re getting close. `rb_ast_t` must be the format for the prior CRuby parser.


From a naming perspective, I would _never_ have guessed that `ruby_options` is the place that parses our Ruby code. In principle I guess this is all preamble to actually running the program, so it _kind of_ relates.


I won&#39;t dig into `prism_script`, since it would create our [Abstract Syntax Tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree), which I expect later will be used by the compiler:


```c
typedef struct {
    //...
    /** The resulting scope node that will hold the generated AST. */
    pm_scope_node_t node;
    //...
} pm_parse_result_t;
```
Ok, here we go! I think we&#39;ve got it with this next section! The `pm_scope_node_t node` (which should be set by `prism_script`) is used to create our `rb_iseq_t *iseq` inside of `pm_iseq_new_main`!


```c
// ~320 lines into the function
pm_parse_result_t *pm = &amp;result.prism;
int error_state;
iseq = pm_iseq_new_main(&amp;pm-&gt;node, opt-&gt;script_name, path, parent, optimize, &amp;error_state);
```
We now have the entrypoint into creating our `InstructionSequence` (our `iseq`, or `rb_iseq_t`). I wanted to start digging into the actual compiler, but I think I&#39;ll stop here for today.


Now that we know the entrypoint into the compiler, we can start figuring out what code might need to change to add a new bytecode instruction. Next up, i&#39;m hoping we can find the appropriate area that needs that change. See you then! 👋🏼

</source:markdown>
    </item>
    
    <item>
      <title>Adding `opt_respond_to` to the Ruby VM: part 1</title>
      <link>https://jpcamara.com/2024/12/22/adding-optrespondto-to.html</link>
      <pubDate>Sun, 22 Dec 2024 01:22:24 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/22/adding-optrespondto-to.html</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://bsky.app/profile/byroot.bsky.social&#34;&gt;@byroot&lt;/a&gt; has been posting &lt;a href=&#34;https://byroot.github.io/ruby/json/2024/12/15/optimizing-ruby-json-part-1.html&#34;&gt;a series on optimizations he and others have made&lt;/a&gt; to the &lt;a href=&#34;https://github.com/ruby/json&#34;&gt;json&lt;/a&gt; gem, and it&amp;rsquo;s been 🔥🔥🔥. Enjoyable and informative, I highly recommend reading what he&amp;rsquo;s posted so far.&lt;/p&gt;
&lt;p&gt;In his &lt;a href=&#34;https://byroot.github.io/ruby/json/2024/12/18/optimizing-ruby-json-part-2.html#inline-caches&#34;&gt;second post&lt;/a&gt;, he mentions the possibility of improving performance by adding an additional method cache. This would involve compiling &lt;code&gt;respond_to?&lt;/code&gt; calls in a special way:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It actually wouldn’t be too hard to add such a cache, we’d need to modify the Ruby compiler to compile respond_to? calls into a specialized opt_respond_to instruction that does have two caches instead of one. The first cache would be used to look up respond_to? on the object to make sure it wasn’t redefined, and the second one to look up the method we’re interested in. Or perhaps even 3 caches, as you also need to check if the object has a respond_to_missing? method defined in some cases.&lt;/p&gt;
&lt;p&gt;That’s an idea I remember discussing in the past with some fellow committers, but I can’t quite remember if there was a reason we didn’t do it yet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Inspired by his comment, I&amp;rsquo;m going to add a new bytecode instruction - &lt;code&gt;opt_respond_to&lt;/code&gt; - to the Ruby VM, for fun. I don&amp;rsquo;t know how to add a new bytecode instruction (yet). I don&amp;rsquo;t know if one would get accepted by the Ruby team. I don&amp;rsquo;t know if adding it will actually provide a meaningful enhancement to performance. But let&amp;rsquo;s give it a try, shall we?&lt;/p&gt;
&lt;h3 id=&#34;understanding-the-requirements&#34;&gt;Understanding the requirements&lt;/h3&gt;
&lt;p&gt;I know I want to add a new Ruby Virtual Machine bytecode called &lt;code&gt;opt_respond_to&lt;/code&gt;. What does code using &lt;code&gt;respond_to?&lt;/code&gt; look like today, after being compiled? Here&amp;rsquo;s some code to evaluate:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; $stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;)
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Did you know you can write to $stdout?&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can compile it using &lt;code&gt;RubyVM::InstructionSequence&lt;/code&gt;. We &lt;code&gt;compile&lt;/code&gt; the code, then we &lt;code&gt;disassemble&lt;/code&gt; it to see the actual Ruby bytecode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;puts &lt;span style=&#34;color:#66d9ef&#34;&gt;RubyVM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;InstructionSequence&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;compile(&lt;span style=&#34;color:#66d9ef&#34;&gt;DATA&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;disassemble

&lt;span style=&#34;color:#75715e&#34;&gt;__END__
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;if $stdout.respond_to?(:write)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;  puts &amp;#34;Did you know you can write to $stdout?&amp;#34;
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 The &lt;code&gt;__END__&lt;/code&gt; format is just a convenient way of supplying some text to your program. Here we put all our Ruby code we want to compile after &lt;code&gt;__END__&lt;/code&gt; and it will be available to our program as an IO object called &lt;code&gt;DATA&lt;/code&gt;. Thanks to &lt;a href=&#34;https://bsky.app/profile/drbragg.dev/post/3lcdf5deh7k2a&#34;&gt;Drew Bragg&lt;/a&gt; for the tip!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Compiling using &lt;code&gt;InstructionSequence&lt;/code&gt; gives us:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;== disasm: #&amp;lt;ISeq:&amp;lt;compiled&amp;gt;@&amp;lt;compiled&amp;gt;:1 (1,0)-(3,3)&amp;gt;
0000 getglobal                    :$stdout                  (   1)[Li]
0002 putobject                    :write
0004 opt_send_without_block       &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
0006 branchunless                 14
0008 putself                                                (   2)[Li]
0009 putstring                    &amp;#34;Did you know you can write to $stdout?&amp;#34;
0011 opt_send_without_block       &amp;lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&amp;gt;
0013 leave
0014 putnil
0015 leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m going to guess at the parts I think matter most - &lt;code&gt;putobject&lt;/code&gt;, &lt;code&gt;opt_send_without_block&lt;/code&gt; and &lt;code&gt;branchunless&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;putobject&lt;/code&gt; pushes the symbol &lt;code&gt;:write&lt;/code&gt; onto the vm stack&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opt_send_without_block&lt;/code&gt; is given &lt;code&gt;&amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;&lt;/code&gt;. I&amp;rsquo;m guessing &lt;code&gt;calldata&lt;/code&gt; is a format for specifying metadata about what is being called. We&amp;rsquo;ve got the method name, &lt;code&gt;respond_to?&lt;/code&gt;, how many args are being used, &lt;code&gt;argc:1&lt;/code&gt;, and that the arguments are &amp;ldquo;simple&amp;rdquo;, &lt;code&gt;ARGS_SIMPLE&lt;/code&gt;. &lt;code&gt;mid&lt;/code&gt; stands for&amp;hellip; &amp;ldquo;method id&amp;rdquo;?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;branchunless&lt;/code&gt; wouldn&amp;rsquo;t be specifically related to creating a new instruction, but I just think it&amp;rsquo;s informative for how the &lt;code&gt;respond_to?&lt;/code&gt; result is used. I believe it means &amp;ldquo;if the last result is false, jump to instruction 14&amp;rdquo;. 14 in this case is the &lt;code&gt;putnil&lt;/code&gt; near the bottom of the bytecode. Each instruction seems to be prefixed with a hex value that identifies the location of the instruction. &lt;code&gt;putobject&lt;/code&gt; is located at &lt;code&gt;0002&lt;/code&gt;, &lt;code&gt;opt_send_without_block&lt;/code&gt; is located at &lt;code&gt;0004&lt;/code&gt;, &lt;code&gt;branchunless&lt;/code&gt; is located at &lt;code&gt;0006&lt;/code&gt; and &lt;code&gt;putnil&lt;/code&gt; is located at &lt;code&gt;0014&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think the only thing I will be changing is taking calls to &lt;code&gt;respond_to?&lt;/code&gt;, and making that a &lt;code&gt;opt_respond_to&lt;/code&gt; bytecode instead of &lt;code&gt;opt_send_without_block&lt;/code&gt;. We&amp;rsquo;ll see!&lt;/p&gt;
&lt;p&gt;There are actually a few variations for &lt;code&gt;respond_to?&lt;/code&gt; that I wasn&amp;rsquo;t aware of. The interface takes a symbol or a string as the first parameter, and then a boolean for whether to include &lt;code&gt;private&lt;/code&gt; and &lt;code&gt;protected&lt;/code&gt; methods:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;$stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;write&amp;#34;&lt;/span&gt;)
$stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;write&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)
$stdout&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;respond_to?(&lt;span style=&#34;color:#e6db74&#34;&gt;:write&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s see the bytecode for these variations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;== disasm: #&amp;lt;ISeq:&amp;lt;compiled&amp;gt;@&amp;lt;compiled&amp;gt;:1 (1,0)-(3,33)&amp;gt;
0000 getglobal                    :$stdout                  (   1)[Li]
0002 putstring                    &amp;#34;write&amp;#34;
0004 opt_send_without_block       &amp;lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&amp;gt;
0006 pop
0007 getglobal                    :$stdout                  (   2)[Li]
0009 putstring                    &amp;#34;write&amp;#34;
0011 putobject                    true
0013 opt_send_without_block       &amp;lt;calldata!mid:respond_to?, argc:2, ARGS_SIMPLE&amp;gt;
0015 pop
0016 getglobal                    :$stdout                  (   3)[Li]
0018 putobject                    :write
0020 putobject                    true
0022 opt_send_without_block       &amp;lt;calldata!mid:respond_to?, argc:2, ARGS_SIMPLE&amp;gt;
0024 leave
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It doesn&amp;rsquo;t make a huge difference. The string version is identical, except we see &lt;code&gt;&amp;quot;write&amp;quot;&lt;/code&gt; instead of &lt;code&gt;:write&lt;/code&gt; in the &lt;code&gt;putobject&lt;/code&gt; call. The boolean versions add an additional &lt;code&gt;putobject&lt;/code&gt; which pushes the boolean onto the stack. Then the &lt;code&gt;opt_send_without_block&lt;/code&gt; call has &lt;code&gt;argc:2&lt;/code&gt; instead of &lt;code&gt;argc:1&lt;/code&gt;. Good to understand, but functionally the same.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to keep these explorations shorter, and break them into parts, so i&amp;rsquo;m going to stop here. So far we&amp;rsquo;ve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identified that I want to create an &lt;code&gt;opt_respond_to&lt;/code&gt; instruction for the Ruby VM&lt;/li&gt;
&lt;li&gt;Compiled a simple &lt;code&gt;respond_to?&lt;/code&gt; example, and examined what the current bytecode looks like&lt;/li&gt;
&lt;li&gt;Identified what I &lt;em&gt;think&lt;/em&gt; are the relevant instructions that need to be converted to &lt;code&gt;opt_respond_to&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next up, i&amp;rsquo;m going to walk through the path CRuby takes from starting the program, to compiling our code, starting with &lt;code&gt;main.c&lt;/code&gt;. Here we go!&lt;/p&gt;
</description>
      <source:markdown>[@byroot](https://bsky.app/profile/byroot.bsky.social) has been posting [a series on optimizations he and others have made](https://byroot.github.io/ruby/json/2024/12/15/optimizing-ruby-json-part-1.html) to the [json](https://github.com/ruby/json) gem, and it&#39;s been 🔥🔥🔥. Enjoyable and informative, I highly recommend reading what he&#39;s posted so far.

In his [second post](https://byroot.github.io/ruby/json/2024/12/18/optimizing-ruby-json-part-2.html#inline-caches), he mentions the possibility of improving performance by adding an additional method cache. This would involve compiling `respond_to?` calls in a special way:

&gt; It actually wouldn’t be too hard to add such a cache, we’d need to modify the Ruby compiler to compile respond_to? calls into a specialized opt_respond_to instruction that does have two caches instead of one. The first cache would be used to look up respond_to? on the object to make sure it wasn’t redefined, and the second one to look up the method we’re interested in. Or perhaps even 3 caches, as you also need to check if the object has a respond_to_missing? method defined in some cases.
&gt;
&gt; That’s an idea I remember discussing in the past with some fellow committers, but I can’t quite remember if there was a reason we didn’t do it yet.

Inspired by his comment, I&#39;m going to add a new bytecode instruction - `opt_respond_to` - to the Ruby VM, for fun. I don&#39;t know how to add a new bytecode instruction (yet). I don&#39;t know if one would get accepted by the Ruby team. I don&#39;t know if adding it will actually provide a meaningful enhancement to performance. But let&#39;s give it a try, shall we?

### Understanding the requirements

I know I want to add a new Ruby Virtual Machine bytecode called `opt_respond_to`. What does code using `respond_to?` look like today, after being compiled? Here&#39;s some code to evaluate:


```ruby
if $stdout.respond_to?(:write)
  puts &#34;Did you know you can write to $stdout?&#34;
end
```
We can compile it using `RubyVM::InstructionSequence`. We `compile` the code, then we `disassemble` it to see the actual Ruby bytecode:


```ruby
puts RubyVM::InstructionSequence.compile(DATA.read).disassemble

__END__
if $stdout.respond_to?(:write)
  puts &#34;Did you know you can write to $stdout?&#34;
end
```
&gt; 📝 The `__END__` format is just a convenient way of supplying some text to your program. Here we put all our Ruby code we want to compile after `__END__` and it will be available to our program as an IO object called `DATA`. Thanks to [Drew Bragg](https://bsky.app/profile/drbragg.dev/post/3lcdf5deh7k2a) for the tip!


Compiling using `InstructionSequence` gives us:


```text
== disasm: #&lt;ISeq:&lt;compiled&gt;@&lt;compiled&gt;:1 (1,0)-(3,3)&gt;
0000 getglobal                    :$stdout                  (   1)[Li]
0002 putobject                    :write
0004 opt_send_without_block       &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
0006 branchunless                 14
0008 putself                                                (   2)[Li]
0009 putstring                    &#34;Did you know you can write to $stdout?&#34;
0011 opt_send_without_block       &lt;calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE&gt;
0013 leave
0014 putnil
0015 leave
```
I&#39;m going to guess at the parts I think matter most - `putobject`, `opt_send_without_block` and `branchunless`.


- `putobject` pushes the symbol `:write` onto the vm stack
- `opt_send_without_block` is given `&lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;`. I&#39;m guessing `calldata` is a format for specifying metadata about what is being called. We&#39;ve got the method name, `respond_to?`, how many args are being used, `argc:1`, and that the arguments are &#34;simple&#34;, `ARGS_SIMPLE`. `mid` stands for... &#34;method id&#34;?
- `branchunless` wouldn&#39;t be specifically related to creating a new instruction, but I just think it&#39;s informative for how the `respond_to?` result is used. I believe it means &#34;if the last result is false, jump to instruction 14&#34;. 14 in this case is the `putnil` near the bottom of the bytecode. Each instruction seems to be prefixed with a hex value that identifies the location of the instruction. `putobject` is located at `0002`, `opt_send_without_block` is located at `0004`, `branchunless` is located at `0006` and `putnil` is located at `0014`


I think the only thing I will be changing is taking calls to `respond_to?`, and making that a `opt_respond_to` bytecode instead of `opt_send_without_block`. We&#39;ll see!


There are actually a few variations for `respond_to?` that I wasn&#39;t aware of. The interface takes a symbol or a string as the first parameter, and then a boolean for whether to include `private` and `protected` methods:


```ruby
$stdout.respond_to?(&#34;write&#34;)
$stdout.respond_to?(&#34;write&#34;, true)
$stdout.respond_to?(:write, true)
```
Let&#39;s see the bytecode for these variations:


```text
== disasm: #&lt;ISeq:&lt;compiled&gt;@&lt;compiled&gt;:1 (1,0)-(3,33)&gt;
0000 getglobal                    :$stdout                  (   1)[Li]
0002 putstring                    &#34;write&#34;
0004 opt_send_without_block       &lt;calldata!mid:respond_to?, argc:1, ARGS_SIMPLE&gt;
0006 pop
0007 getglobal                    :$stdout                  (   2)[Li]
0009 putstring                    &#34;write&#34;
0011 putobject                    true
0013 opt_send_without_block       &lt;calldata!mid:respond_to?, argc:2, ARGS_SIMPLE&gt;
0015 pop
0016 getglobal                    :$stdout                  (   3)[Li]
0018 putobject                    :write
0020 putobject                    true
0022 opt_send_without_block       &lt;calldata!mid:respond_to?, argc:2, ARGS_SIMPLE&gt;
0024 leave
```
It doesn&#39;t make a huge difference. The string version is identical, except we see `&#34;write&#34;` instead of `:write` in the `putobject` call. The boolean versions add an additional `putobject` which pushes the boolean onto the stack. Then the `opt_send_without_block` call has `argc:2` instead of `argc:1`. Good to understand, but functionally the same.


I&#39;m going to keep these explorations shorter, and break them into parts, so i&#39;m going to stop here. So far we&#39;ve:


- Identified that I want to create an `opt_respond_to` instruction for the Ruby VM
- Compiled a simple `respond_to?` example, and examined what the current bytecode looks like
- Identified what I _think_ are the relevant instructions that need to be converted to `opt_respond_to`


Next up, i&#39;m going to walk through the path CRuby takes from starting the program, to compiling our code, starting with `main.c`. Here we go!

</source:markdown>
    </item>
    
    <item>
      <title>20 days of ruby gems: part 1</title>
      <link>https://jpcamara.com/2024/12/16/days-of-ruby.html</link>
      <pubDate>Mon, 16 Dec 2024 20:12:46 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/16/days-of-ruby.html</guid>
      <description>&lt;p&gt;Over on BlueSky, &lt;a href=&#34;https://bsky.app/profile/skillstopractice.com&#34;&gt;Gregory Brown&lt;/a&gt; suggested a &lt;a href=&#34;https://bsky.app/hashtag/20DayGemChallenge&#34;&gt;#20daygemchallenge&lt;/a&gt;. Post gems you’ve either used time and time again, or have inspired you in some way, in no particular order. &lt;a href=&#34;https://bsky.app/profile/onghu.com&#34;&gt;Mohit Sindhwani&lt;/a&gt; suggested writing about them at the end, which sounded like a great idea!&lt;/p&gt;
&lt;p&gt;I’m breaking it into two parts. Here’s my breakdown of my first 10 gems posted:&lt;/p&gt;
&lt;p&gt;First &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lclvyj2ohs2c&#34;&gt;is HTTParty&lt;/a&gt;: &lt;a href=&#34;https://github.com/jnunemaker/httparty&#34;&gt;https://github.com/jnunemaker/httparty&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;HTTPart is the OG http gem. It’s widely used and dead simple. There are a million http options out there, but HTTParty still remains a simple, common option. It sits on top of &lt;code&gt;Net::HTTP&lt;/code&gt;, so it has the same &lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html#dont-use-timeout&#34;&gt;timeout&lt;/a&gt; concern as any &lt;code&gt;Net::HTTP&lt;/code&gt; usage. But &lt;em&gt;many&lt;/em&gt; Ruby http gems use &lt;code&gt;Net::HTTP&lt;/code&gt;, so that’s not a particular knock against it!&lt;/p&gt;
&lt;p&gt;Second &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lcoa52ixyk2x&#34;&gt;is Async&lt;/a&gt;: &lt;a href=&#34;https://github.com/socketry/async&#34;&gt;https://github.com/socketry/async&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Async is the de facto &lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html#async-aside&#34;&gt;FiberScheduler&lt;/a&gt; for Ruby. That’s what allows Fibers to parallelize blocking operations in Ruby 3+. It’s a great gem and a great ecosystem of tools as well, particularly revolving around all things IO and web protocols.&lt;/p&gt;
&lt;p&gt;I talk about it in more detail in my &lt;a href=&#34;https://jpcamara.com/2024/12/14/my-rubyconf-talk.html&#34;&gt;In-Depth Ruby Concurrency&lt;/a&gt; talk from RubyConf and I’ll have future articles about it as well.&lt;/p&gt;
&lt;p&gt;Third &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lcqbrqimfk2z&#34;&gt;is Pitchfork&lt;/a&gt;: &lt;a href=&#34;https://github.com/Shopify/pitchfork&#34;&gt;https://github.com/Shopify/pitchfork&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pitchfork is an evolution of the Unicorn web server. Its innovation is a forking technique called “Reforking”, where processes are forked multiple times from existing “warm” processes, getting to a point of maximally optimized Copy-on-Write performance.&lt;/p&gt;
&lt;p&gt;Like async, I talk about it in more detail in my &lt;a href=&#34;https://jpcamara.com/2024/12/14/my-rubyconf-talk.html&#34;&gt;In-Depth Ruby Concurrency&lt;/a&gt; talk from RubyConf and I’ll have future articles about it as well.&lt;/p&gt;
&lt;p&gt;Fourth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lcsemdic3k2r&#34;&gt;is Ractor TVar&lt;/a&gt;: &lt;a href=&#34;https://github.com/ko1/ractor-tvar&#34;&gt;https://github.com/ko1/ractor-tvar&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ractor TVar is an implementation of &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Software_transactional_memory&#34;&gt;software transactional memory&lt;/a&gt; in Ruby. I learned about it from the Mooro gem, which is a later gem pick. It’s a fascinating and largely unknown library that koichi seemed to have released alongside Ractors and not updated since. I’m very curious to read the source more to better understand how it works and maybe even give it a try in some real code. It only documents examples supporting Ractors but claims to also support threads.&lt;/p&gt;
&lt;p&gt;Fifth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lcusuj5pzc2o&#34;&gt;is Strong Migrations&lt;/a&gt;: &lt;a href=&#34;https://github.com/ankane/strong_migrations&#34;&gt;https://github.com/ankane/strong_migrations&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I prefer my database migrations to be stress-free and zero-downtime. The strong migrations gem helps me sleep at night. It detects unsafe operations and blocks them from being run, offering safe alternatives.&lt;/p&gt;
&lt;p&gt;There are a few other gems that help keep migrations safe, but I prefer the explicit style of strong migrations. Most other options are a bit “magical”, and will try to rewrite things for you.&lt;/p&gt;
&lt;p&gt;Sixth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lcxfpa53hk24&#34;&gt;is ZipKit&lt;/a&gt;: &lt;a href=&#34;https://github.com/julik/zip_kit&#34;&gt;https://github.com/julik/zip_kit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’ve mostly used ZipTricks in the past, and ZipKit is the successor to that gem. Being able to stream writes to a zip file is amazing for scaling and ZipKit makes it dead simple.&lt;/p&gt;
&lt;p&gt;Using it you can, for instance, stream a file to S3, zipping it on the fly as it’s being uploaded! Streaming is the only way you can reasonably manage operations on very large files, so having this option is critical.&lt;/p&gt;
&lt;p&gt;Seventh &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3lczwvdvqhk2t&#34;&gt;is Falcon&lt;/a&gt;: &lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;https://github.com/socketry/falcon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Falcon is a web server based on the FiberScheduler (provided by the async gem). It’s a very scalable server, particularly for IO bound operations. There’s a great talk focusing on it from RailsConf 2023 called &lt;a href=&#34;https://youtu.be/27uVIIgguQg&#34;&gt;Look ma, no jobs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also did some benchmarking of its web socket performance compared to a node.js implementation and it is very close in performance!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/f9240bd15e.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here’s the code for it:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gist.github.com/jpcamara/8a1a09c9c67347c4e32384b9ce806b70&#34;&gt;https://gist.github.com/jpcamara/8a1a09c9c67347c4e32384b9ce806b70&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Like async and pitchfork, I talk about it in more detail in my &lt;a href=&#34;https://jpcamara.com/2024/12/14/my-rubyconf-talk.html&#34;&gt;In-Depth Ruby Concurrency&lt;/a&gt; talk from RubyConf and I’ll have future articles about it as well.&lt;/p&gt;
&lt;p&gt;Eighth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3ld5hvh6lt22u&#34;&gt;is OJ&lt;/a&gt;: &lt;a href=&#34;https://github.com/ohler55/oj&#34;&gt;https://github.com/ohler55/oj&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;OJ has historically been the fastest JSON parser in the Ruby world. Usually you can just drop it into a project as a JSON replacement and see things immediately speed up, especially if you are doing any heavy JSON processing.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/ruby/json&#34;&gt;JSON&lt;/a&gt; gem was recently taken over by the Ruby GitHub organization and &lt;a href=&#34;https://bsky.app/profile/byroot.bsky.social&#34;&gt;byroot&lt;/a&gt; has been making some big performance improvements to it - maybe we’ll see parity at some point but OJ is still a great choice.&lt;/p&gt;
&lt;p&gt;Ninth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3ldaheo6qvs27&#34;&gt;is io-event&lt;/a&gt;: &lt;a href=&#34;https://github.com/socketry/io-event&#34;&gt;https://github.com/socketry/io-event&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The async gem is the public interface, but io-event is what powers the scheduling at the OS level. It provides all of the integrations with each operating systems kernel event queue: io_uring and epoll for Linux, and kqueue for MacOS. IOCP support for windows is still in progress, so it falls back to a basic Ruby &lt;code&gt;select&lt;/code&gt; there. If you don’t know why any of that is useful, it’s because it’s an important part of keeping the “Reactor” pattern of asynchronous IO efficient.&lt;/p&gt;
&lt;p&gt;Like async, pitchfork and falcon (😅), I talk about the reactor pattern in more detail in my &lt;a href=&#34;https://jpcamara.com/2024/12/14/my-rubyconf-talk.html&#34;&gt;In-Depth Ruby Concurrency&lt;/a&gt; talk from RubyConf and I’ll have future articles about it as well. I obviously like concurrency 🙂.&lt;/p&gt;
&lt;p&gt;Tenth &lt;a href=&#34;https://bsky.app/profile/jpcamara.com/post/3ldbmqyjzss27&#34;&gt;is Glimmer&lt;/a&gt;: &lt;a href=&#34;https://github.com/AndyObtiva/glimmer&#34;&gt;https://github.com/AndyObtiva/glimmer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I wasn’t familiar with Glimmer but I learned about it at RubyConf. It’s a DSL for building UIs with pure Ruby and has bindings for desktop app ui layers as well as the web. It’s a really cool concept and I look forward to learning more about it by watching &lt;a href=&#34;https://youtu.be/TTSqRdTVtDY&#34;&gt;How to build basic desktop applications in Ruby&lt;/a&gt;. I’ve been working on a cross platform app using React Native and Tauri - maybe I’ll port some of it to Glimmer as an experiment.&lt;/p&gt;
&lt;p&gt;After I finish 11 through 20, I’ll post about them as well. Give these gems a try! 👋&lt;/p&gt;
</description>
      <source:markdown>Over on BlueSky, [Gregory Brown](https://bsky.app/profile/skillstopractice.com) suggested a [\#20daygemchallenge](https://bsky.app/hashtag/20DayGemChallenge). Post gems you’ve either used time and time again, or have inspired you in some way, in no particular order. [Mohit Sindhwani](https://bsky.app/profile/onghu.com) suggested writing about them at the end, which sounded like a great idea!

I’m breaking it into two parts. Here’s my breakdown of my first 10 gems posted:

First [is HTTParty](https://bsky.app/profile/jpcamara.com/post/3lclvyj2ohs2c): [https://github.com/jnunemaker/httparty](https://github.com/jnunemaker/httparty)

HTTPart is the OG http gem. It’s widely used and dead simple. There are a million http options out there, but HTTParty still remains a simple, common option. It sits on top of `Net::HTTP`, so it has the same [timeout](https://jpcamara.com/2024/08/26/the-thread-api.html#dont-use-timeout) concern as any `Net::HTTP` usage. But _many_ Ruby http gems use `Net::HTTP`, so that’s not a particular knock against it!

Second [is Async](https://bsky.app/profile/jpcamara.com/post/3lcoa52ixyk2x): [https://github.com/socketry/async](https://github.com/socketry/async)

Async is the de facto [FiberScheduler](https://jpcamara.com/2024/07/15/ruby-methods-are.html#async-aside) for Ruby. That’s what allows Fibers to parallelize blocking operations in Ruby 3+. It’s a great gem and a great ecosystem of tools as well, particularly revolving around all things IO and web protocols.

I talk about it in more detail in my [In-Depth Ruby Concurrency](https://jpcamara.com/2024/12/14/my-rubyconf-talk.html) talk from RubyConf and I’ll have future articles about it as well.

Third [is Pitchfork](https://bsky.app/profile/jpcamara.com/post/3lcqbrqimfk2z): [https://github.com/Shopify/pitchfork](https://github.com/Shopify/pitchfork)

Pitchfork is an evolution of the Unicorn web server. Its innovation is a forking technique called “Reforking”, where processes are forked multiple times from existing “warm” processes, getting to a point of maximally optimized Copy-on-Write performance. 

Like async, I talk about it in more detail in my [In-Depth Ruby Concurrency](https://jpcamara.com/2024/12/14/my-rubyconf-talk.html) talk from RubyConf and I’ll have future articles about it as well.

Fourth [is Ractor TVar](https://bsky.app/profile/jpcamara.com/post/3lcsemdic3k2r): [https://github.com/ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)

Ractor TVar is an implementation of [software transactional memory](https://en.m.wikipedia.org/wiki/Software_transactional_memory) in Ruby. I learned about it from the Mooro gem, which is a later gem pick. It’s a fascinating and largely unknown library that koichi seemed to have released alongside Ractors and not updated since. I’m very curious to read the source more to better understand how it works and maybe even give it a try in some real code. It only documents examples supporting Ractors but claims to also support threads.

Fifth [is Strong Migrations](https://bsky.app/profile/jpcamara.com/post/3lcusuj5pzc2o): [https://github.com/ankane/strong\_migrations](https://github.com/ankane/strong_migrations)

I prefer my database migrations to be stress-free and zero-downtime. The strong migrations gem helps me sleep at night. It detects unsafe operations and blocks them from being run, offering safe alternatives. 

There are a few other gems that help keep migrations safe, but I prefer the explicit style of strong migrations. Most other options are a bit “magical”, and will try to rewrite things for you.

Sixth [is ZipKit](https://bsky.app/profile/jpcamara.com/post/3lcxfpa53hk24): [https://github.com/julik/zip\_kit](https://github.com/julik/zip_kit)

I’ve mostly used ZipTricks in the past, and ZipKit is the successor to that gem. Being able to stream writes to a zip file is amazing for scaling and ZipKit makes it dead simple. 

Using it you can, for instance, stream a file to S3, zipping it on the fly as it’s being uploaded! Streaming is the only way you can reasonably manage operations on very large files, so having this option is critical.

Seventh [is Falcon](https://bsky.app/profile/jpcamara.com/post/3lczwvdvqhk2t): [https://github.com/socketry/falcon](https://github.com/socketry/falcon)

Falcon is a web server based on the FiberScheduler (provided by the async gem). It’s a very scalable server, particularly for IO bound operations. There’s a great talk focusing on it from RailsConf 2023 called [Look ma, no jobs](https://youtu.be/27uVIIgguQg).

I also did some benchmarking of its web socket performance compared to a node.js implementation and it is very close in performance!

![](https://cdn.uploads.micro.blog/98548/2024/f9240bd15e.png)

Here’s the code for it:

[https://gist.github.com/jpcamara/8a1a09c9c67347c4e32384b9ce806b70](https://gist.github.com/jpcamara/8a1a09c9c67347c4e32384b9ce806b70)

Like async and pitchfork, I talk about it in more detail in my [In-Depth Ruby Concurrency](https://jpcamara.com/2024/12/14/my-rubyconf-talk.html) talk from RubyConf and I’ll have future articles about it as well.

Eighth [is OJ](https://bsky.app/profile/jpcamara.com/post/3ld5hvh6lt22u): [https://github.com/ohler55/oj](https://github.com/ohler55/oj)

OJ has historically been the fastest JSON parser in the Ruby world. Usually you can just drop it into a project as a JSON replacement and see things immediately speed up, especially if you are doing any heavy JSON processing.

The [JSON](https://github.com/ruby/json) gem was recently taken over by the Ruby GitHub organization and [byroot](https://bsky.app/profile/byroot.bsky.social) has been making some big performance improvements to it - maybe we’ll see parity at some point but OJ is still a great choice.

Ninth [is io-event](https://bsky.app/profile/jpcamara.com/post/3ldaheo6qvs27): [https://github.com/socketry/io-event](https://github.com/socketry/io-event)

The async gem is the public interface, but io-event is what powers the scheduling at the OS level. It provides all of the integrations with each operating systems kernel event queue: io\_uring and epoll for Linux, and kqueue for MacOS. IOCP support for windows is still in progress, so it falls back to a basic Ruby `select` there. If you don’t know why any of that is useful, it’s because it’s an important part of keeping the “Reactor” pattern of asynchronous IO efficient. 

Like async, pitchfork and falcon (😅), I talk about the reactor pattern in more detail in my [In-Depth Ruby Concurrency](https://jpcamara.com/2024/12/14/my-rubyconf-talk.html) talk from RubyConf and I’ll have future articles about it as well. I obviously like concurrency 🙂.

Tenth [is Glimmer](https://bsky.app/profile/jpcamara.com/post/3ldbmqyjzss27): [https://github.com/AndyObtiva/glimmer](https://github.com/AndyObtiva/glimmer)

I wasn’t familiar with Glimmer but I learned about it at RubyConf. It’s a DSL for building UIs with pure Ruby and has bindings for desktop app ui layers as well as the web. It’s a really cool concept and I look forward to learning more about it by watching [How to build basic desktop applications in Ruby](https://youtu.be/TTSqRdTVtDY). I’ve been working on a cross platform app using React Native and Tauri - maybe I’ll port some of it to Glimmer as an experiment.

After I finish 11 through 20, I’ll post about them as well. Give these gems a try! 👋 

</source:markdown>
    </item>
    
    <item>
      <title>My RubyConf talk is now on YouTube!</title>
      <link>https://jpcamara.com/2024/12/14/my-rubyconf-talk.html</link>
      <pubDate>Sat, 14 Dec 2024 22:40:45 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/14/my-rubyconf-talk.html</guid>
      <description>&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-15-at-10.28.24pm.png&#34; width=&#34;600&#34; height=&#34;200&#34; alt=&#34;&#34;&gt;
&lt;p&gt;I was honored to present a talk on Ruby concurrency at RubyConf 2024. It represents a high-level distillation of much of my writing and research over the past year. The conference itself was great, and presenting was such a fun experience.&lt;/p&gt;
&lt;p&gt;Here is the talk, titled “In-Depth Ruby Concurrency: Navigating the Ruby Concurrency Landscape”:&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/rewiLd2w0kE?si=p8DBcuUqqXnmsnlO&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;If you want the slides, I’ve published them as a short YouTube video:&lt;/p&gt;
&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/6HaXuQJMcvs?si=ckoz8lZKr47vQ-hy&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;I know it’s a bit odd to share my slides as a video, but there are so many animations it was the easiest option to preserve the original flow.&lt;/p&gt;
&lt;p&gt;You can also find information about my talk and every other RubyConf 2024 talk at &lt;a href=&#34;https://www.rubyvideo.dev/talks/in-depth-ruby-concurrency-navigating-the-ruby-concurrency-landscape&#34;&gt;RubyVideo&lt;/a&gt;.&lt;/p&gt;
</description>
      <source:markdown>&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-15-at-10.28.24pm.png&#34; width=&#34;600&#34; height=&#34;200&#34; alt=&#34;&#34;&gt;

I was honored to present a talk on Ruby concurrency at RubyConf 2024. It represents a high-level distillation of much of my writing and research over the past year. The conference itself was great, and presenting was such a fun experience.

Here is the talk, titled “In-Depth Ruby Concurrency: Navigating the Ruby Concurrency Landscape”:

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/rewiLd2w0kE?si=p8DBcuUqqXnmsnlO&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;

If you want the slides, I’ve published them as a short YouTube video:

&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/6HaXuQJMcvs?si=ckoz8lZKr47vQ-hy&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;

I know it’s a bit odd to share my slides as a video, but there are so many animations it was the easiest option to preserve the original flow.

You can also find information about my talk and every other RubyConf 2024 talk at [RubyVideo](https://www.rubyvideo.dev/talks/in-depth-ruby-concurrency-navigating-the-ruby-concurrency-landscape).

</source:markdown>
    </item>
    
    <item>
      <title>Speeding up Ruby by rewriting C… in Ruby</title>
      <link>https://jpcamara.com/2024/12/01/speeding-up-ruby.html</link>
      <pubDate>Wed, 04 Dec 2024 00:29:25 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/01/speeding-up-ruby.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/yjitvsc.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There is a recent &lt;a href=&#34;https://github.com/bddicken/languages&#34;&gt;language comparison repo&lt;/a&gt; which has been getting shared a lot. In it, CRuby was the third slowest option, only beating out R and Python.&lt;/p&gt;
&lt;p&gt;The repo author, &lt;a href=&#34;https://x.com/BenjDicken&#34;&gt;@BenjDicken&lt;/a&gt;, &lt;a href=&#34;https://x.com/BenjDicken/status/1861072804239847914&#34;&gt;created a fun visualization&lt;/a&gt; of each language’s performance. Here’s one of the visualizations, which shows Ruby as the third slowest language benchmarked:&lt;/p&gt;
&lt;div id=&#34;original_visual&#34;&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;The code for this visualization is from &lt;a href=&#34;https://benjdd.com/languages/&#34;&gt;https://benjdd.com/languages/&lt;/a&gt;, with permission from &lt;a href=&#34;https://x.com/BenjDicken/status/1862623583803253149&#34;&gt;@BenjDicken&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The repository describes itself as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A repo for collaboratively building small benchmarks to compare languages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It contains two different benchmarks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“Loops”, which “Emphasizes loop, conditional, and basic math performance”&lt;/li&gt;
&lt;li&gt;“Fibonacci”, which “Emphasizes function call overhead and recursion.”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The loop example iterates 1 billion times, utilizing a nested loop:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;to_i       
r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rand(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)                          
a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)                 
	
(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;                     
  (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100_000&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;j&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;               
    a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; u                     
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; r                      
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
puts a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Fibonacci example is a basic “naive” Fibonacci implementation&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fibonacci(n)
  return 0 if n == 0
  return 1 if n == 1
  fibonacci(n - 1) + fibonacci(n - 2)
end

u = ARGV[0].to_i
r = 0

(1...u).each do |i|
  r += fibonacci(i)
end

puts r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run on &lt;a href=&#34;https://x.com/BenjDicken&#34;&gt;@BenjDicken&lt;/a&gt;’s M3 MacBook Pro, Ruby 3.3.6 takes 28 seconds to run the loop iteration example, and 12 seconds to run the Fibonacci example. For comparison, node.js takes a little over a second for both examples - it’s not a great showing for Ruby.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;Fibonacci&lt;/th&gt;
      &lt;th&gt;Loops&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;12.17s&lt;/td&gt;
      &lt;td&gt;28.80s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;node.js&lt;/td&gt;
      &lt;td&gt;1.11s&lt;/td&gt;
      &lt;td&gt;1.03s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;From this point on, I’ll use benchmarks relative to my own computer. Running the same benchmark on my M2 MacBook Air, I get 33.43 seconds for the loops and 16.33 seconds for fibonacci - even worse 🥺. Node runs a little over 1 second for fibonacci and 2 seconds for the loop example.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;Fibonacci&lt;/th&gt;
      &lt;th&gt;Loops&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;16.33s&lt;/td&gt;
      &lt;td&gt;33.43s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;node.js&lt;/td&gt;
      &lt;td&gt;1.36s&lt;/td&gt;
      &lt;td&gt;2.07s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;who-cares&#34;&gt;Who cares?&lt;/h3&gt;
&lt;p&gt;In most ways, these types of benchmarks are meaningless. Python was the slowest language in the benchmark, and yet at the same time it’s the &lt;a href=&#34;https://github.blog/news-insights/octoverse/octoverse-2024/&#34;&gt;most used language on Github as of October 2024&lt;/a&gt;. Ruby runs some of the &lt;a href=&#34;https://x.com/tobi/status/1863935229620363693&#34;&gt;largest web apps in the world&lt;/a&gt;. I ran a &lt;a href=&#34;https://x.com/jpcamara/status/1849984009515966958&#34;&gt;benchmark recently of websocket performance between the Ruby Falcon web server and node.js&lt;/a&gt;, and the Ruby results were close to the node.js results. Are you doing a billion loop iterations or using web sockets?&lt;/p&gt;
&lt;p&gt;A programming language should be reasonably efficient - after that the usefulness of the language, the type of tasks you work on, and language productivity outweigh the speed at which you can run a billion iterations of a loop, or complete an intentionally inefficient implementation of a Fibonacci method.&lt;/p&gt;
&lt;p&gt;That said:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The programming world loves microbenchmarks 🤷‍♂️&lt;/li&gt;
&lt;li&gt;Having a fast benchmark may not be valuable in practice but it has meaning for people’s interest in a language. Some would claim it means you’ll have an easier time scaling performance, but that’s arguable&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;It’s disappointing if your language of choice doesn’t perform well. It’s nice to be able to say “I use and enjoy this language, and it runs fast in all benchmarks!”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the case of this Ruby benchmark, I had a feeling that YJIT wasn’t being applied in the Ruby code, so I checked the repo. Lo and behold, the command was as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ruby ./code.rb 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We know my results from earlier (33 seconds and 16 seconds). What do we get with YJIT applied?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ruby --yjit ./code.rb 40
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
&lt;th&gt;Fibonacci&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;2.06s&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Nice! With YJIT, Fibonacci gets a massive boost - going from 16.88 seconds down to 2.06 seconds. It’s close to the speed of node.js at that point!&lt;/p&gt;
&lt;p&gt;YJIT makes a more modest difference for the looping example - going from 33.43 seconds down to 25.57 seconds. Why is that?&lt;/p&gt;
&lt;h3 id=&#34;a-team-effort&#34;&gt;A team effort&lt;/h3&gt;
&lt;p&gt;I wasn’t alone in trying out these code samples with YJIT. On twitter, &lt;a href=&#34;https://x.com/bsilva96&#34;&gt;@bsilva96&lt;/a&gt; had asked the same questions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-01-at-8.38.46pm.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://x.com/bsilva96/status/1861136096689606708&#34;&gt;https://x.com/bsilva96/status/1861136096689606708&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://bsky.app/profile/k0kubun.com&#34;&gt;@k0kubun&lt;/a&gt; came through with insights into why things were slow and ways of improving the performance:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-01-at-8.41.03pm.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://x.com/k0kubun/status/1861149512640979260&#34;&gt;https://x.com/k0kubun/status/1861149512640979260&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s unpack his response. There are three parts to it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Range#each&lt;/code&gt; is still written in C as of Ruby 3.4&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Integer#times&lt;/code&gt; was converted from C to Ruby in Ruby 3.3&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Array#each&lt;/code&gt; was converted from C to Ruby in Ruby 3.4&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;1-rangeeach-is-still-written-in-c-which-yjit-cant-optimize&#34;&gt;1. &lt;code&gt;Range#each&lt;/code&gt; is still written in C, which YJIT can’t optimize&lt;/h3&gt;
&lt;p&gt;Looking back at our Ruby code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(0...10_000).each do |i|                     
  (0...100_000).each do |j|               
    a[i] += j % u                     
  end
  a[i] += r                      
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s written as a range, and range has its own &lt;code&gt;each&lt;/code&gt; implementation, which is apparently written in C. The CRuby codebase is pretty easy to navigate - let’s find that implementation 🕵️‍♂️.&lt;/p&gt;
&lt;p&gt;Most core classes in Ruby have top-level C files named after them - in this case we’ve got &lt;code&gt;range.c&lt;/code&gt; at the root of the project. CRuby has a pretty readable interface for exposing C functions as classes and methods - there is an &lt;code&gt;Init&lt;/code&gt; function, usually at the bottom of the file. Inside that &lt;code&gt;Init&lt;/code&gt; our classes, modules and methods are exposed from C to Ruby. Here are the relevant pieces of &lt;code&gt;Init_Range&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void
Init_Range(void)
{
  //...
  rb_cRange = rb_struct_define_without_accessor(
    &amp;quot;Range&amp;quot;, rb_cObject, range_alloc,
    &amp;quot;begin&amp;quot;, &amp;quot;end&amp;quot;, &amp;quot;excl&amp;quot;, NULL);

  rb_include_module(rb_cRange, rb_mEnumerable);
  // ...
  rb_define_method(rb_cRange, &amp;quot;each&amp;quot;, range_each, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, we define our &lt;code&gt;Range&lt;/code&gt; class using &lt;code&gt;rb_struct_define...&lt;/code&gt;. We name it &lt;code&gt;“Range”&lt;/code&gt;, with a super class of &lt;code&gt;Object&lt;/code&gt; (&lt;code&gt;rb_cObject&lt;/code&gt;), and some initialization parameters (&lt;code&gt;“begin”&lt;/code&gt;, &lt;code&gt;“end”&lt;/code&gt; and whether to exclude the last value, ie the &lt;code&gt;..&lt;/code&gt; vs &lt;code&gt;...&lt;/code&gt; range syntax).&lt;/p&gt;
&lt;p&gt;Second, we include &lt;code&gt;Enumerable&lt;/code&gt; using &lt;code&gt;rb_include_module&lt;/code&gt;. That gives us all the great Ruby enumeration methods like &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt;, &lt;code&gt;include?&lt;/code&gt; and &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Enumerable.html&#34;&gt;a bajillion others&lt;/a&gt;. All you have to do is provide an &lt;code&gt;each&lt;/code&gt; implementation and it handles the rest.&lt;/p&gt;
&lt;p&gt;Third, we define our &lt;code&gt;“each”&lt;/code&gt; method. It’s implemented by the &lt;code&gt;range_each&lt;/code&gt; function in C, and takes zero explicit arguments (blocks are not considered in this count).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;range_each&lt;/code&gt; is hefty. It’s almost 100 lines long, and specializes into several versions of itself. I’ll highlight a few, collapsed all together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static VALUE
range_each(VALUE range)
{
  //...
  range_each_fixnum_endless(beg);
  range_each_fixnum_loop(beg, end, range);
  range_each_bignum_endless(beg);
  rb_str_upto_endless_each(beg, sym_each_i, 0);
  // and even more...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These C functions handle all the variations of ranges you might use in your own code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each
(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each
(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;z&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each
&lt;span style=&#34;color:#75715e&#34;&gt;# and on...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Why does it matter that &lt;code&gt;Range#each&lt;/code&gt; is written in C? It means YJIT can’t inspect it - optimizations stop at the function call and resume when the function call returns. C functions are fast, but YJIT can take things further by creating specializations for hot paths of code. There is a great article from Aaron Patterson called &lt;a href=&#34;https://railsatscale.com/2023-08-29-ruby-outperforms-c/&#34;&gt;Ruby Outperforms C&lt;/a&gt; where you can learn more about some of those specialized optimizations.&lt;/p&gt;
&lt;h3 id=&#34;2-optimizing-our-loop-integertimes-was-converted-from-c-to-ruby-in-ruby-33&#34;&gt;2. Optimizing our loop: &lt;code&gt;Integer#times&lt;/code&gt; was converted from C to Ruby in Ruby 3.3&lt;/h3&gt;
&lt;p&gt;The hot path (&lt;em&gt;where most of our CPU time is spent&lt;/em&gt;) is &lt;code&gt;Range#each&lt;/code&gt;, which is a C function. YJIT can’t optimize C functions - they’re a black box. So what can we do?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We converted Integer#times to Ruby in 3.3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Interesting! In Ruby 3.3, &lt;code&gt;Integer#times&lt;/code&gt; was &lt;a href=&#34;https://github.com/ruby/ruby/pull/8388&#34;&gt;converted from a C function to a Ruby method&lt;/a&gt;! Here’s the 3.3+ version - its pretty simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def times
  #... a little C interop code
  i = 0
  while i &amp;lt; self
    yield i
    i = i.succ
  end
  self
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Very simple. It’s just a basic while loop. Most importantly, it’s all Ruby code, which means YJIT should be able to introspect and optimize it!&lt;/p&gt;
&lt;h3 id=&#34;an-aside-on-integersucc&#34;&gt;An aside on &lt;code&gt;Integer#succ&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The slightly odd part of that code is &lt;code&gt;i.succ&lt;/code&gt;. I’d never heard of &lt;code&gt;Integer#succ&lt;/code&gt;, which apparently gives you the “successor” to an integer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/0c8bd56f64.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’ve never seen this show, and yet it’s the first thing I thought of when I learned about this method. Thanks, advertising.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There was a PR to improve the performance of &lt;code&gt;Integer#succ&lt;/code&gt; in early 2024, which helped me understand why anyone would ever use it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We use Integer#succ when we rewrite loop methods in Ruby (e.g. Integer#times and Array#each) because opt_succ (i = i.succ) is faster to dispatch on the interpreter than putobject 1; opt_plus (i += 1).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ruby/ruby/pull/9519&#34;&gt;https://github.com/ruby/ruby/pull/9519&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;Integer#succ&lt;/code&gt; is like a virtual machine cheat code. It takes a common operation (adding 1 to an integer) and turns it from two virtual machine operations into one. We can call &lt;code&gt;disasm&lt;/code&gt; on the &lt;code&gt;times&lt;/code&gt; method to see that in action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;puts RubyVM::InstructionSequence.disasm(1.method(:times))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Integer#times&lt;/code&gt; method gets broken down into a lot of Ruby VM bytecode, but we only care about a few lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
0025 getlocal_WC_0   i@0
0027 opt_succ        &amp;lt;calldata!mid:succ, ARGS_SIMPLE&amp;gt;[CcCr]
0029 setlocal_WC_0   i@0
...
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;getlocal_WC_0&lt;/code&gt; gets our &lt;code&gt;i&lt;/code&gt; variable from the current scope. That’s the &lt;code&gt;i&lt;/code&gt; in &lt;code&gt;i.succ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opt_succ&lt;/code&gt; performs the &lt;code&gt;succ&lt;/code&gt; call in our &lt;code&gt;i.succ&lt;/code&gt;. It will either call the actual &lt;code&gt;Integer#succ&lt;/code&gt; method, or an optimized C function for small numbers&lt;/li&gt;
&lt;li&gt;In Ruby 3.4 with YJIT enabled, small numbers get optimized even further into machine code (just a note, not shown in the VM machine code)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setlocal_WC_0&lt;/code&gt; sets the result of &lt;code&gt;opt_succ&lt;/code&gt; to our local variable &lt;code&gt;i&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we change from &lt;code&gt;i = i.succ&lt;/code&gt; to &lt;code&gt;i += 1&lt;/code&gt;, we now have two VM operations take the place of &lt;code&gt;opt_succ&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
0025 getlocal_WC_0        i@0
0027 putobject_INT2FIX_1_
0028 opt_plus             &amp;lt;calldata!mid:+, argc:1, ARGS_SIMPLE&amp;gt;
0029 setlocal_WC_0        i@0
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything is essentially the same as before, except now we have two steps to go through instead of one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;putobject_INT2FIX_1_&lt;/code&gt; pushes the integer &lt;code&gt;1&lt;/code&gt; onto the virtual machine stack&lt;/li&gt;
&lt;li&gt;&lt;code&gt;opt_plus&lt;/code&gt; is the &lt;code&gt;+&lt;/code&gt; in our &lt;code&gt;+= 1&lt;/code&gt;, and calls either the Ruby &lt;code&gt;+&lt;/code&gt; method or an optimized C function for small numbers&lt;/li&gt;
&lt;li&gt;There is probably a YJIT optimization for &lt;code&gt;opt_plus&lt;/code&gt; as well&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If there is nothing else to learn from this code, it’s this: the kinds of optimizations you do at the VM and JIT level are &lt;em&gt;deep&lt;/em&gt;. When writing general Ruby programs we typically don’t and &lt;em&gt;shouldn’t&lt;/em&gt; consider the impact of one versus two &lt;em&gt;machine code instructions&lt;/em&gt;. But at the JIT level, on the scale of millions and billions of operations, it matters!&lt;/p&gt;
&lt;h3 id=&#34;back-to-integertimes&#34;&gt;Back to &lt;code&gt;Integer#times&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Let’s try running our benchmark code again, using &lt;code&gt;times&lt;/code&gt;! Instead of iterating over ranges, we simply iterate for &lt;code&gt;10_000&lt;/code&gt; and &lt;code&gt;100_000&lt;/code&gt; &lt;code&gt;times&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;to_i        
r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rand(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)        
a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
	
&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
  &lt;span style=&#34;color:#ae81ff&#34;&gt;100_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;j&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
    a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; u
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; r
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
puts a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integer#times&lt;/td&gt;
&lt;td&gt;13.66s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Nice! YJIT makes a much larger impact using &lt;code&gt;Integer#times&lt;/code&gt;. That trims things down significantly, taking it down to 13.66 seconds on my machine. On &lt;a href=&#34;https://bsky.app/profile/k0kubun.com&#34;&gt;@k0kobun&lt;/a&gt;’s machine it actually goes down to 9 seconds (and 8 seconds on Ruby 3.4).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s probably Ruby 3.5’s job to make it faster than 8s though.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We might look forward to even faster performance in Ruby 3.5. We’ll see!&lt;/p&gt;
&lt;h3 id=&#34;3-arrayeach-was-converted-from-c-to-ruby-in-ruby-34&#34;&gt;3. &lt;code&gt;Array#each&lt;/code&gt; was converted from C to Ruby in Ruby 3.4&lt;/h3&gt;
&lt;p&gt;CRuby continues to see C code rewritten in Ruby, and in Ruby 3.4 &lt;code&gt;Array#each&lt;/code&gt; was one of those changes. Here is an &lt;a href=&#34;https://github.com/ruby/ruby/pull/6687/files&#34;&gt;example of the first attempt at implementing it&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def each
  unless block_given?
    return to_enum(:each) { self.length }
  end
  i = 0
  while i &amp;lt; self.length
    yield self[i]
    i = i.succ
  end
  self
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Super simple and readable! And YJIT optimizable!&lt;/p&gt;
&lt;p&gt;Unfortunately, due to something related to CRuby internals, it contained &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#race-conditions&#34;&gt;race conditions&lt;/a&gt;. A later implementation &lt;a href=&#34;https://github.com/ruby/ruby/pull/11955&#34;&gt;landed in Ruby 3.4&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def each
  Primitive.attr! :inline_block, :c_trace

  unless defined?(yield)
    return Primitive.cexpr! &#39;SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)&#39;
  end
  _i = 0
  value = nil
  while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
    yield value
  end
  self
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unlike the first implementation, and unlike &lt;code&gt;Integer#times&lt;/code&gt;, things are a bit more cryptic this time. This is definitely not pure Ruby code that anyone could be expected to write. Somehow, the &lt;code&gt;Primitive&lt;/code&gt; module seems to allow evaluating C code from Ruby, and in doing so avoids the race conditions present in the pure Ruby solution&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;By fetching indexes and values using C code, I think it results in a more atomic operation. I have no idea why the &lt;code&gt;Primitive.cexpr!&lt;/code&gt; is used to return the enumerator, or what value &lt;code&gt;Primitive.attr! :inline_block&lt;/code&gt; provides. Please comment if you have insights there!&lt;/p&gt;
&lt;p&gt;I was a little loose with my earlier &lt;code&gt;Integer#times&lt;/code&gt; source code as well. That actually had a bit of this &lt;code&gt;Primitive&lt;/code&gt; syntax as well. The core of the method is what we looked at, and it’s all Ruby, but the start of the method contains the same &lt;code&gt;Primitive&lt;/code&gt; calls for &lt;code&gt;:inline_block&lt;/code&gt; and returning the enumerator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def times
  Primitive.attr! :inline_block
  unless defined?(yield)
    return Primitive.cexpr! &#39;SIZED_ENUMERATOR(self, 0, 0, int_dotimes_size)&#39;
  end
  #...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok - it’s more cryptic than &lt;code&gt;Integer#times&lt;/code&gt; was, but &lt;code&gt;Array#each&lt;/code&gt; is mostly Ruby (on Ruby 3.4+). Let’s give it a try using arrays instead of ranges or &lt;code&gt;times&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;to_i
r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rand(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)
a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
	
outer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_a&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;freeze
inner &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;100_000&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_a&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;freeze
outer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
  inner&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;j&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
    a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; u
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; r
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
puts a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Despite the embedded C code, YJIT still seems capable of making some hefty performance optimizations. It’s within the same range as &lt;code&gt;Integer#times&lt;/code&gt;!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integer#times&lt;/td&gt;
&lt;td&gt;13.66s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array#each&lt;/td&gt;
&lt;td&gt;13.96s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;microbenchmarking-ruby-performance&#34;&gt;Microbenchmarking Ruby performance&lt;/h3&gt;
&lt;p&gt;I’ve forked the original language implementation repo, and created my own repository called “Ruby Microbench”. It takes all of the examples discussed, as well as several other forms of doing the iteration in Ruby: &lt;a href=&#34;https://github.com/jpcamara/ruby_microbench&#34;&gt;https://github.com/jpcamara/ruby_microbench&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the output of just running those using Ruby 3.4 with and without YJIT:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fibonacci&lt;/th&gt;&lt;th&gt;array#each&lt;/th&gt;
&lt;th&gt;range#each&lt;/th&gt;
&lt;th&gt;times&lt;/th&gt;
&lt;th&gt;for&lt;/th&gt;
&lt;th&gt;while&lt;/th&gt;
&lt;th&gt;loop do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4 YJIT&lt;/td&gt;
&lt;td&gt;2.19s&lt;/td&gt;
&lt;td&gt;14.02s&lt;/td&gt;
&lt;td&gt;26.61s&lt;/td&gt;
&lt;td&gt;13.12s&lt;/td&gt;
&lt;td&gt;14.91s&lt;/td&gt;
&lt;td&gt;37.10s&lt;/td&gt;
&lt;td&gt;13.95s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4&lt;/td&gt;
&lt;td&gt;16.49s&lt;/td&gt;
&lt;td&gt;34.29s&lt;/td&gt;
&lt;td&gt;33.88s&lt;/td&gt;
&lt;td&gt;33.18s&lt;/td&gt;
&lt;td&gt;36.32s&lt;/td&gt;
&lt;td&gt;37.14s&lt;/td&gt;
&lt;td&gt;50.65s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I have no idea why the &lt;code&gt;while&lt;/code&gt; loop example I wrote seems to be so slow. I’d expected it to run much faster. Maybe there’s an issue with how I wrote it - feel free to open an issue or PR if you see something wrong with my implementation. The &lt;code&gt;loop do&lt;/code&gt; (taken from &lt;a href=&#34;https://bsky.app/profile/timtilberg.bsky.social&#34;&gt;@timtilberg&lt;/a&gt;’s &lt;a href=&#34;https://x.com/timtilberg/status/1861194052516864004&#34;&gt;example&lt;/a&gt;) runs around the same speed as &lt;code&gt;Integer#times&lt;/code&gt; - although its performance is &lt;em&gt;awful&lt;/em&gt; with YJIT turned off.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 The &lt;code&gt;for in&lt;/code&gt; loop and &lt;code&gt;array#each&lt;/code&gt; have very similar performance, and that&amp;rsquo;s because at the Ruby VM bytecode level they are almost identical. &lt;code&gt;for in&lt;/code&gt; is mostly syntactic sugar that transforms into an &lt;code&gt;#each&lt;/code&gt; call in the VM. Thanks to &lt;a href=&#34;https://ruby.social/@dodecadaniel&#34;&gt;Daniel Colson&lt;/a&gt; for pointing this out, and you can read his &lt;a href=&#34;https://danieljamescolson.com/blog/for-loops&#34;&gt;for loops in Ruby&lt;/a&gt; article for some additional information and nuance around &lt;code&gt;for in&lt;/code&gt;!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In addition to running Ruby 3.4, for fun I have it using &lt;code&gt;rbenv&lt;/code&gt; to run:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ruby 3.3&lt;/li&gt;
&lt;li&gt;Ruby 3.3 YJIT&lt;/li&gt;
&lt;li&gt;Ruby 3.2&lt;/li&gt;
&lt;li&gt;Ruby 3.2 YJIT&lt;/li&gt;
&lt;li&gt;TruffleRuby 24.1&lt;/li&gt;
&lt;li&gt;Ruby Artichoke&lt;/li&gt;
&lt;li&gt;MRuby&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A few of the test runs are listed here:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fibonacci&lt;/th&gt;
&lt;th&gt;
array#each
&lt;/th&gt;
&lt;th&gt;range#each&lt;/th&gt;
&lt;th&gt;times&lt;/th&gt;
&lt;th&gt;for&lt;/th&gt;
&lt;th&gt;while&lt;/th&gt;
&lt;th&gt;loop do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4 YJIT&lt;/td&gt;&lt;td&gt;2.19s&lt;/td&gt;
&lt;td&gt;14.02s&lt;/td&gt;
&lt;td&gt;26.61s&lt;/td&gt;
&lt;td&gt;13.12s&lt;/td&gt;
&lt;td&gt;14.91s&lt;/td&gt;
&lt;td&gt;37.10s&lt;/td&gt;
&lt;td&gt;13.95s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4&lt;/td&gt;
&lt;td&gt;16.49s&lt;/td&gt;
&lt;td&gt;34.29s&lt;/td&gt;&lt;td&gt;33.88s&lt;/td&gt;
&lt;td&gt;33.18s&lt;/td&gt;
&lt;td&gt;36.32s&lt;/td&gt;
&lt;td&gt;37.14s&lt;/td&gt;
&lt;td&gt;50.65s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TruffleRuby 24.1&lt;/td&gt;&lt;td&gt;0.92s&lt;/td&gt;
&lt;td&gt;0.97s&lt;/td&gt;
&lt;td&gt;0.92s&lt;/td&gt;&lt;td&gt;2.39s&lt;/td&gt;
&lt;td&gt;2.06s&lt;/td&gt;&lt;td&gt;3.90s&lt;/td&gt;
&lt;td&gt;0.77s&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;MRuby 3.3&lt;/td&gt;
&lt;td&gt;28.83s&lt;/td&gt;
&lt;td&gt;144.65s&lt;/td&gt;
&lt;td&gt;126.40s&lt;/td&gt;
&lt;td&gt;128.22s&lt;/td&gt;
&lt;td&gt;133.58s&lt;/td&gt;&lt;td&gt;91.55s&lt;/td&gt;
&lt;td&gt;144.93s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artichoke&lt;/td&gt;
&lt;td&gt;19.71s&lt;/td&gt;
&lt;td&gt;236.10s&lt;/td&gt;&lt;td&gt;214.55s&lt;/td&gt;
&lt;td&gt;214.51s&lt;/td&gt;
&lt;td&gt;215.95s&lt;/td&gt;
&lt;td&gt;174.70s&lt;/td&gt;
&lt;td&gt;264.67s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Based on that, I’ve taken the original visualization and made a Ruby specific one here just for the &lt;code&gt;fibonacci&lt;/code&gt; run:&lt;/p&gt;
&lt;div id=&#34;ruby_visual&#34;&gt;&lt;/div&gt;
&lt;h3 id=&#34;speeding-up-rangeeach&#34;&gt;Speeding up &lt;code&gt;range#each&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Can we, the non &lt;a href=&#34;https://bsky.app/profile/k0kubun.com&#34;&gt;@k0kobun&lt;/a&gt;’s of the world, make &lt;code&gt;range#each&lt;/code&gt; faster? If I monkey patch the &lt;code&gt;Range&lt;/code&gt; class with a pure-ruby implementation, things &lt;em&gt;do&lt;/em&gt; get much faster! Here’s my implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Range
  def each
    beginning = self.begin
    ending = self.end
    i = beginning
    loop do
      break if i == ending
      yield i
      i = i.succ
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is the change in performance - 2 seconds slower than &lt;code&gt;times&lt;/code&gt; - not bad!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;				
&lt;/th&gt;
&lt;th&gt;
Time spent
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each in C&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range#each in Ruby&lt;/td&gt;
&lt;td&gt;16.64s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is obviously over-simplified. I don’t handle all of the different cases of &lt;code&gt;Range&lt;/code&gt;, and there may be nuances I am missing. Also, most of the Ruby rewritten methods I’ve seen invoke a &lt;code&gt;Primitive&lt;/code&gt; class for certain operations. I’d love to learn more about when and why it’s needed.&lt;/p&gt;
&lt;p&gt;But! It goes to show the power of moving things &lt;em&gt;out&lt;/em&gt; of C and letting YJIT optimize our code. It can improve performance in ways that would be difficult or impossible to replicate in regular C code.&lt;/p&gt;
&lt;h3 id=&#34;yjit-standard-library&#34;&gt;YJIT standard library&lt;/h3&gt;
&lt;p&gt;Last year Aaron Patterson wrote an article called &lt;a href=&#34;https://railsatscale.com/2023-08-29-ruby-outperforms-c/&#34;&gt;Ruby Outperforms C&lt;/a&gt;, in which he rewrote a C extension in Ruby for some GraphQL parsing. The Ruby code outperformed C thanks to YJIT optimizations.&lt;/p&gt;
&lt;p&gt;This got me thinking that it would be interesting to see a kind of “YJIT standard library” emerge, where core ruby functionality run in C could be swapped out for Ruby implementations for use by people using YJIT.&lt;/p&gt;
&lt;p&gt;As it turns out, this is almost exactly what the core YJIT team has been doing. In many cases they’ve completely removed C code, but more recently they’ve created a &lt;code&gt;with_yjit&lt;/code&gt; block. The code will only take effect if YJIT is enabled, and otherwise the C code will run. For example, this is how&lt;code&gt;Array#each&lt;/code&gt; is implemented:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;with_yjit do
  if Primitive.rb_builtin_basic_definition_p(:each)
    undef :each

    def each # :nodoc:
      # ... we examined this code earlier ...
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As of Ruby 3.3, YJIT can be lazily initialized. Thankfully the &lt;code&gt;with_yjit&lt;/code&gt; code handles this - the appropriate &lt;code&gt;with_yjit&lt;/code&gt; versions of methods will be run once YJIT is enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Uses C-builtin
[1, 2, 3].each do |i|
  puts i
end

RubyVM::YJIT.enable

# Uses Ruby version, which can be YJIT optimized
[1, 2, 3].each do |i|
  puts i
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because &lt;code&gt;with_yjit&lt;/code&gt; is a YJIT “hook”, which is called the moment YJIT is enabled. After being called, it is removed from the runtime using &lt;code&gt;undef :with_yjit&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;investigating-yjit-optimizations&#34;&gt;Investigating YJIT optimizations&lt;/h3&gt;
&lt;p&gt;We’ve looked at Ruby code. We’ve looked at C code. We’ve looked at Ruby VM bytecode. Why not take it one step deeper and look at some &lt;em&gt;machine code&lt;/em&gt;? And maybe some Rust code? Hey - where are you going! Don’t walk away while I’m talking to you!&lt;/p&gt;
&lt;p&gt;If you &lt;em&gt;haven’t&lt;/em&gt; walked away, or skipped to the next section, let’s take a look at a small sliver of YJIT while we’re here!&lt;/p&gt;
&lt;p&gt;We can see the machine code YJIT generates 😱. It’s possible by building CRuby from source with YJIT debug flags. If you’re on a Mac you can see &lt;a href=&#34;https://jpcamara.com/2024/12/02/my-macos-setup.html&#34;&gt;my MacOS setup for hacking on CRuby&lt;/a&gt; or &lt;a href=&#34;https://jpcamara.com/2024/11/27/my-docker-setup.html&#34;&gt;my docker setup for hacking on CRuby&lt;/a&gt; for more elaborate instructions on building Ruby. But the simplified step is when you go to &lt;code&gt;./configure&lt;/code&gt; Ruby, you hand in an option of &lt;code&gt;--enable-yjit=dev&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./configure --enable-yjit=dev
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s use our &lt;code&gt;Integer#times&lt;/code&gt; example from earlier as our example Ruby code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;u &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ARGV&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;to_i
r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; rand(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;)
a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)
	
&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
  &lt;span style=&#34;color:#ae81ff&#34;&gt;100_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;j&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
    a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; j &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; u
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;i&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; r
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
puts a&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because you’ve built Ruby with YJIT in dev mode, you can hand in the &lt;code&gt;--yjit-dump-disasm&lt;/code&gt; flag when running your ruby program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./ruby --yjit --yjit-dump-disasm test.rb 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this, we can see the machine code created. We’ll just focus in on one tiny part - the machine code equivalent of the Ruby VM bytecode we read earlier. Here is the original VM bytecode for &lt;code&gt;opt_succ&lt;/code&gt;, which is generated when you call &lt;code&gt;i.succ&lt;/code&gt;, the &lt;code&gt;Integer#succ&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
0027 opt_succ        &amp;lt;calldata!mid:succ, ARGS_SIMPLE&amp;gt;[CcCr]
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is the machine code YJIT generates in this scenario, on my Mac M2 arm64 architecture:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Block: times@&amp;lt;internal:numeric&amp;gt;:259 
# reg_mapping: [Some(Stack(0)), None, None, None, None]
# Insn: 0027 opt_succ (stack_size: 1)
# call to Integer#succ
# guard object is fixnum
0x1096808c4: tst x1, #1
0x1096808c8: b.eq #0x109683014
0x1096808cc: nop 
0x1096808d0: nop 
0x1096808d4: nop 
0x1096808d8: nop 
0x1096808dc: nop 
# Integer#succ
0x1096808e0: adds x11, x1, #2
0x1096808e4: b.vs #0x109683048
0x1096808e8: mov x1, x11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be honest, I about 25% understand this, and 75% am combining my own logic and AI to learn it 🤫. Feel free to yell at me if I get it a little wrong, I’d love to learn more. But here’s how I break this down.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Block: times@&amp;lt;internal:numeric&amp;gt;:259
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👆🏼This roughly corresponds to the line &lt;code&gt;i = i.succ&lt;/code&gt; in the &lt;code&gt;Integer#times&lt;/code&gt; method in &lt;code&gt;numeric.rb&lt;/code&gt;. I say roughly because in my current code I see that on line 258, but maybe it shows the end of the block it’s run in since YJIT compiles “blocks” of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;256: while i &amp;lt; self
257:   yield i
258:   i = i.succ
259: end

# reg_mapping: [Some(Stack(0)), None, None, None, None]
# Insn: 0027 opt_succ (stack_size: 1)
# call to Integer#succ
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👆🏼I have no idea what &lt;code&gt;reg_mapping&lt;/code&gt; means - probably mapping how it uses a CPU register? &lt;code&gt;Insn: 0027 opt_succ&lt;/code&gt; looks very familiar! That’s our VM bytecode! &lt;code&gt;call to Integer#succ&lt;/code&gt; is just a helpful comment added. YJIT is capable of adding comments to the machine code. We still haven’t even left the safety of the comments 😅.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# guard object is fixnum
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;👆🏼This is interesting. I can find a corresponding bit of Rust code that maps directly to this. Let’s take a look at it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fn jit_rb_int_succ(
  //...
  asm: &amp;amp;mut Assembler,
  //...
) -&amp;gt; bool {
  // Guard the receiver is fixnum
  let recv_type = asm.ctx.get_opnd_type(StackOpnd(0));
  let recv = asm.stack_pop(1);
  if recv_type != Type::Fixnum {
    asm_comment!(asm, &amp;quot;guard object is fixnum&amp;quot;);
    asm.test(recv, Opnd::Imm(RUBY_FIXNUM_FLAG as i64));
         asm.jz(Target::side_exit(Counter::opt_succ_not_fixnum));
  }

  asm_comment!(asm, &amp;quot;Integer#succ&amp;quot;);
  let out_val = asm.add(recv, Opnd::Imm(2)); // 2 is untagged Fixnum 1
  asm.jo(Target::side_exit(Counter::opt_succ_overflow));

  // Push the output onto the stack
  let dst = asm.stack_push(Type::Fixnum);
  asm.mov(dst, out_val);

  true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh nice! This is the actual YJIT Rust implementation of the &lt;code&gt;opt_succ&lt;/code&gt; call. This is that optimization &lt;a href=&#34;https://bsky.app/profile/k0kubun.com&#34;&gt;@k0kobun&lt;/a&gt; made to further improve &lt;code&gt;opt_succ&lt;/code&gt; performance beyond the bytecode C function calls. We’re in the section that is checking if what we’re operating on is a Fixnum, which is a way small integers are stored internally in CRuby:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if recv_type != TypeFixnum 
  asm_comment!(asm, &amp;quot;guard object is fixnum&amp;quot;);
  asm.test(recv, Opnd::Imm(RUBY_FIXNUM_FLAG as i64));
  asm.jz(Target::side_exit(Counter::opt_succ_not_fixnum));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That becomes this machine code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# guard object is fixnum
0x1096808c4: tst x1, #1
0x1096808c8: b.eq #0x109683014
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;asm.test&lt;/code&gt; generates &lt;code&gt;tst x1, #1&lt;/code&gt;, which according to an AI bot I asked is checking the least significant bit, which is a Fixnum “tag” that indicates this is a Fixnum. If it’s Fixnum, the result is 1 and &lt;code&gt;b.eq&lt;/code&gt; is false. If it’s not a Fixnum, the result is &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;b.eq&lt;/code&gt; is true and jumps away from this code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0x1096808cc: nop 
0x1096808d0: nop 
0x1096808d4: nop 
0x1096808d8: nop 
0x1096808dc: nop 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🤖 “NOPs for alignment/padding”. Thanks AI. I don’t know why it is needed, but at least I know what it probably is.&lt;/p&gt;
&lt;p&gt;Finally, we &lt;em&gt;actually&lt;/em&gt; add 1 to the number.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;asm_comment!(asm, &amp;quot;Integer#succ&amp;quot;);
let out_val = asm.add(recv, Opnd::Imm(2)); // 2 is untagged Fixnum 1
asm.jo(Target::side_exit(Counter::opt_succ_overflow));

// Push the output onto the stack
let dst = asm.stack_push(Type::Fixnum);
asm.mov(dst, out_val);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Rust code generates our &lt;code&gt;Integer#succ&lt;/code&gt; comment. Then, to add 1, because of the “Fixnum tag” data embedded within our integer, actually means we have to add 2 using &lt;code&gt;adds x11, x1, #2&lt;/code&gt; 😵‍💫. If we overflow the space available, it exits to a different code path - &lt;code&gt;b.vs&lt;/code&gt; is a branch on overflow. Otherwise, it stores the result with &lt;code&gt;mov x1, x11&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Integer#succ
0x1096808e0: adds x11, x1, #2
0x1096808e4: b.vs #0x109683048
0x1096808e8: mov x1, x11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;😮‍💨. That was a lot. And it seems like &lt;em&gt;alot&lt;/em&gt; of working is being done, but because it’s such low level machine code it’s presumably super fast. We examined a teensy tiny portion of what YJIT is capable of generating - JITs are complicated!&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://bsky.app/profile/k0kubun.com&#34;&gt;@k0kobun&lt;/a&gt; for providing me with the commands and pointing me at the &lt;a href=&#34;https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md&#34;&gt;YJIT docs&lt;/a&gt; which contain tons of additional options as well.&lt;/p&gt;
&lt;h3 id=&#34;the-future-of-cruby-optimizations&#34;&gt;The future of CRuby optimizations&lt;/h3&gt;
&lt;p&gt;The irony of language implementation is that you often work less in the language you’re implementing than you do in something lower-level - in Ruby’s case, that’s mostly C and some Rust.&lt;/p&gt;
&lt;p&gt;With a layer like YJIT, it potentially opens up a future where more of the language becomes plain Ruby, and Ruby developer contribution is easier. Many languages have a smaller low level core, and the majority of the language is written in itself (like Java, for instance). Maybe that’s a future for CRuby, someday! Until then, keep the YJIT optimizations coming, YJIT team!&lt;/p&gt;
&lt;script src=&#34;https://jpcamara.com/d3.v7.min.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://jpcamara.com/latency_visual.js&#34;&gt;&lt;/script&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Naive in this case meaning that there are more efficient ways to calculate fibonacci numbers in a program&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;MJIT, the precursor to YJIT, made Ruby much faster on certain benchmarks. But on large realistic Rails applications it actually &lt;a href=&#34;https://bugs.ruby-lang.org/issues/14490&#34;&gt;made things &lt;em&gt;slower&lt;/em&gt;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;When C code is running, it has to opt-in to releasing the GVL, so it’s more difficult for threads to corrupt or modify data mid-operation. The original Ruby version could yield the GVL at points that would invalidate the array. That’s my understanding of the situation anyways.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/yjitvsc.drawio.png)

There is a recent [language comparison repo](https://github.com/bddicken/languages) which has been getting shared a lot. In it, CRuby was the third slowest option, only beating out R and Python. 

The repo author, [@BenjDicken](https://x.com/BenjDicken), [created a fun visualization](https://x.com/BenjDicken/status/1861072804239847914) of each language’s performance. Here’s one of the visualizations, which shows Ruby as the third slowest language benchmarked:

&lt;div id=&#34;original_visual&#34;&gt;&lt;/div&gt;

&gt; The code for this visualization is from [https://benjdd.com/languages/](https://benjdd.com/languages/), with permission from [@BenjDicken](https://x.com/BenjDicken/status/1862623583803253149)

The repository describes itself as:

&gt; A repo for collaboratively building small benchmarks to compare languages.

It contains two different benchmarks:

1. “Loops”, which “Emphasizes loop, conditional, and basic math performance”
2. “Fibonacci”, which “Emphasizes function call overhead and recursion.”

The loop example iterates 1 billion times, utilizing a nested loop:

```ruby
u = ARGV[0].to_i       
r = rand(10_000)                          
a = Array.new(10_000, 0)                 
	
(0...10_000).each do |i|                     
  (0...100_000).each do |j|               
    a[i] += j % u                     
  end
  a[i] += r                      
end
	
puts a[r]
```
The Fibonacci example is a basic “naive” Fibonacci implementation[^1]:

	def fibonacci(n)
	  return 0 if n == 0
	  return 1 if n == 1
	  fibonacci(n - 1) + fibonacci(n - 2)
	end
	
	u = ARGV[0].to_i
	r = 0
	
	(1...u).each do |i|
	  r += fibonacci(i)
	end
	
	puts r

Run on [@BenjDicken](https://x.com/BenjDicken)’s M3 MacBook Pro, Ruby 3.3.6 takes 28 seconds to run the loop iteration example, and 12 seconds to run the Fibonacci example. For comparison, node.js takes a little over a second for both examples - it’s not a great showing for Ruby. 

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;Fibonacci&lt;/th&gt;
      &lt;th&gt;Loops&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;12.17s&lt;/td&gt;
      &lt;td&gt;28.80s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;node.js&lt;/td&gt;
      &lt;td&gt;1.11s&lt;/td&gt;
      &lt;td&gt;1.03s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

From this point on, I’ll use benchmarks relative to my own computer. Running the same benchmark on my M2 MacBook Air, I get 33.43 seconds for the loops and 16.33 seconds for fibonacci - even worse 🥺. Node runs a little over 1 second for fibonacci and 2 seconds for the loop example.

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
      &lt;th&gt;Fibonacci&lt;/th&gt;
      &lt;th&gt;Loops&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ruby&lt;/td&gt;
      &lt;td&gt;16.33s&lt;/td&gt;
      &lt;td&gt;33.43s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;node.js&lt;/td&gt;
      &lt;td&gt;1.36s&lt;/td&gt;
      &lt;td&gt;2.07s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

### Who cares?
In most ways, these types of benchmarks are meaningless. Python was the slowest language in the benchmark, and yet at the same time it’s the [most used language on Github as of October 2024](https://github.blog/news-insights/octoverse/octoverse-2024/). Ruby runs some of the [largest web apps in the world](https://x.com/tobi/status/1863935229620363693). I ran a [benchmark recently of websocket performance between the Ruby Falcon web server and node.js](https://x.com/jpcamara/status/1849984009515966958), and the Ruby results were close to the node.js results. Are you doing a billion loop iterations or using web sockets?

A programming language should be reasonably efficient - after that the usefulness of the language, the type of tasks you work on, and language productivity outweigh the speed at which you can run a billion iterations of a loop, or complete an intentionally inefficient implementation of a Fibonacci method.

That said:

1. The programming world loves microbenchmarks 🤷‍♂️
2. Having a fast benchmark may not be valuable in practice but it has meaning for people’s interest in a language. Some would claim it means you’ll have an easier time scaling performance, but that’s arguable[^2]
3. It’s disappointing if your language of choice doesn’t perform well. It’s nice to be able to say “I use and enjoy this language, and it runs fast in all benchmarks!”

In the case of this Ruby benchmark, I had a feeling that YJIT wasn’t being applied in the Ruby code, so I checked the repo. Lo and behold, the command was as follows:

	ruby ./code.rb 40

We know my results from earlier (33 seconds and 16 seconds). What do we get with YJIT applied?

	ruby --yjit ./code.rb 40

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;/th&gt;
&lt;th&gt;Fibonacci&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby&lt;/td&gt;
&lt;td&gt;2.06s&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

Nice! With YJIT, Fibonacci gets a massive boost - going from 16.88 seconds down to 2.06 seconds. It’s close to the speed of node.js at that point!

YJIT makes a more modest difference for the looping example - going from 33.43 seconds down to 25.57 seconds. Why is that?

### A team effort
I wasn’t alone in trying out these code samples with YJIT. On twitter, [@bsilva96](https://x.com/bsilva96) had asked the same questions:

![](https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-01-at-8.38.46pm.png)

&gt; [https://x.com/bsilva96/status/1861136096689606708](https://x.com/bsilva96/status/1861136096689606708)

[@k0kubun](https://bsky.app/profile/k0kubun.com) came through with insights into why things were slow and ways of improving the performance:

![](https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-01-at-8.41.03pm.png)

&gt; [https://x.com/k0kubun/status/1861149512640979260](https://x.com/k0kubun/status/1861149512640979260)

Let’s unpack his response. There are three parts to it:

1. `Range#each` is still written in C as of Ruby 3.4
2. `Integer#times` was converted from C to Ruby in Ruby 3.3
3. `Array#each` was converted from C to Ruby in Ruby 3.4

### 1. `Range#each` is still written in C, which YJIT can’t optimize
Looking back at our Ruby code:

	(0...10_000).each do |i|                     
	  (0...100_000).each do |j|               
	    a[i] += j % u                     
	  end
	  a[i] += r                      
	end

It’s written as a range, and range has its own `each` implementation, which is apparently written in C. The CRuby codebase is pretty easy to navigate - let’s find that implementation 🕵️‍♂️.

Most core classes in Ruby have top-level C files named after them - in this case we’ve got `range.c` at the root of the project. CRuby has a pretty readable interface for exposing C functions as classes and methods - there is an `Init` function, usually at the bottom of the file. Inside that `Init` our classes, modules and methods are exposed from C to Ruby. Here are the relevant pieces of `Init_Range`:

	void
	Init_Range(void)
	{
	  //...
	  rb_cRange = rb_struct_define_without_accessor(
	    &#34;Range&#34;, rb_cObject, range_alloc,
	    &#34;begin&#34;, &#34;end&#34;, &#34;excl&#34;, NULL);
	
	  rb_include_module(rb_cRange, rb_mEnumerable);
	  // ...
	  rb_define_method(rb_cRange, &#34;each&#34;, range_each, 0);

First, we define our `Range` class using `rb_struct_define...`. We name it `“Range”`, with a super class of `Object` (`rb_cObject`), and some initialization parameters (`“begin”`, `“end”` and whether to exclude the last value, ie the `..` vs `...` range syntax).

Second, we include `Enumerable` using `rb_include_module`. That gives us all the great Ruby enumeration methods like `map`, `select`, `include?` and [a bajillion others](https://docs.ruby-lang.org/en/3.3/Enumerable.html). All you have to do is provide an `each` implementation and it handles the rest.

Third, we define our `“each”` method. It’s implemented by the `range_each` function in C, and takes zero explicit arguments (blocks are not considered in this count).

`range_each` is hefty. It’s almost 100 lines long, and specializes into several versions of itself. I’ll highlight a few, collapsed all together:

	static VALUE
	range_each(VALUE range)
	{
	  //...
	  range_each_fixnum_endless(beg);
	  range_each_fixnum_loop(beg, end, range);
	  range_each_bignum_endless(beg);
	  rb_str_upto_endless_each(beg, sym_each_i, 0);
	  // and even more...

These C functions handle all the variations of ranges you might use in your own code:

```ruby
(0...).each
(0...100).each
(&#34;a&#34;...&#34;z&#34;).each
# and on...
```
Why does it matter that `Range#each` is written in C? It means YJIT can’t inspect it - optimizations stop at the function call and resume when the function call returns. C functions are fast, but YJIT can take things further by creating specializations for hot paths of code. There is a great article from Aaron Patterson called [Ruby Outperforms C](https://railsatscale.com/2023-08-29-ruby-outperforms-c/) where you can learn more about some of those specialized optimizations.

### 2. Optimizing our loop: `Integer#times` was converted from C to Ruby in Ruby 3.3
The hot path (_where most of our CPU time is spent_) is `Range#each`, which is a C function. YJIT can’t optimize C functions - they’re a black box. So what can we do?

&gt; We converted Integer#times to Ruby in 3.3

Interesting! In Ruby 3.3, `Integer#times` was [converted from a C function to a Ruby method](https://github.com/ruby/ruby/pull/8388)! Here’s the 3.3+ version - its pretty simple:

	def times
	  #... a little C interop code
	  i = 0
	  while i &lt; self
	    yield i
	    i = i.succ
	  end
	  self
	end

Very simple. It’s just a basic while loop. Most importantly, it’s all Ruby code, which means YJIT should be able to introspect and optimize it!

### An aside on `Integer#succ`
The slightly odd part of that code is `i.succ`. I’d never heard of `Integer#succ`, which apparently gives you the “successor” to an integer.

![](https://cdn.uploads.micro.blog/98548/2024/0c8bd56f64.png)

&gt; I’ve never seen this show, and yet it’s the first thing I thought of when I learned about this method. Thanks, advertising.

There was a PR to improve the performance of `Integer#succ` in early 2024, which helped me understand why anyone would ever use it:

&gt; We use Integer#succ when we rewrite loop methods in Ruby (e.g. Integer#times and Array#each) because opt\_succ (i = i.succ) is faster to dispatch on the interpreter than putobject 1; opt\_plus (i += 1).
&gt; 
&gt; [https://github.com/ruby/ruby/pull/9519](https://github.com/ruby/ruby/pull/9519)

`Integer#succ` is like a virtual machine cheat code. It takes a common operation (adding 1 to an integer) and turns it from two virtual machine operations into one. We can call `disasm` on the `times` method to see that in action:

	puts RubyVM::InstructionSequence.disasm(1.method(:times))

The `Integer#times` method gets broken down into a lot of Ruby VM bytecode, but we only care about a few lines:

	...
	0025 getlocal_WC_0   i@0
	0027 opt_succ        &lt;calldata!mid:succ, ARGS_SIMPLE&gt;[CcCr]
	0029 setlocal_WC_0   i@0
	...

- `getlocal_WC_0` gets our `i` variable from the current scope. That’s the `i` in `i.succ`
- `opt_succ` performs the `succ` call in our `i.succ`. It will either call the actual `Integer#succ` method, or an optimized C function for small numbers 
- In Ruby 3.4 with YJIT enabled, small numbers get optimized even further into machine code (just a note, not shown in the VM machine code)
- `setlocal_WC_0` sets the result of `opt_succ` to our local variable `i`

If we change from `i = i.succ` to `i += 1`, we now have two VM operations take the place of `opt_succ`:

	...
	0025 getlocal_WC_0        i@0
	0027 putobject_INT2FIX_1_
	0028 opt_plus             &lt;calldata!mid:+, argc:1, ARGS_SIMPLE&gt;
	0029 setlocal_WC_0        i@0
	...

Everything is essentially the same as before, except now we have two steps to go through instead of one:

- `putobject_INT2FIX_1_` pushes the integer `1` onto the virtual machine stack
- `opt_plus` is the `+` in our `+= 1`, and calls either the Ruby `+` method or an optimized C function for small numbers 
- There is probably a YJIT optimization for `opt_plus` as well

If there is nothing else to learn from this code, it’s this: the kinds of optimizations you do at the VM and JIT level are _deep_. When writing general Ruby programs we typically don’t and _shouldn’t_ consider the impact of one versus two _machine code instructions_. But at the JIT level, on the scale of millions and billions of operations, it matters!

### Back to `Integer#times`
Let’s try running our benchmark code again, using `times`! Instead of iterating over ranges, we simply iterate for `10_000` and `100_000` `times`:

```ruby
u = ARGV[0].to_i        
r = rand(10_000)        
a = Array.new(10_000, 0)
	
10_000.times do |i|
  100_000.times do |j|
    a[i] += j % u
  end
  a[i] += r
end
	
puts a[r]
```
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integer#times&lt;/td&gt;
&lt;td&gt;13.66s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

Nice! YJIT makes a much larger impact using `Integer#times`. That trims things down significantly, taking it down to 13.66 seconds on my machine. On [@k0kobun](https://bsky.app/profile/k0kubun.com)’s machine it actually goes down to 9 seconds (and 8 seconds on Ruby 3.4).

&gt; It’s probably Ruby 3.5’s job to make it faster than 8s though.

We might look forward to even faster performance in Ruby 3.5. We’ll see!

### 3. `Array#each` was converted from C to Ruby in Ruby 3.4
CRuby continues to see C code rewritten in Ruby, and in Ruby 3.4 `Array#each` was one of those changes. Here is an [example of the first attempt at implementing it](https://github.com/ruby/ruby/pull/6687/files):

	def each
	  unless block_given?
	    return to_enum(:each) { self.length }
	  end
	  i = 0
	  while i &lt; self.length
	    yield self[i]
	    i = i.succ
	  end
	  self
	end

Super simple and readable! And YJIT optimizable!

Unfortunately, due to something related to CRuby internals, it contained [race conditions](https://jpcamara.com/2024/06/23/your-ruby-programs.html#race-conditions). A later implementation [landed in Ruby 3.4](https://github.com/ruby/ruby/pull/11955). 

	def each
	  Primitive.attr! :inline_block, :c_trace
	
	  unless defined?(yield)
	    return Primitive.cexpr! &#39;SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)&#39;
	  end
	  _i = 0
	  value = nil
	  while Primitive.cexpr!(%q{ ary_fetch_next(self, LOCAL_PTR(_i), LOCAL_PTR(value)) })
	    yield value
	  end
	  self
	end

Unlike the first implementation, and unlike `Integer#times`, things are a bit more cryptic this time. This is definitely not pure Ruby code that anyone could be expected to write. Somehow, the `Primitive` module seems to allow evaluating C code from Ruby, and in doing so avoids the race conditions present in the pure Ruby solution[^3].

By fetching indexes and values using C code, I think it results in a more atomic operation. I have no idea why the `Primitive.cexpr!` is used to return the enumerator, or what value `Primitive.attr! :inline_block` provides. Please comment if you have insights there!

I was a little loose with my earlier `Integer#times` source code as well. That actually had a bit of this `Primitive` syntax as well. The core of the method is what we looked at, and it’s all Ruby, but the start of the method contains the same `Primitive` calls for `:inline_block` and returning the enumerator:

	def times
	  Primitive.attr! :inline_block
	  unless defined?(yield)
	    return Primitive.cexpr! &#39;SIZED_ENUMERATOR(self, 0, 0, int_dotimes_size)&#39;
	  end
	  #...

Ok - it’s more cryptic than `Integer#times` was, but `Array#each` is mostly Ruby (on Ruby 3.4+). Let’s give it a try using arrays instead of ranges or `times`:

```ruby
u = ARGV[0].to_i
r = rand(10_000)
a = Array.new(10_000, 0)
	
outer = (0...10_000).to_a.freeze
inner = (0...100_000).to_a.freeze
outer.each do |i|
  inner.each do |j|
    a[i] += j % u
  end
  a[i] += r
end
	
puts a[r]
```
Despite the embedded C code, YJIT still seems capable of making some hefty performance optimizations. It’s within the same range as `Integer#times`!

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Loops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integer#times&lt;/td&gt;
&lt;td&gt;13.66s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array#each&lt;/td&gt;
&lt;td&gt;13.96s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

### Microbenchmarking Ruby performance
I’ve forked the original language implementation repo, and created my own repository called “Ruby Microbench”. It takes all of the examples discussed, as well as several other forms of doing the iteration in Ruby: [https://github.com/jpcamara/ruby\_microbench](https://github.com/jpcamara/ruby_microbench)

Here is the output of just running those using Ruby 3.4 with and without YJIT:

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fibonacci&lt;/th&gt;&lt;th&gt;array#each&lt;/th&gt;
&lt;th&gt;range#each&lt;/th&gt;
&lt;th&gt;times&lt;/th&gt;
&lt;th&gt;for&lt;/th&gt;
&lt;th&gt;while&lt;/th&gt;
&lt;th&gt;loop do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4 YJIT&lt;/td&gt;
&lt;td&gt;2.19s&lt;/td&gt;
&lt;td&gt;14.02s&lt;/td&gt;
&lt;td&gt;26.61s&lt;/td&gt;
&lt;td&gt;13.12s&lt;/td&gt;
&lt;td&gt;14.91s&lt;/td&gt;
&lt;td&gt;37.10s&lt;/td&gt;
&lt;td&gt;13.95s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4&lt;/td&gt;
&lt;td&gt;16.49s&lt;/td&gt;
&lt;td&gt;34.29s&lt;/td&gt;
&lt;td&gt;33.88s&lt;/td&gt;
&lt;td&gt;33.18s&lt;/td&gt;
&lt;td&gt;36.32s&lt;/td&gt;
&lt;td&gt;37.14s&lt;/td&gt;
&lt;td&gt;50.65s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

I have no idea why the `while` loop example I wrote seems to be so slow. I’d expected it to run much faster. Maybe there’s an issue with how I wrote it - feel free to open an issue or PR if you see something wrong with my implementation. The `loop do` (taken from [@timtilberg](https://bsky.app/profile/timtilberg.bsky.social)’s [example](https://x.com/timtilberg/status/1861194052516864004)) runs around the same speed as `Integer#times` - although its performance is _awful_ with YJIT turned off.

&gt; 📝 The `for in` loop and `array#each` have very similar performance, and that&#39;s because at the Ruby VM bytecode level they are almost identical. `for in` is mostly syntactic sugar that transforms into an `#each` call in the VM. Thanks to [Daniel Colson](https://ruby.social/@dodecadaniel) for pointing this out, and you can read his [for loops in Ruby](https://danieljamescolson.com/blog/for-loops) article for some additional information and nuance around `for in`!

In addition to running Ruby 3.4, for fun I have it using `rbenv` to run:

- Ruby 3.3
- Ruby 3.3 YJIT
- Ruby 3.2
- Ruby 3.2 YJIT
- TruffleRuby 24.1
- Ruby Artichoke
- MRuby

A few of the test runs are listed here:

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;fibonacci&lt;/th&gt;
&lt;th&gt;
array#each
&lt;/th&gt;
&lt;th&gt;range#each&lt;/th&gt;
&lt;th&gt;times&lt;/th&gt;
&lt;th&gt;for&lt;/th&gt;
&lt;th&gt;while&lt;/th&gt;
&lt;th&gt;loop do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4 YJIT&lt;/td&gt;&lt;td&gt;2.19s&lt;/td&gt;
&lt;td&gt;14.02s&lt;/td&gt;
&lt;td&gt;26.61s&lt;/td&gt;
&lt;td&gt;13.12s&lt;/td&gt;
&lt;td&gt;14.91s&lt;/td&gt;
&lt;td&gt;37.10s&lt;/td&gt;
&lt;td&gt;13.95s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ruby 3.4&lt;/td&gt;
&lt;td&gt;16.49s&lt;/td&gt;
&lt;td&gt;34.29s&lt;/td&gt;&lt;td&gt;33.88s&lt;/td&gt;
&lt;td&gt;33.18s&lt;/td&gt;
&lt;td&gt;36.32s&lt;/td&gt;
&lt;td&gt;37.14s&lt;/td&gt;
&lt;td&gt;50.65s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TruffleRuby 24.1&lt;/td&gt;&lt;td&gt;0.92s&lt;/td&gt;
&lt;td&gt;0.97s&lt;/td&gt;
&lt;td&gt;0.92s&lt;/td&gt;&lt;td&gt;2.39s&lt;/td&gt;
&lt;td&gt;2.06s&lt;/td&gt;&lt;td&gt;3.90s&lt;/td&gt;
&lt;td&gt;0.77s&lt;/td&gt;
&lt;/tr&gt;&lt;tr&gt;
&lt;td&gt;MRuby 3.3&lt;/td&gt;
&lt;td&gt;28.83s&lt;/td&gt;
&lt;td&gt;144.65s&lt;/td&gt;
&lt;td&gt;126.40s&lt;/td&gt;
&lt;td&gt;128.22s&lt;/td&gt;
&lt;td&gt;133.58s&lt;/td&gt;&lt;td&gt;91.55s&lt;/td&gt;
&lt;td&gt;144.93s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artichoke&lt;/td&gt;
&lt;td&gt;19.71s&lt;/td&gt;
&lt;td&gt;236.10s&lt;/td&gt;&lt;td&gt;214.55s&lt;/td&gt;
&lt;td&gt;214.51s&lt;/td&gt;
&lt;td&gt;215.95s&lt;/td&gt;
&lt;td&gt;174.70s&lt;/td&gt;
&lt;td&gt;264.67s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

Based on that, I’ve taken the original visualization and made a Ruby specific one here just for the `fibonacci` run:

&lt;div id=&#34;ruby_visual&#34;&gt;&lt;/div&gt;

### Speeding up `range#each`
Can we, the non [@k0kobun](https://bsky.app/profile/k0kubun.com)’s of the world, make `range#each` faster? If I monkey patch the `Range` class with a pure-ruby implementation, things _do_ get much faster! Here’s my implementation:

	class Range
	  def each
	    beginning = self.begin
	    ending = self.end
	    i = beginning
	    loop do
	      break if i == ending
	      yield i
	      i = i.succ
	    end
	  end
	end

And here is the change in performance - 2 seconds slower than `times` - not bad!

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;				
&lt;/th&gt;
&lt;th&gt;
Time spent
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Range#each in C&lt;/td&gt;
&lt;td&gt;25.57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Range#each in Ruby&lt;/td&gt;
&lt;td&gt;16.64s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

This is obviously over-simplified. I don’t handle all of the different cases of `Range`, and there may be nuances I am missing. Also, most of the Ruby rewritten methods I’ve seen invoke a `Primitive` class for certain operations. I’d love to learn more about when and why it’s needed. 

But! It goes to show the power of moving things _out_ of C and letting YJIT optimize our code. It can improve performance in ways that would be difficult or impossible to replicate in regular C code.

### YJIT standard library
Last year Aaron Patterson wrote an article called [Ruby Outperforms C](https://railsatscale.com/2023-08-29-ruby-outperforms-c/), in which he rewrote a C extension in Ruby for some GraphQL parsing. The Ruby code outperformed C thanks to YJIT optimizations.

This got me thinking that it would be interesting to see a kind of “YJIT standard library” emerge, where core ruby functionality run in C could be swapped out for Ruby implementations for use by people using YJIT. 

As it turns out, this is almost exactly what the core YJIT team has been doing. In many cases they’ve completely removed C code, but more recently they’ve created a `with_yjit` block. The code will only take effect if YJIT is enabled, and otherwise the C code will run. For example, this is how`Array#each` is implemented:

	with_yjit do
	  if Primitive.rb_builtin_basic_definition_p(:each)
	    undef :each
	
	    def each # :nodoc:
	      # ... we examined this code earlier ...
	    end
	  end
	end

As of Ruby 3.3, YJIT can be lazily initialized. Thankfully the `with_yjit` code handles this - the appropriate `with_yjit` versions of methods will be run once YJIT is enabled:

	# Uses C-builtin
	[1, 2, 3].each do |i|
	  puts i
	end
	
	RubyVM::YJIT.enable
	
	# Uses Ruby version, which can be YJIT optimized
	[1, 2, 3].each do |i|
	  puts i
	end

This is because `with_yjit` is a YJIT “hook”, which is called the moment YJIT is enabled. After being called, it is removed from the runtime using `undef :with_yjit`. 

### Investigating YJIT optimizations
We’ve looked at Ruby code. We’ve looked at C code. We’ve looked at Ruby VM bytecode. Why not take it one step deeper and look at some _machine code_? And maybe some Rust code? Hey - where are you going! Don’t walk away while I’m talking to you!

If you _haven’t_ walked away, or skipped to the next section, let’s take a look at a small sliver of YJIT while we’re here!

We can see the machine code YJIT generates 😱. It’s possible by building CRuby from source with YJIT debug flags. If you’re on a Mac you can see [my MacOS setup for hacking on CRuby](https://jpcamara.com/2024/12/02/my-macos-setup.html) or [my docker setup for hacking on CRuby](https://jpcamara.com/2024/11/27/my-docker-setup.html) for more elaborate instructions on building Ruby. But the simplified step is when you go to `./configure` Ruby, you hand in an option of `--enable-yjit=dev`:

	./configure --enable-yjit=dev
	make install

Let’s use our `Integer#times` example from earlier as our example Ruby code:

```ruby
u = ARGV[0].to_i
r = rand(10_000)
a = Array.new(10_000, 0)
	
10_000.times do |i|
  100_000.times do |j|
    a[i] += j % u
  end
  a[i] += r
end
	
puts a[r]
```
Because you’ve built Ruby with YJIT in dev mode, you can hand in the `--yjit-dump-disasm` flag when running your ruby program:

	./ruby --yjit --yjit-dump-disasm test.rb 40

Using this, we can see the machine code created. We’ll just focus in on one tiny part - the machine code equivalent of the Ruby VM bytecode we read earlier. Here is the original VM bytecode for `opt_succ`, which is generated when you call `i.succ`, the `Integer#succ` method:

	...
	0027 opt_succ        &lt;calldata!mid:succ, ARGS_SIMPLE&gt;[CcCr]
	...

And here is the machine code YJIT generates in this scenario, on my Mac M2 arm64 architecture:

	# Block: times@&lt;internal:numeric&gt;:259 
	# reg_mapping: [Some(Stack(0)), None, None, None, None]
	# Insn: 0027 opt_succ (stack_size: 1)
	# call to Integer#succ
	# guard object is fixnum
	0x1096808c4: tst x1, #1
	0x1096808c8: b.eq #0x109683014
	0x1096808cc: nop 
	0x1096808d0: nop 
	0x1096808d4: nop 
	0x1096808d8: nop 
	0x1096808dc: nop 
	# Integer#succ
	0x1096808e0: adds x11, x1, #2
	0x1096808e4: b.vs #0x109683048
	0x1096808e8: mov x1, x11

To be honest, I about 25% understand this, and 75% am combining my own logic and AI to learn it 🤫. Feel free to yell at me if I get it a little wrong, I’d love to learn more. But here’s how I break this down.

	# Block: times@&lt;internal:numeric&gt;:259

👆🏼This roughly corresponds to the line `i = i.succ` in the `Integer#times` method in `numeric.rb`. I say roughly because in my current code I see that on line 258, but maybe it shows the end of the block it’s run in since YJIT compiles “blocks” of code:

	256: while i &lt; self
	257:   yield i
	258:   i = i.succ
	259: end

	# reg_mapping: [Some(Stack(0)), None, None, None, None]
	# Insn: 0027 opt_succ (stack_size: 1)
	# call to Integer#succ

👆🏼I have no idea what `reg_mapping` means - probably mapping how it uses a CPU register? `Insn: 0027 opt_succ` looks very familiar! That’s our VM bytecode! `call to Integer#succ` is just a helpful comment added. YJIT is capable of adding comments to the machine code. We still haven’t even left the safety of the comments 😅.

	# guard object is fixnum

👆🏼This is interesting. I can find a corresponding bit of Rust code that maps directly to this. Let’s take a look at it:

	fn jit_rb_int_succ(
	  //...
	  asm: &amp;mut Assembler,
	  //...
	) -&gt; bool {
	  // Guard the receiver is fixnum
	  let recv_type = asm.ctx.get_opnd_type(StackOpnd(0));
	  let recv = asm.stack_pop(1);
	  if recv_type != Type::Fixnum {
	    asm_comment!(asm, &#34;guard object is fixnum&#34;);
	    asm.test(recv, Opnd::Imm(RUBY_FIXNUM_FLAG as i64));
             asm.jz(Target::side_exit(Counter::opt_succ_not_fixnum));
	  }
	
	  asm_comment!(asm, &#34;Integer#succ&#34;);
	  let out_val = asm.add(recv, Opnd::Imm(2)); // 2 is untagged Fixnum 1
	  asm.jo(Target::side_exit(Counter::opt_succ_overflow));
	
	  // Push the output onto the stack
	  let dst = asm.stack_push(Type::Fixnum);
	  asm.mov(dst, out_val);
	
	  true
	}

Oh nice! This is the actual YJIT Rust implementation of the `opt_succ` call. This is that optimization [@k0kobun](https://bsky.app/profile/k0kubun.com) made to further improve `opt_succ` performance beyond the bytecode C function calls. We’re in the section that is checking if what we’re operating on is a Fixnum, which is a way small integers are stored internally in CRuby:

	if recv_type != TypeFixnum 
	  asm_comment!(asm, &#34;guard object is fixnum&#34;);
	  asm.test(recv, Opnd::Imm(RUBY_FIXNUM_FLAG as i64));
	  asm.jz(Target::side_exit(Counter::opt_succ_not_fixnum));
	}

That becomes this machine code:

	# guard object is fixnum
	0x1096808c4: tst x1, #1
	0x1096808c8: b.eq #0x109683014

`asm.test` generates `tst x1, #1`, which according to an AI bot I asked is checking the least significant bit, which is a Fixnum “tag” that indicates this is a Fixnum. If it’s Fixnum, the result is 1 and `b.eq` is false. If it’s not a Fixnum, the result is `0` and `b.eq` is true and jumps away from this code.

	0x1096808cc: nop 
	0x1096808d0: nop 
	0x1096808d4: nop 
	0x1096808d8: nop 
	0x1096808dc: nop 

🤖 “NOPs for alignment/padding”. Thanks AI. I don’t know why it is needed, but at least I know what it probably is.

Finally, we _actually_ add 1 to the number. 

	asm_comment!(asm, &#34;Integer#succ&#34;);
	let out_val = asm.add(recv, Opnd::Imm(2)); // 2 is untagged Fixnum 1
	asm.jo(Target::side_exit(Counter::opt_succ_overflow));
	
	// Push the output onto the stack
	let dst = asm.stack_push(Type::Fixnum);
	asm.mov(dst, out_val);

The Rust code generates our `Integer#succ` comment. Then, to add 1, because of the “Fixnum tag” data embedded within our integer, actually means we have to add 2 using `adds x11, x1, #2` 😵‍💫. If we overflow the space available, it exits to a different code path - `b.vs` is a branch on overflow. Otherwise, it stores the result with `mov x1, x11`!

	# Integer#succ
	0x1096808e0: adds x11, x1, #2
	0x1096808e4: b.vs #0x109683048
	0x1096808e8: mov x1, x11

😮‍💨. That was a lot. And it seems like _alot_ of working is being done, but because it’s such low level machine code it’s presumably super fast. We examined a teensy tiny portion of what YJIT is capable of generating - JITs are complicated!

Thanks to [@k0kobun](https://bsky.app/profile/k0kubun.com) for providing me with the commands and pointing me at the [YJIT docs](https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md) which contain tons of additional options as well.

### The future of CRuby optimizations 
The irony of language implementation is that you often work less in the language you’re implementing than you do in something lower-level - in Ruby’s case, that’s mostly C and some Rust.

With a layer like YJIT, it potentially opens up a future where more of the language becomes plain Ruby, and Ruby developer contribution is easier. Many languages have a smaller low level core, and the majority of the language is written in itself (like Java, for instance). Maybe that’s a future for CRuby, someday! Until then, keep the YJIT optimizations coming, YJIT team!

[^1]:	Naive in this case meaning that there are more efficient ways to calculate fibonacci numbers in a program

[^2]:	MJIT, the precursor to YJIT, made Ruby much faster on certain benchmarks. But on large realistic Rails applications it actually [made things _slower_](https://bugs.ruby-lang.org/issues/14490)

[^3]:	When C code is running, it has to opt-in to releasing the GVL, so it’s more difficult for threads to corrupt or modify data mid-operation. The original Ruby version could yield the GVL at points that would invalidate the array. That’s my understanding of the situation anyways.

&lt;script src=&#34;https://jpcamara.com/d3.v7.min.js&#34;&gt;&lt;/script&gt;

&lt;script src=&#34;https://jpcamara.com/latency_visual.js&#34;&gt;&lt;/script&gt;
</source:markdown>
    </item>
    
    <item>
      <title>My MacOS setup for hacking on CRuby</title>
      <link>https://jpcamara.com/2024/12/02/my-macos-setup.html</link>
      <pubDate>Mon, 02 Dec 2024 22:59:05 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/12/02/my-macos-setup.html</guid>
      <description>&lt;p&gt;I recently posted &lt;a href=&#34;https://jpcamara.com/2024/11/27/my-docker-setup.html&#34;&gt;my docker setup for hacking on CRuby&lt;/a&gt;, which showed how I test Linux features when working on CRuby. But most of the time, I just build CRuby directly on MacOS.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html&#34;&gt;Building Ruby&lt;/a&gt; guide from ruby-lang.org is the most up-to-date guide on doing this, but I like to spell it out exactly in order of how I do it day-to-day. So this is for me more than anything, but you may find it helpful!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /bin/bash -c &amp;#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&amp;#34;&lt;/span&gt;
xcode-select --install
brew update
brew install openssl@3
brew install autoconf
brew install gperf
brew install libffi
brew install libyaml
brew install zlib
brew install gmp
curl --proto &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;=https&amp;#39;&lt;/span&gt; --tlsv1.2 -sSf https://sh.rustup.rs | sh

export CONFIGURE_ARGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; ext in openssl readline libyaml zlib; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  CONFIGURE_ARGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;CONFIGURE_ARGS&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; --with-&lt;/span&gt;$ext&lt;span style=&#34;color:#e6db74&#34;&gt;-dir=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;brew --prefix $ext&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;

./autogen.sh
mkdir build &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; cd build
mkdir ./.rubies

../configure --prefix&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/path/to/ruby/build/.rubies/ruby-master&amp;#34;&lt;/span&gt; --disable-install-doc --config-cache --enable-debug-env optflags&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-O0 -fno-omit-frame-pointer&amp;#34;&lt;/span&gt; CFLAGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-DRUBY_DEBUG -O0&amp;#34;&lt;/span&gt; --with-opt-dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;brew --prefix gmp&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;brew --prefix jemalloc&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;

make install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s pretty much everything I do when setting things up!&lt;/p&gt;
&lt;p&gt;If you want to run YJIT in &amp;ldquo;dev&amp;rdquo; mode, you add &lt;code&gt;--enable-yjit=dev&lt;/code&gt; to the &lt;code&gt;configure&lt;/code&gt; call:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;../configure --prefix&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/path/to/ruby/build/.rubies/ruby-master&amp;#34;&lt;/span&gt; --disable-install-doc --config-cache --enable-debug-env optflags&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-O0 -fno-omit-frame-pointer&amp;#34;&lt;/span&gt; CFLAGS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;-DRUBY_DEBUG -O0&amp;#34;&lt;/span&gt; --with-opt-dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;brew --prefix gmp&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;:&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;brew --prefix jemalloc&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt; --enable-yjit&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here, the simplest way to run some code is to place a &lt;code&gt;test.rb&lt;/code&gt; file in the root of the project and run it using &lt;code&gt;make runruby&lt;/code&gt;. To run it in a debug mode, you can run &lt;code&gt;make lldb-ruby&lt;/code&gt;.&lt;/p&gt;
</description>
      <source:markdown>I recently posted [my docker setup for hacking on CRuby](https://jpcamara.com/2024/11/27/my-docker-setup.html), which showed how I test Linux features when working on CRuby. But most of the time, I just build CRuby directly on MacOS. 

The [Building Ruby](https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html) guide from ruby-lang.org is the most up-to-date guide on doing this, but I like to spell it out exactly in order of how I do it day-to-day. So this is for me more than anything, but you may find it helpful!

```bash
# /bin/bash -c &#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&#34;
xcode-select --install
brew update
brew install openssl@3
brew install autoconf
brew install gperf
brew install libffi
brew install libyaml
brew install zlib
brew install gmp
curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh

export CONFIGURE_ARGS=&#34;&#34;
for ext in openssl readline libyaml zlib; do
  CONFIGURE_ARGS=&#34;${CONFIGURE_ARGS} --with-$ext-dir=$(brew --prefix $ext)&#34;
done

./autogen.sh
mkdir build &amp;&amp; cd build
mkdir ./.rubies

../configure --prefix=&#34;/path/to/ruby/build/.rubies/ruby-master&#34; --disable-install-doc --config-cache --enable-debug-env optflags=&#34;-O0 -fno-omit-frame-pointer&#34; CFLAGS=&#34;-DRUBY_DEBUG -O0&#34; --with-opt-dir=$(brew --prefix gmp):$(brew --prefix jemalloc)

make install
```
That&#39;s pretty much everything I do when setting things up!

If you want to run YJIT in &#34;dev&#34; mode, you add `--enable-yjit=dev` to the `configure` call:

```bash
../configure --prefix=&#34;/path/to/ruby/build/.rubies/ruby-master&#34; --disable-install-doc --config-cache --enable-debug-env optflags=&#34;-O0 -fno-omit-frame-pointer&#34; CFLAGS=&#34;-DRUBY_DEBUG -O0&#34; --with-opt-dir=$(brew --prefix gmp):$(brew --prefix jemalloc) --enable-yjit=dev
```
From here, the simplest way to run some code is to place a `test.rb` file in the root of the project and run it using `make runruby`. To run it in a debug mode, you can run `make lldb-ruby`.
</source:markdown>
    </item>
    
    <item>
      <title>Counting C method calls in CRuby</title>
      <link>https://jpcamara.com/2024/11/28/counting-c-method.html</link>
      <pubDate>Thu, 28 Nov 2024 06:30:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/11/28/counting-c-method.html</guid>
      <description>&lt;p&gt;There is a central macro in CRuby, &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt;, which is a very hot path for the Ruby runtime. It&amp;rsquo;s an important part of how threads are managed, and it&amp;rsquo;s called &lt;em&gt;constantly&lt;/em&gt;. I was curious just how often it was called, and it turned out CRuby comes with some handy debugging functionality for just this scenario.&lt;/p&gt;
&lt;p&gt;Inside of &lt;code&gt;debug_counter.h&lt;/code&gt;, I changed &lt;code&gt;#define USE_DEBUG_COUNTER 0&lt;/code&gt; to &lt;code&gt;#define USE_DEBUG_COUNTER 1&lt;/code&gt; and added this line later in that file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;RB_DEBUG_COUNTER(rb_vm_check_ints)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then inside &lt;code&gt;vm_core.h&lt;/code&gt; I updated &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt; to add a debug increment:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define RUBY_VM_CHECK_INTS(ec) rb_vm_check_ints(ec)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inline&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;
&lt;span style=&#34;color:#a6e22e&#34;&gt;rb_vm_check_ints&lt;/span&gt;(rb_execution_context_t &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;ec)
{
    RB_DEBUG_COUNTER_INC(rb_vm_check_ints); &lt;span style=&#34;color:#75715e&#34;&gt;// increment!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that I ran the following simple Ruby program:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10_000&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times {}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this was printed after it ran:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;[RUBY_DEBUG_COUNTER]    rb_vm_check_ints    21,055
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Iterating a loop ten thousand times results in twenty thousand calls to &lt;code&gt;RUBY_VM_CHECK_INTS&lt;/code&gt;, exactly what I was looking to measure!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to know the proper configuration to compile without having to manually modify &lt;code&gt;USE_DEBUG_COUNTER&lt;/code&gt; in the header file. Maybe someone can comment and let me know how? It has something to do with &lt;code&gt;CFLAGS&lt;/code&gt;, I think.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update 12/5/24&lt;/em&gt; Thanks to &lt;a href=&#34;https://ruby.social/@onghu/113581774915874979&#34;&gt;Mohit Sindhwani for some advice on how to add the CFLAGS!&lt;/a&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-05-at-7.13.06pm.png&#34; width=&#34;600&#34; height=&#34;357&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
</description>
      <source:markdown>There is a central macro in CRuby, `RUBY_VM_CHECK_INTS`, which is a very hot path for the Ruby runtime. It&#39;s an important part of how threads are managed, and it&#39;s called _constantly_. I was curious just how often it was called, and it turned out CRuby comes with some handy debugging functionality for just this scenario.

Inside of `debug_counter.h`, I changed `#define USE_DEBUG_COUNTER 0` to `#define USE_DEBUG_COUNTER 1` and added this line later in that file:

```c
RB_DEBUG_COUNTER(rb_vm_check_ints)
```
Then inside `vm_core.h` I updated `RUBY_VM_CHECK_INTS` to add a debug increment:

```c
#define RUBY_VM_CHECK_INTS(ec) rb_vm_check_ints(ec)
static inline void
rb_vm_check_ints(rb_execution_context_t *ec)
{
    RB_DEBUG_COUNTER_INC(rb_vm_check_ints); // increment!
```
After that I ran the following simple Ruby program:

```ruby
10_000.times {}
```
And this was printed after it ran:

```text
[RUBY_DEBUG_COUNTER]    rb_vm_check_ints    21,055
```
Iterating a loop ten thousand times results in twenty thousand calls to `RUBY_VM_CHECK_INTS`, exactly what I was looking to measure!

I&#39;d like to know the proper configuration to compile without having to manually modify `USE_DEBUG_COUNTER` in the header file. Maybe someone can comment and let me know how? It has something to do with `CFLAGS`, I think.

*Update 12/5/24* Thanks to [Mohit Sindhwani for some advice on how to add the CFLAGS!](https://ruby.social/@onghu/113581774915874979)
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/screenshot-2024-12-05-at-7.13.06pm.png&#34; width=&#34;600&#34; height=&#34;357&#34; alt=&#34;&#34;&gt;
</source:markdown>
    </item>
    
    <item>
      <title>My docker setup for hacking on CRuby</title>
      <link>https://jpcamara.com/2024/11/27/my-docker-setup.html</link>
      <pubDate>Wed, 27 Nov 2024 06:43:15 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/11/27/my-docker-setup.html</guid>
      <description>&lt;p&gt;I run on MacOS, but I often want to test Linux behaviors when working on the CRuby implementation.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the &lt;code&gt;Dockerfile&lt;/code&gt; I use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;FROM ubuntu:24.04

# Preventing dialog prompts when installing packages
ENV DEBIAN_FRONTEND=noninteractive

# Update and install basic build dependencies and Rust
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y \
    git \
    curl \
    build-essential \
    autoconf \
    libreadline-dev \
    libssl-dev \
    libyaml-dev \
    libncurses5-dev \
    zlib1g-dev \
    libffi-dev \
    bison \
    libgdbm-dev \
    libgdbm-compat-dev \
    libreadline6-dev \
    libssl-dev \
    libgmp-dev \
    liburing-dev \
    &amp;amp;&amp;amp; apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Install Rust via rustup
RUN curl --proto &amp;#39;=https&amp;#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

RUN apt-get update &amp;amp;&amp;amp; apt-get install -y ruby
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y gdb

# Add Rust to the PATH so the cargo and rustc commands are available
ENV PATH=&amp;#34;/root/.cargo/bin:${PATH}&amp;#34;

# Create a directory for the Ruby source code
WORKDIR /usr/src/ruby

# Copy Ruby source code from your local directory
COPY . .

# This will be the default command when you run the container.
CMD [ &amp;#34;/bin/bash&amp;#34; ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To run the &lt;code&gt;Dockerfile&lt;/code&gt;, you can use the following two commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build -t ruby-source-build-env .
docker run -it --mount type=bind,src=.,target=/usr/src/ruby ruby-source-build-env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Based on our &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;docker run&lt;/code&gt; will open up a &lt;code&gt;bash&lt;/code&gt; shell for you. From there, I run the following commands to build CRuby:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./autogen.sh
mkdir build &amp;amp;&amp;amp; cd build
mkdir ./.rubies
../configure --prefix=&amp;quot;/usr/src/ruby/build/.rubies/ruby-master&amp;quot; --disable-install-doc --config-cache --enable-debug-env optflags=&amp;quot;-O0 -fno-omit-frame-pointer&amp;quot;
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have CRuby operating under Ubuntu Linux! From here, the simplest way to run some code is to place a &lt;code&gt;test.rb&lt;/code&gt; file in the root of the project and run it using &lt;code&gt;make runruby&lt;/code&gt;.&lt;/p&gt;
</description>
      <source:markdown>I run on MacOS, but I often want to test Linux behaviors when working on the CRuby implementation.


Here&#39;s the `Dockerfile` I use:


```text
FROM ubuntu:24.04

# Preventing dialog prompts when installing packages
ENV DEBIAN_FRONTEND=noninteractive

# Update and install basic build dependencies and Rust
RUN apt-get update &amp;&amp; apt-get install -y \
    git \
    curl \
    build-essential \
    autoconf \
    libreadline-dev \
    libssl-dev \
    libyaml-dev \
    libncurses5-dev \
    zlib1g-dev \
    libffi-dev \
    bison \
    libgdbm-dev \
    libgdbm-compat-dev \
    libreadline6-dev \
    libssl-dev \
    libgmp-dev \
    liburing-dev \
    &amp;&amp; apt-get clean &amp;&amp; rm -rf /var/lib/apt/lists/*

# Install Rust via rustup
RUN curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

RUN apt-get update &amp;&amp; apt-get install -y ruby
RUN apt-get update &amp;&amp; apt-get install -y gdb

# Add Rust to the PATH so the cargo and rustc commands are available
ENV PATH=&#34;/root/.cargo/bin:${PATH}&#34;

# Create a directory for the Ruby source code
WORKDIR /usr/src/ruby

# Copy Ruby source code from your local directory
COPY . .

# This will be the default command when you run the container.
CMD [ &#34;/bin/bash&#34; ]
```
To run the `Dockerfile`, you can use the following two commands:




    docker build -t ruby-source-build-env .
    docker run -it --mount type=bind,src=.,target=/usr/src/ruby ruby-source-build-env




Based on our `Dockerfile`, `docker run` will open up a `bash` shell for you. From there, I run the following commands to build CRuby:




    ./autogen.sh
    mkdir build &amp;&amp; cd build
    mkdir ./.rubies
    ../configure --prefix=&#34;/usr/src/ruby/build/.rubies/ruby-master&#34; --disable-install-doc --config-cache --enable-debug-env optflags=&#34;-O0 -fno-omit-frame-pointer&#34;
    make install




We now have CRuby operating under Ubuntu Linux! From here, the simplest way to run some code is to place a `test.rb` file in the root of the project and run it using `make runruby`.
</source:markdown>
    </item>
    
    <item>
      <title>Calculating the largest known prime in Ruby</title>
      <link>https://jpcamara.com/2024/11/26/looking-to-impress.html</link>
      <pubDate>Tue, 26 Nov 2024 21:53:43 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/11/26/looking-to-impress.html</guid>
      <description>&lt;p&gt;Looking to impress your Ruby friends by calculating the largest known prime, &lt;code&gt;2 ** 136_279_841-1&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;On Ruby 3.4.0-preview2 and earlier, &lt;code&gt;2 ** 136_279_841-1&lt;/code&gt; logs a warning and returns Infinity 😔:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;**&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;136_279_841&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# warning: in a**b, b may be too big&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# =&amp;gt; Infinity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks to &lt;a href=&#34;http://mametter.bsky.social&#34;&gt;@mametter&lt;/a&gt;, Ruby 3.4 will handle this calculation just fine! See &lt;a href=&#34;https://github.com/ruby/ruby/pull/12033&#34;&gt;Do not round &lt;code&gt;a**b&lt;/code&gt; to infinity&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Knowing this, you excitedly use your ruby manager of choice to pull down ruby master:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;rvm install ruby-head
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You run &lt;code&gt;ruby -e &amp;quot;puts 2 ** 136_279_841-1&amp;quot;&lt;/code&gt;, and your excitement is slowly eroded. An hour into calculating, you terminate the command in frustration 😫.&lt;/p&gt;
&lt;p&gt;Is &lt;a href=&#34;http://mametter.bsky.social&#34;&gt;@mametter&lt;/a&gt; a &lt;em&gt;liar&lt;/em&gt;?!&lt;/p&gt;
&lt;p&gt;As it turns out, there is critically important library you need for accelerating &amp;ldquo;Bignum&amp;rdquo; calculations: &lt;a href=&#34;https://gmplib.org/&#34;&gt;GMP, the GNU Multiple Precision Arithmetic Library&lt;/a&gt;. It&amp;rsquo;s even specifically mentioned in the &lt;a href=&#34;https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html&#34;&gt;CRuby guide to building ruby&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Without it, you can kiss your largest prime calculating dreams goodbye 👋.&lt;/p&gt;
&lt;p&gt;You reinstall ruby head, making sure &lt;code&gt;gmp&lt;/code&gt; is available&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;brew install gmp
rvm reinstall ruby-head --with-gmp-dir=$(brew --prefix gmp)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With a bit of hope in your heart, you try again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;ruby -e &amp;#34;puts 2 ** 136_279_841-1&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Success! &lt;a href=&#34;http://mametter.bsky.social&#34;&gt;@mametter&lt;/a&gt; was telling the truth!&lt;/p&gt;
&lt;p&gt;Within around 5 seconds, your terminal is filled with a beautiful output of 41,024,320 digits. Your Ruby friends cheer and carry you off on their shoulders.&lt;/p&gt;
&lt;p&gt;This was all inspired by &lt;a href=&#34;https://matz.bsky.social&#34;&gt;Matz&lt;/a&gt;&amp;rsquo;s keynote at RubyConf 2024 - where he mentioned that Ruby 3.4 can now calculate the largest known prime. For fun, I tried it on my mac and just let it keep running - 2 hours later, it was still running! I&amp;rsquo;d never heard of &lt;a href=&#34;https://gmplib.org/&#34;&gt;GMP&lt;/a&gt;, but now I know!&lt;/p&gt;
</description>
      <source:markdown>Looking to impress your Ruby friends by calculating the largest known prime, `2 ** 136_279_841-1`?


On Ruby 3.4.0-preview2 and earlier, `2 ** 136_279_841-1` logs a warning and returns Infinity 😔:

```ruby
2 ** 136_279_841-1
# warning: in a**b, b may be too big
# =&gt; Infinity
```
Thanks to [@mametter](http://mametter.bsky.social), Ruby 3.4 will handle this calculation just fine! See [Do not round `a**b` to infinity](https://github.com/ruby/ruby/pull/12033).

Knowing this, you excitedly use your ruby manager of choice to pull down ruby master:

```text
rvm install ruby-head
```
You run `ruby -e &#34;puts 2 ** 136_279_841-1&#34;`, and your excitement is slowly eroded. An hour into calculating, you terminate the command in frustration 😫. 


Is [@mametter](http://mametter.bsky.social) a _liar_?!

As it turns out, there is critically important library you need for accelerating &#34;Bignum&#34; calculations: [GMP, the GNU Multiple Precision Arithmetic Library](https://gmplib.org/). It&#39;s even specifically mentioned in the [CRuby guide to building ruby](https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html).

Without it, you can kiss your largest prime calculating dreams goodbye 👋.

You reinstall ruby head, making sure `gmp` is available

```text
brew install gmp
rvm reinstall ruby-head --with-gmp-dir=$(brew --prefix gmp)
```
With a bit of hope in your heart, you try again:

```text
ruby -e &#34;puts 2 ** 136_279_841-1&#34;
```
Success! [@mametter](http://mametter.bsky.social) was telling the truth!

Within around 5 seconds, your terminal is filled with a beautiful output of 41,024,320 digits. Your Ruby friends cheer and carry you off on their shoulders.

This was all inspired by [Matz](https://matz.bsky.social)&#39;s keynote at RubyConf 2024 - where he mentioned that Ruby 3.4 can now calculate the largest known prime. For fun, I tried it on my mac and just let it keep running - 2 hours later, it was still running! I&#39;d never heard of [GMP](https://gmplib.org/), but now I know!
</source:markdown>
    </item>
    
    <item>
      <title>The Thread API : Concurrent, colorless Ruby</title>
      <link>https://jpcamara.com/2024/08/26/the-thread-api-concurrent-colorless.html</link>
      <pubDate>Mon, 26 Aug 2024 17:59:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/08/26/the-thread-api-concurrent-colorless.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/group-74.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Thread API&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “The Thread API: Concurrent, colorless Ruby”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#the-thread-api&#34;&gt;The Thread API&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#the-cute-mascot&#34;&gt;Don’t let the cute mascot fool you&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-api-primer&#34;&gt;A thread api primer (with examples!)&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#waiting-to-complete&#34;&gt;Waiting for a thread to complete&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-join&#34;&gt;&lt;code&gt;join&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-value&#34;&gt;&lt;code&gt;value&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-join-timeout&#34;&gt;&lt;code&gt;join(timeout_in_seconds)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-error-reporting&#34;&gt;Error reporting&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-exceptions&#34;&gt;&lt;code&gt;report_on_exception&lt;/code&gt; and &lt;code&gt;abort_on_exception&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-tracking&#34;&gt;Tracking threads&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-list&#34;&gt;&lt;code&gt;Thread.list&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-name&#34;&gt;&lt;code&gt;name&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-statuses&#34;&gt;&lt;code&gt;status&lt;/code&gt;, &lt;code&gt;alive?&lt;/code&gt; and &lt;code&gt;stop?&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-scheduling&#34;&gt;Thread scheduling&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-schedule-methods&#34;&gt;&lt;code&gt;Thread.pass&lt;/code&gt;, &lt;code&gt;wakeup&lt;/code&gt;, &lt;code&gt;Thread.stop&lt;/code&gt;, &lt;code&gt;run&lt;/code&gt;, and &lt;code&gt;priority&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-shutdown&#34;&gt;Thread shutdown&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#thread-raise-kill&#34;&gt;&lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-handle-interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#process-fork&#34;&gt;&lt;code&gt;Process._fork&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-coordination&#34;&gt;Coordinating threads&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#mutex&#34;&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#condition-variable&#34;&gt;&lt;code&gt;ConditionVariable&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#monitor&#34;&gt;&lt;code&gt;Monitor&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#queue&#34;&gt;&lt;code&gt;Queue&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#sized-queue&#34;&gt;&lt;code&gt;SizedQueue&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#thread-group&#34;&gt;&lt;code&gt;ThreadGroup&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#memory-visibility&#34;&gt;Memory visibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-thread-api&#34;&gt;The Thread API 🧵&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re going to break down threads into three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Thread API - All the tools available to you in the Ruby runtime to manage threads, and how they work&lt;/li&gt;
&lt;li&gt;Interrupting Threads - How threads get stuck, and how to shut them down safely&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends - Thread architecture and the GVL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post covers the Thread API. We&amp;rsquo;ll go over every method available to you, why they matter, how to call them, and often how popular open source projects use them.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ab73de82d9.png&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;the-cute-mascot&#34;&gt;Don’t let the cute mascot fool you&lt;/h3&gt;
&lt;p&gt;Before we start digging into threads, I’d like to make a small disclaimer: &lt;strong&gt;writing safe, deterministic, bug-free threaded code is hard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I think understanding threads is valuable knowledge. After all, whether you explicitly use threads or not, &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;your Ruby programs are always multi-threaded&lt;/a&gt;. If you’re always in the context of a thread, it’s helpful to know how they work behind the scenes. Even if the most you ever do is set a thread count on a server, it still helps to know how they work - it better informs what changing those numbers can and can’t do for you.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;seems easy enough? 🤷🏻‍♂️&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Behind the simplicity of the thread interface lives a lot of complexity. An OS thread gives you the literal ability to perform tasks in &lt;em&gt;parallel&lt;/em&gt;. And once things can run in parallel, our sequential thinking starts to fail us. It’s difficult to correctly read through code step by step if at any point that step by step code can swap out with another separate piece of code. No warning, no ability to determine exactly where your program will switch to next.&lt;/p&gt;
&lt;p&gt;It’s the same when multiple people work on some tasks in parallel - you encounter communication breakdown. There can be contention over a shared resource. You can undo someone else’s work, or leave them with inconsistent information that causes them to finish their task, but incorrectly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/90bc5383-008d-42e3-8532-29cf8c6ad832.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There’s a cognitive bias known as the &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect&#34;&gt;Dunning Kruger Effect&lt;/a&gt;. Essentially someone with limited experience in something can overestimate their abilities, or the complexity of the task. &lt;code&gt;Thread.new {}&lt;/code&gt; feels pretty simple - it’s easy to underestimate what goes into it. Diving into threads together helps us realize they wield a lot of power!&lt;/p&gt;
&lt;p&gt;There’s a reason that most threaded code in gems like Rails, SolidQueue and GoodJob use the &lt;code&gt;concurrent-ruby&lt;/code&gt; gem. Abstractions are your friend. We’ll dig into abstractions later in the series - for now we’ll learn about threads directly. But dont stop here! Learn the foundation, and when you need it yourself, learn the abstractions and tools that make it easier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/24c9850ebd.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: Eli and JP Camara, &lt;a href=&#34;https://x.com/logicalcomic&#34;&gt;https://x.com/logicalcomic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This post will dig into all the options available in Ruby out of the box. You’ll learn each thread method and what they’re for, and we’ll discuss how to coordinate your threads once you create them. Let’s go!&lt;/p&gt;
&lt;h3 id=&#34;thread-api-primer&#34;&gt;A &lt;a href=&#34;https://rubyapi.org/3.3/o/thread&#34;&gt;thread&lt;/a&gt; api primer (with examples!)&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ll start off with some details on how you interact with threads. Let&amp;rsquo;s create a thread!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finished!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run that code&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; 👆🏼… hmmm, nothing happens. Nothing prints and the program exits silently. What happened?&lt;/p&gt;
&lt;p&gt;Everything starts off running in the “main thread”, which is accessible by calling &lt;code&gt;Thread.main&lt;/code&gt;. When a new thread is created it’s like a branch off of the main thread. Those branches exist independently and the main thread doesn’t wait for them to finish by default.&lt;/p&gt;
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-without-join.drawio.png&#34; width=&#34;453&#34; height=&#34;409&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;waiting-to-complete&#34;&gt;Waiting for a thread to complete&lt;/h3&gt;
&lt;h4 id=&#34;thread-join&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-join&#34;&gt;&lt;code&gt;join&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To wait on a specific thread, you &lt;code&gt;join&lt;/code&gt; that thread and the current thread together. Here we have the main thread join with &lt;code&gt;t&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finished!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# We&amp;#39;re at the top level in `Thread.main`, which &amp;#34;joins&amp;#34; with `t` until it finishes&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# finished!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-with-join.drawio.png&#34; width=&#34;529&#34; height=&#34;448&#34; alt=&#34;&#34;&gt;
&lt;p&gt;When the thread finishes, the thread object is returned from &lt;code&gt;join&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;h4 id=&#34;thread-value&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-value&#34;&gt;&lt;code&gt;value&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can also join on a thread and ask it for the last value returned using &lt;code&gt;value&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;File&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read(a_file_path)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value &lt;span style=&#34;color:#75715e&#34;&gt;# contents of `a_file_path`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The simplest way to run some work concurrently is to start up several threads and then iterate over each one calling &lt;code&gt;join&lt;/code&gt; or &lt;code&gt;value&lt;/code&gt;. We’ll make 4 http requests here, and the overall time will take as long as the longest request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# frozen_string_literal: true&lt;/span&gt;
	
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;json&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;generate_uuid_thread&lt;/span&gt;
  url &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/uuid&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Net&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;HTTP&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(&lt;span style=&#34;color:#66d9ef&#34;&gt;URI&lt;/span&gt;(url))
    &lt;span style=&#34;color:#66d9ef&#34;&gt;JSON&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;parse(response)&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uuid&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
uuids &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;times&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  generate_uuid_thread
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:value&lt;/span&gt;)
puts uuids
&lt;span style=&#34;color:#75715e&#34;&gt;# 6b76167d-9bac-45dc-8d0b-5b7af865b843&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# a025a125-51a5-4773-b78d-ab93fba02eb3&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 98befb95-adf7-4c89-9fd0-d10f6b2a3d7a&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 95f486fd-5bc6-46fe-b65a-c52a87140dfb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-parallel-io.drawio.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;&#34;&gt;
&lt;p&gt;Make sure you let each thread start first! If you join too early, you end up running them sequentially:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;uuids &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;
uuids &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; generate_uuid_thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value
uuids &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; generate_uuid_thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value
uuids &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; generate_uuid_thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value
uuids &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; generate_uuid_thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value
puts uuids
&lt;span style=&#34;color:#75715e&#34;&gt;# 6b76167d-9bac-45dc-8d0b-5b7af865b843&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# a025a125-51a5-4773-b78d-ab93fba02eb3&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 98befb95-adf7-4c89-9fd0-d10f6b2a3d7a&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 95f486fd-5bc6-46fe-b65a-c52a87140dfb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The end result looks identical, and the &lt;em&gt;output&lt;/em&gt; is but the &lt;em&gt;execution&lt;/em&gt; is totally different. When we create all the threads up front, certain operations can run in parallel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Threads 1 through 4 get created and are “runnable”&lt;/li&gt;
&lt;li&gt;The main thread blocks on the first iteration to call &lt;code&gt;map(&amp;amp;:join)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;As each thread blocks on the HTTP call, they run in parallel&lt;/li&gt;
&lt;li&gt;The loop takes as long as the longest thread, so if it takes us 50ms for 3 requests, and the 4th is 100ms, we spend around 100ms total&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Calling &lt;code&gt;generate_uuid_thread.value&lt;/code&gt; one at a time, we’re just running the code sequentially:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread 1 gets created and run, returning its &lt;code&gt;value&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Thread 2 gets created and run, returning its &lt;code&gt;value&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Same for threads 3 and 4&lt;/li&gt;
&lt;li&gt;We’re running sequentially, so the threads provided no value. It would take roughly 250ms. If anything, the threads likely &lt;em&gt;added&lt;/em&gt; overhead.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ Never &lt;em&gt;actually&lt;/em&gt; generate a uuid using a web service, that’s crazy 🤣&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;h4 id=&#34;thread-join-timeout&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-join&#34;&gt;&lt;code&gt;join(timeout_in_seconds)&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;You can also limit the amount of time you &lt;code&gt;join&lt;/code&gt;. If you’re calling &lt;code&gt;join&lt;/code&gt;, typically you just want to wait however long it takes. But using &lt;code&gt;join(seconds)&lt;/code&gt; could be useful to periodically pop into some other work or alert that you’ve been running too long. &lt;code&gt;join(seconds)&lt;/code&gt; will return the thread while it is still running, and return &lt;code&gt;nil&lt;/code&gt; once it finishes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;alive?
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;wait a bit more... &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... #&amp;lt;Thread:0x0... main.rb:116 dead&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# done!&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fib&lt;/span&gt;(n)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; n &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
  fib(n &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; fib(n &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  fib(&lt;span style=&#34;color:#ae81ff&#34;&gt;40&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;alive?
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;wait a bit more... &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done! &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# get returned even after a join&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# wait a bit more... &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# done! 9227465&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You could use it to intermittently check for a thread finishing without wasting too many CPU cycles:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;still waiting...&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# done!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;a href=&#34;https://github.com/puma/puma&#34;&gt;Puma web server&lt;/a&gt;, it uses &lt;code&gt;join(seconds)&lt;/code&gt; to manage &lt;a href=&#34;https://github.com/puma/puma/blob/master/lib/puma/thread_pool.rb&#34;&gt;shutting down its thread pool&lt;/a&gt;. It iterates over each thread, adjusting the &lt;code&gt;join&lt;/code&gt; timeout based on how much time has elapsed since starting the method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;join &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;(inner_timeout) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  start &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clock_gettime(
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CLOCK_MONOTONIC&lt;/span&gt;
  )
  threads&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;reject! &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
    elapsed &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;clock_gettime(
      &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CLOCK_MONOTONIC&lt;/span&gt;
    ) &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; start
    t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join inner_timeout &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; elapsed
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Wait +timeout+ seconds for threads to finish.&lt;/span&gt;
join&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call(timeout)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It uses &lt;code&gt;join(timeout)&lt;/code&gt; to try and let each thread finish before forcing a shutdown with &lt;code&gt;raise&lt;/code&gt; or &lt;code&gt;kill&lt;/code&gt;. The &lt;code&gt;reject!&lt;/code&gt; removes any threads that finish during that time - &lt;code&gt;nil&lt;/code&gt; returned from &lt;code&gt;join&lt;/code&gt; means the thread is still running, otherwise the thread object is returned (removing it from the array).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 A “thread pool” is a reusable, typically fixed size set of threads. Rather than create brand new threads every time they are needed, a thread pool saves on thread creation by reusing threads to perform operations. They are generally used to save on thread creation cost and limit the number of threads running at a time. &lt;code&gt;concurrent-ruby&lt;/code&gt; comes with several types of thread pools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;thread-error-reporting&#34;&gt;Error Reporting&lt;/h3&gt;
&lt;h4 id=&#34;thread-exceptions&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-report_on_exception&#34;&gt;&lt;code&gt;report_on_exception&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-abort_on_exception&#34;&gt;&lt;code&gt;abort_on_exception&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;What happens if something fails in your thread?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;oops!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
sleep &lt;span style=&#34;color:#75715e&#34;&gt;# does the program sleep forever, or raise an error?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running that 👆🏼, it just runs forever. Even though our thread has failed, it never impacts the program. We will see an error printed in our console, however:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;terminated with exception (report_on_exception is true):
main.rb:2:in `block in &amp;lt;main&amp;gt;`: oops! (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can control whether it fails silently, but the default is to report the error. Keep that default - silent errors are not your friend. But if you &lt;em&gt;really&lt;/em&gt; needed to, you can change it globally or with a per thread setting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# No threads log their errors ☠️ &lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;report_on_exception &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# This individual thread does not log&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;report_on_exception &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What if you want to report it in the current thread? Similar to &lt;code&gt;join&lt;/code&gt;ing execution with a thread to wait for it to finish, you need to join with it to raise its error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;oops!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join &lt;span style=&#34;color:#75715e&#34;&gt;# or .value, raises &amp;#34;oops!&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also force the thread to raise, even when running independently:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;listen to me!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;abort_on_exception &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# t blows up the program!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This can be set globally as well:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;abort_on_exception &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;listen to me!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# t blows up the program!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rack timeout uses it in &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb&#34;&gt;its Scheduler thread&lt;/a&gt;, applying it using &lt;code&gt;Thread.current&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run_loop!&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;abort_on_exception &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In general, you don’t see this much in a production setting. But it could be useful if you’re writing a one-off script and you want any thread that fails to kill the program.&lt;/p&gt;
&lt;h3 id=&#34;thread-tracking&#34;&gt;Tracking threads&lt;/h3&gt;
&lt;p&gt;Can we find out what threads are running?&lt;/p&gt;
&lt;h4 id=&#34;thread-list&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-list&#34;&gt;&lt;code&gt;Thread.list&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Thread.list&lt;/code&gt; will give you every thread that hasn&amp;rsquo;t already finished:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new {}
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
	
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... run&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... main.rb:1 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... main.rb:3 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... main.rb:4 dead&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;thread-name&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-name&#34;&gt;&lt;code&gt;name&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Ok… but how can you differentiate them? There’s a few ways you can achieve that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;File information&lt;/li&gt;
&lt;li&gt;Setting a name&lt;/li&gt;
&lt;li&gt;Inheritance&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;File information gets printed by default, and shows you the name of the file and what line the thread was started on:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
t3 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
t4 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
	
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:1 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:3 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:4 sleep&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The file info is useful, but often your threads are all started from the same place, so it doesn’t tell you much:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;start_thread&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start_thread
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start_thread
t3 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start_thread
t4 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; start_thread
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
	
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In that case, you can set a &lt;code&gt;name&lt;/code&gt; on each thread to differentiate them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;main&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;first!&amp;#34;&lt;/span&gt;
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;second!&amp;#34;&lt;/span&gt;
t3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;third!&amp;#34;&lt;/span&gt;
t4&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fourth!&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
	
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@first!  main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@second! main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@third!  main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@fourth! main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href=&#34;https://github.com/ruby-concurrency/concurrent-ruby&#34;&gt;concurrent-ruby&lt;/a&gt; gem uses this to help differentiate threads created in its &lt;a href=&#34;https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb&#34;&gt;ThreadPoolExecutor&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# concurrent/executor/ruby_thread_pool_executor.rb&lt;/span&gt;
&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;@thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;http&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;//&lt;/span&gt;thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;worker&amp;#39;&lt;/span&gt;, id&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;compact&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
	
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;concurrent&amp;#34;&lt;/span&gt;
	
pool &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Concurrent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(
  name: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;🤖&amp;#34;&lt;/span&gt;
)
pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
pool&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;post { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@🤖-worker-1...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@🤖-worker-2...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@🤖-worker-3...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...@🤖-worker-4...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href=&#34;https://github.com/honeybadger-io/honeybadger-ruby&#34;&gt;Honeybadger gem&lt;/a&gt; runs a &lt;a href=&#34;https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/worker.rb&#34;&gt;background thread&lt;/a&gt; when sending errors to their error reporting service. To differentiate their thread, they use inheritance. The information about each thread includes the class so it makes it easy to identify:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;module&lt;/span&gt; Honeybadger
  &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Worker&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Thread&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Honeybadger&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Worker&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0... run&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Honeybadger::Worker::Thread:0x0... main.rb:119 sleep&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;———&lt;/p&gt;
&lt;h4 id=&#34;thread-statuses&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-status&#34;&gt;&lt;code&gt;status&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-alive-3F&#34;&gt;&lt;code&gt;alive?&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-stop-3F&#34;&gt;&lt;code&gt;stop?&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Can you keep track of the status of a running thread? Yep! Threads operate in one of 5 states - they’re not the &lt;em&gt;most&lt;/em&gt; intuitive, but they’re what you’ve got:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;“run”&lt;/code&gt; - the thread is running&lt;/li&gt;
&lt;li&gt;&lt;code&gt;“sleep”&lt;/code&gt; - the thread is “sleeping”. There is some blocking operation going on or the thread went to sleep or was put to sleep by the thread scheduler&lt;/li&gt;
&lt;li&gt;&lt;code&gt;“aborting”&lt;/code&gt; - the thread has failed but hasn’t finished running yet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nil&lt;/code&gt; - an error was raised and the thread is dead&lt;/li&gt;
&lt;li&gt;&lt;code&gt;false&lt;/code&gt; - the thread finished normally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s demonstrate some statuses:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bye bye&amp;#34;&lt;/span&gt;) }
b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop }
c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new {}
d &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { 
  &lt;span style=&#34;color:#66d9ef&#34;&gt;IO&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;select(&lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)
}
d&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;# wait on d for 1 second&lt;/span&gt;
puts a&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;class          &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; NilClass&lt;/span&gt;
puts b&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status                &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; &amp;#34;sleep&amp;#34;&lt;/span&gt;
puts c&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status                &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; false&lt;/span&gt;
puts d&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status                &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; &amp;#34;sleep&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status   &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; &amp;#34;run&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You’ll notice a few things about the above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I didn’t show “aborting”. That’s because I’m not sure regular code could ever see that status. Catching a failed thread that hasn’t finished yet seems pretty hard to do. Internally the CRuby thread needs to be in a &lt;code&gt;to_kill&lt;/code&gt; state. I would love it if someone knows a way to demonstrate it, though! It would perhaps require the assistance of a core CRuby wizard 🧙‍♀️.&lt;/li&gt;
&lt;li&gt;“sleep” is not specific to the &lt;code&gt;sleep&lt;/code&gt; method. In thread &lt;code&gt;d&lt;/code&gt; we are making an &lt;code&gt;IO.select&lt;/code&gt; call that takes three seconds. So the thread blocks while waiting, hence it is “sleep”ing.&lt;/li&gt;
&lt;li&gt;Since you can’t run Ruby code in multiple threads in parallel, you &lt;em&gt;pretty&lt;/em&gt; much would only ever see “run” on the current, active thread.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It would be nice if the information was a bit more readable. We can put together a little helper to make the output clearer. We’ll also add in one more internal status not directly exposed by &lt;code&gt;status&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ThreadStatus&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Data&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;define(
  &lt;span style=&#34;color:#e6db74&#34;&gt;:status&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:error&lt;/span&gt;
)
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;thread_status&lt;/span&gt;(thread)
  error &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
  status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status
    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NilClass&lt;/span&gt;
      error &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;begin&lt;/span&gt;
        thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
      &lt;span style=&#34;color:#66d9ef&#34;&gt;rescue&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; e
        e
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed w/ error: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;error&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FalseClass&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;finished&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;run&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;running&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;when&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;
      parse_thread_sleep_status(thread)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;status
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ThreadStatus&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;status&lt;/span&gt;:, &lt;span style=&#34;color:#e6db74&#34;&gt;error&lt;/span&gt;:)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parse_thread_sleep_status&lt;/span&gt;(thread)
  status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_s
  status&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;status&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;index(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;..-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;sub(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sleep&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sleeping&amp;#34;&lt;/span&gt;
  )
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# our previous thread code... then...&lt;/span&gt;
puts thread_status(a)
puts thread_status(b)
puts thread_status(c)
puts thread_status(d)
puts thread_status(&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current)
	
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: bye bye&amp;#34;, error=#&amp;lt;RuntimeError: bye bye&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;sleeping_forever&amp;#34;, error=nil&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;finished&amp;#34;, error=nil&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;sleeping&amp;#34;, error=nil&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;running&amp;#34;, error=nil&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using our new helper, we get a bit more readability and depth into our thread statuses.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;join&lt;/code&gt; we are able to return more information about the failed thread - “failed” instead of nil, and the actual error it failed with&lt;/li&gt;
&lt;li&gt;We return “finished” instead of false for a successful finish&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sleep_forever&lt;/code&gt; let’s us differentiate actively blocked threads (like one doing &lt;code&gt;IO.select&lt;/code&gt;) from a thread that is actually stopped, and won’t run again without intervention. We’ll talk more about &lt;code&gt;Thread.stop&lt;/code&gt; in the next section&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to &lt;code&gt;status&lt;/code&gt;, we can also use &lt;code&gt;alive?&lt;/code&gt; and &lt;code&gt;stop?&lt;/code&gt; to check on a threads status:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new {}
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; {} }
t3 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop }
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;quick&amp;#34;&lt;/span&gt;
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;slow&amp;#34;&lt;/span&gt;
t3&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on ice&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;t, t2, t3&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;thread&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# make sure it gets a chance to run&lt;/span&gt;
  thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;01&lt;/span&gt;)
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;: &amp;#34;&lt;/span&gt; \
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alive? &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;alive?&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;, &amp;#34;&lt;/span&gt; \
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stopped? &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop?&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# quick:  alive? false, stopped? true&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# slow:   alive? true,  stopped? false&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# on ice: alive? true,  stopped? true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;thread-scheduling&#34;&gt;Thread Scheduling&lt;/h3&gt;
&lt;p&gt;There are several methods for either taking direct action, or suggesting action to the thread scheduler. Before using them, keep in mind that &lt;strong&gt;you’re probably not smarter than the Ruby thread scheduler&lt;/strong&gt;. It tries to do what makes the most sense for the runtime, and it’s been tuned extensively. But these tools exist, and they get used, so let’s discuss them a bit.&lt;/p&gt;
&lt;h4 id=&#34;thread-schedule-methods&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-pass&#34;&gt;&lt;code&gt;Thread.pass&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-wakeup&#34;&gt;&lt;code&gt;wakeup&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-stop&#34;&gt;&lt;code&gt;Thread.stop&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-run&#34;&gt;&lt;code&gt;run&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-priority&#34;&gt;&lt;code&gt;priority&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pass&lt;/code&gt; and &lt;code&gt;wakeup&lt;/code&gt; are kind of like nudges to the runtime. They request a particular action, but the scheduler does not have to honor them. &lt;code&gt;Thread.pass&lt;/code&gt; tells the thread scheduler it can “pass” control to another thread:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pass
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hi!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bye!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# Most of the time you will see:&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# but it&amp;#39;s not guaranteed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;wakeup&lt;/code&gt; marks a thread as eligible for scheduling. It’s up to the thread scheduler whether that happens:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hi!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wakeup
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Thread.stop&lt;/code&gt; and &lt;code&gt;run&lt;/code&gt; are more direct commands to the thread scheduler:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Seconds slept: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# Seconds slept: 1.000076481&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Only one second has passed, but &lt;code&gt;run&lt;/code&gt; caused the &lt;code&gt;sleep&lt;/code&gt; to finish early.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;done!&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
puts thread_status(t)
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alive? &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;alive?&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stopped? &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop?&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;sleeping_forever&amp;#34;&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# alive? true&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# stopped? true&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# done!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;priority&lt;/code&gt; gives a hint to the scheduler of which thread should be given more runtime. The thread docs have a good example of this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;count1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; count2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; { count1 &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
a&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;priority &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; { count2 &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
b&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;priority &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
puts count1 &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; 21472634&lt;/span&gt;
puts count2 &lt;span style=&#34;color:#75715e&#34;&gt;#=&amp;gt; 14256235&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The threads run forever, but thread &lt;code&gt;a&lt;/code&gt; gets higher priority so it adds to the counter more often.&lt;/p&gt;
&lt;p&gt;For an open source example of &lt;code&gt;run&lt;/code&gt;, you can check the &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb&#34;&gt;&lt;code&gt;rack-timeout&lt;/code&gt; scheduler&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;schedule&lt;/span&gt;(event)
  @mx_events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { 
    @events &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; event 
  }
  runner&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run  &lt;span style=&#34;color:#75715e&#34;&gt;# wakes up the runner thread so it can recalculate sleep length taking this new event into consideration&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; event
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Thread.pass&lt;/code&gt; is a recommendation from Mike Perham if you have your &lt;a href=&#34;https://github.com/sidekiq/sidekiq/discussions/5039&#34;&gt;jobs hogging CPU&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ExpensiveJob&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;include&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Sidekiq&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Job&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;perform&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# expensive stuff&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pass &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; occasional_condition
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;thread-shutdown&#34;&gt;Thread Shutdown&lt;/h3&gt;
&lt;h4 id=&#34;thread-raise-kill&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-raise&#34;&gt;&lt;code&gt;raise&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-kill&#34;&gt;&lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ab73de82d9.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;You want to kill… me? 🥺&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;TL;DR&lt;/strong&gt; You shouldn’t use these methods unless you &lt;em&gt;really&lt;/em&gt; know what you’re doing. Instead, &lt;a href=&#34;#interrupt-safely&#34;&gt;interrupt your thread safely&lt;/a&gt;. Incidentally, you should also &lt;a href=&#34;#dont-use-timeout&#34;&gt;avoid the timeout module&lt;/a&gt;. We’ll dig deep into &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt; in the next post on “Interrupting Threads”.&lt;/p&gt;
&lt;p&gt;&lt;strong id=&#34;interrupt-safely&#34;&gt;Interrupt your thread safely&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of killing your thread, set it up to be interruptible. Most mature, threaded frameworks operate this way.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;still_kickin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Concurrent&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;AtomicBoolean&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; still_kickin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;true?
    &lt;span style=&#34;color:#75715e&#34;&gt;# more work!&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
still_kickin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;make_false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong id=&#34;dont-use-timeout&#34;&gt;Don&amp;rsquo;t use &lt;code&gt;timeout&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you see this in code, be concerned:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Timeout&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;timeout(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# 😱 &lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For some reason, the &lt;a href=&#34;https://github.com/ruby/timeout&#34;&gt;&lt;code&gt;timeout&lt;/code&gt;&lt;/a&gt; gem itself doesn’t warn about any issues. But &lt;a href=&#34;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&#34;&gt;Mike Perham summarizes it best&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/727697a3-b461-4705-a1f1-8d0ed257857e.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There’s nothing that exactly matches what timeout offers: a blanket way of timing out &lt;em&gt;any&lt;/em&gt; operation after the specified time limit. But instead of using the &lt;code&gt;timeout&lt;/code&gt; gem, there is a repository called &lt;a href=&#34;https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts&#34;&gt;The Ultimate Guide to Ruby Timeouts&lt;/a&gt;. It shows you how to set timeouts safely for basically &lt;em&gt;every&lt;/em&gt; blocking operation you could care about timing out. For instance, how to properly handle timeouts using the &lt;a href=&#34;https://github.com/redis/redis-rb&#34;&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/a&gt; gem:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Redis&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(
  &lt;span style=&#34;color:#e6db74&#34;&gt;connect_timeout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, 
  &lt;span style=&#34;color:#e6db74&#34;&gt;timeout&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The one piece mentioned in that repository you should leave alone: &lt;code&gt;Net::HTTP&lt;/code&gt; &lt;code&gt;open_timeout&lt;/code&gt;. Behind the scenes it uses the &lt;code&gt;timeout&lt;/code&gt; module 🙅‍♂️. Leave the 60 second default, it should almost never impact you, and you’re probably worse off lowering it.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;h4 id=&#34;thread-handle-interrupt&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-handle_interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A thread can be externally “interrupted” by a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Thread#kill&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Thread#raise&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Your program being exited&lt;/li&gt;
&lt;li&gt;A signal, like Ctrl+C&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;handle_interrupt&lt;/code&gt; gives you the ability to control how your program reacts to 1-3&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Because it primarily matters in the context of &lt;code&gt;raise&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;, we’ll discuss it in the next post on “Interrupting Threads”.&lt;/p&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;h4 id=&#34;process-fork&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/process#method-c-_fork&#34;&gt;&lt;code&gt;Process._fork&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Process#_fork&lt;/code&gt; isn’t a thread api, but it’s good be aware of for your threaded code.&lt;/p&gt;
&lt;p&gt;What’s happens to a thread when a process forks?&lt;/p&gt;
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-fork.drawio.png&#34; width=&#34;519&#34; height=&#34;220&#34; alt=&#34;&#34;&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep }
fork &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;inside fork: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;thread_status(t)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;outside fork: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;thread_status(t)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# outside fork: #&amp;lt;ThreadStatus status=&amp;#34;running&amp;#34;&amp;gt;,  pid: 362&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# inside fork:  #&amp;lt;ThreadStatus status=&amp;#34;finished&amp;#34;&amp;gt;, pid: 367&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can’t bring your threads with you when you fork. But you can recreate them, using &lt;code&gt;_fork&lt;/code&gt;!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;module&lt;/span&gt; OnFork
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_fork&lt;/span&gt;
    pid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pid &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# your code to restart threads&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    pid
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;singleton_class&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;prepend(&lt;span style=&#34;color:#66d9ef&#34;&gt;OnFork&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It’s a little strange of a setup. You’re hooking into the inheritance chain for &lt;code&gt;Process._fork&lt;/code&gt;, so you need to call &lt;code&gt;super&lt;/code&gt; directly. No one calls &lt;code&gt;_fork&lt;/code&gt; directly, &lt;code&gt;super&lt;/code&gt; ultimately returns the result of &lt;code&gt;fork&lt;/code&gt; itself. If the result is &lt;code&gt;0&lt;/code&gt;, we’re in the forked process, which means we can perform any kind of post-fork action. In the case of managing threads, that would involve recreating them.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;connection_pool&lt;/code&gt; gem uses this to run an &lt;code&gt;after_fork&lt;/code&gt; method. It uses it to close out connections.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;module&lt;/span&gt; ForkTracker
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_fork&lt;/span&gt;
    pid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;super&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; pid &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;ConnectionPool&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;after_fork
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    pid
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;singleton_class&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;prepend(
  &lt;span style=&#34;color:#66d9ef&#34;&gt;ForkTracker&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href=&#34;https://github.com/redis-rb/redis-client/blob/master/lib/redis_client.rb&#34;&gt;&lt;code&gt;redis-client&lt;/code&gt; gem&lt;/a&gt; uses &lt;code&gt;_fork&lt;/code&gt; to track the &lt;code&gt;pid&lt;/code&gt; in &lt;code&gt;PIDCache&lt;/code&gt;, so it can determine whether it needs to close the inherited socket (threads are not inherited when forking, but file descriptors are).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ensure_connected&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;retryable&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)
  close &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;config&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;inherit_socket &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; @pid &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PIDCache&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;thread-coordination&#34;&gt;Coordinating Threads&lt;/h3&gt;
&lt;p&gt;Now that we know the different methods of interacting with a thread directly, how can we coordinate threads together safely?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 If you can avoid it, don’t coordinate at all! Immutable structures, or isolated work are your friends.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;mutex&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/mutex&#34;&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Mutex&lt;/code&gt; is the core thread coordination primitive in Ruby. It stands for &lt;strong&gt;mut&lt;/strong&gt;ual &lt;strong&gt;ex&lt;/strong&gt;clusion, and it allows you to control single thread access to a particular resource. A thread or fiber acquires a lock on the mutex, and it is the only thing that can unlock that mutex.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 If you know database locks, a &lt;code&gt;Mutex&lt;/code&gt; basically operates like an exclusive lock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 You’re better off not sharing objects, it keeps things simpler. I’ll reference my own note from “Your Ruby programs are always multi-threaded: Part 2”:&lt;/p&gt;
&lt;p&gt;My personal metric is that the right amount of mutexes in my code is zero. If I am using a mutex, I think hard to figure out a way to avoid it because it means I’m opening up myself and future devs to a lot of cognitive overhead: you need to think critically anytime you make a change relating to mutex code.&lt;/p&gt;
&lt;p&gt;If you’re a library or framework author they may be unavoidable at some point to do interesting or useful things. In my own application code, I can pretty much &lt;em&gt;always&lt;/em&gt; avoid them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Earlier we used a class from &lt;code&gt;concurrent-ruby&lt;/code&gt; called &lt;code&gt;AtomicBoolean&lt;/code&gt; to implement an interruptible thread. What if we wanted to implement it ourselves?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AtomicBoolean&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(default &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
    @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; default
    @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;true?&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { value &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;false?&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { value &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;make_true&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; } 
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;make_false&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To make sure our data stays consistent, we &lt;code&gt;synchronize&lt;/code&gt; every access. That way we know we can’t corrupt anything and we have a consistent view of ``@value```:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;still_kickin &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;AtomicBoolean&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; still_kickin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;true?
    &lt;span style=&#34;color:#75715e&#34;&gt;# more work!&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
still_kickin&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;make_false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It doesn’t make sure we do things in any expected &lt;em&gt;order&lt;/em&gt;, but it’s a foolproof way of having proper access and visibility.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 Truthfully, CRuby doesn’t need this kind of corruption guarantee. But true parallel Ruby runtimes like JRuby and TruffleRuby do.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;synchronize&lt;/code&gt; is the probably the only method you’ll find yourself using on a &lt;code&gt;Mutex&lt;/code&gt;. Look in different projects and that’s 95% of all &lt;code&gt;Mutex&lt;/code&gt; usage. But there are more methods available you may see on occasion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unlock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try_lock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;owned?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;locked?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sleep&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;lock&lt;/code&gt; and &lt;code&gt;unlock&lt;/code&gt; can be used to recreate what &lt;code&gt;synchronize&lt;/code&gt; does:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;synchronize&lt;/span&gt;(mutex)
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lock
  &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unlock
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
synchronize(mutex) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;# locked work&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-mutex.drawio.png&#34; width=&#34;435&#34; height=&#34;105&#34; alt=&#34;&#34;&gt;
&lt;p&gt;&lt;code&gt;try_lock&lt;/code&gt; lets you attempt a lock without blocking. When you call &lt;code&gt;lock&lt;/code&gt; or &lt;code&gt;synchronize&lt;/code&gt;, your code will block until you are able to acquire a lock:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lock
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; {} &lt;span style=&#34;color:#75715e&#34;&gt;# runs forever, never releasing&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unlock
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#75715e&#34;&gt;# do some work&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join &lt;span style=&#34;color:#75715e&#34;&gt;# t never releases the lock, so t2 runs forever&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But &lt;code&gt;try_lock&lt;/code&gt; will just return a boolean if the lock worked:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lock; &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; {} }
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;try_lock
    &lt;span style=&#34;color:#75715e&#34;&gt;# do some work&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Couldn&amp;#39;t acquire the lock!&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;unlock &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;owned?
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join &lt;span style=&#34;color:#75715e&#34;&gt;# tries to acquire the lock, and raises an error because it can&amp;#39;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice the &lt;code&gt;owned?&lt;/code&gt; call in the &lt;code&gt;ensure&lt;/code&gt;? We don’t know if the lock was successfully acquired, so we only call &lt;code&gt;unlock&lt;/code&gt; if the lock is &lt;code&gt;owned?&lt;/code&gt;. Being &lt;code&gt;owned?&lt;/code&gt; means the current thread successfully acquired the lock, and is the current “owner”. If you tried to call &lt;code&gt;unlock&lt;/code&gt; on a thread that wasn’t the owner, an error would be raised.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;locked?&lt;/code&gt; allows you to check if the lock is owned by &lt;em&gt;some&lt;/em&gt; thread. Seems susceptible to some race conditions, but you could use it to determine if you need to perform an action. The &lt;a href=&#34;https://github.com/aws/aws-sdk-ruby/blob/8a53163418da7273eb990740b78174c2480b5eef/gems/aws-sdk-core/lib/aws-sdk-core/refreshing_credentials.rb#L73&#34;&gt;&lt;code&gt;aws-sdk-core&lt;/code&gt;&lt;/a&gt; uses it to determine whether to create a thread for an “async refresh”. If the thread has already started and is refreshing, &lt;code&gt;locked?&lt;/code&gt; will be &lt;code&gt;true&lt;/code&gt; and no thread will be created:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;unless&lt;/span&gt; @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;locked?
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# refresh async&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Last we have &lt;code&gt;sleep(timeout = nil)&lt;/code&gt;, which releases the lock for &lt;code&gt;timeout&lt;/code&gt; seconds, or runs forever if given &lt;code&gt;nil&lt;/code&gt;. This is exactly what the &lt;a href=&#34;https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb#L90&#34;&gt;&lt;code&gt;rack-timeout&lt;/code&gt; gem uses&lt;/a&gt; internally to create a &lt;code&gt;Scheduler&lt;/code&gt; class which it uses to schedule request timeouts:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;
  @mx_events &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
  &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;run_loop!&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# begin event reader loop&lt;/span&gt;
    @mx_events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize {
      @events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;reject!(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:cancelled?&lt;/span&gt;)
      sleep_for &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;@events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;http&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;//e&lt;/span&gt;vents&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map)(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:monotime&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;min
      @mx_events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;sleep sleep_for
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It acquires a lock using &lt;code&gt;synchronize&lt;/code&gt; to safely operate on the &lt;code&gt;@events&lt;/code&gt; array. It then finds the event with the shortest wait time, and &lt;code&gt;sleep&lt;/code&gt;s the mutex for that period. That way other events can be added to the &lt;code&gt;@events&lt;/code&gt; array using the appropriate lock, even while waiting. This supports the scheduler interface:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rack-timeout&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Rack&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Timeout&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run_in(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I did a thing last!&amp;#34;&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run_in(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;whoop whoop, i&amp;#39;m second&amp;#34;&lt;/span&gt; }
&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run_in(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yowza, i&amp;#39;m first&amp;#34;&lt;/span&gt; }
&lt;span style=&#34;color:#75715e&#34;&gt;# yowza, i&amp;#39;m first&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# whoop whoop, i&amp;#39;m second&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# I did a thing last!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you call &lt;code&gt;run_in&lt;/code&gt;, a new event is appended to the &lt;code&gt;@events&lt;/code&gt; array. &lt;code&gt;run&lt;/code&gt; is called on the thread with the &lt;code&gt;sleep&lt;/code&gt;ing mutex , which causes &lt;code&gt;@mx_events.sleep&lt;/code&gt; to wakeup and &lt;code&gt;run_loop!&lt;/code&gt; to iterate again, checking for any events to fire and scheduling the shortest even duration to wait again using &lt;code&gt;@mx_events.sleep&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;schedule&lt;/span&gt;(event)
  @mx_events&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @events &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; event }
  runner&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run  &lt;span style=&#34;color:#75715e&#34;&gt;# wakes up the runner thread so it can recalculate sleep length taking this new event into consideration&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; event
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Similar to the principle of a database lock, the shorter you can keep the mutex lock the better for performance.&lt;/p&gt;
&lt;h4 id=&#34;condition-variable&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/conditionvariable&#34;&gt;&lt;code&gt;ConditionVariable&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Similar to &lt;code&gt;Mutex#sleep&lt;/code&gt;, a &lt;code&gt;ConditionVariable&lt;/code&gt;s purpose is to let you release a lock and sleep - you do that using the &lt;code&gt;wait&lt;/code&gt; method. The difference is that it provides a direct communication mechanism to wake up: &lt;code&gt;signal&lt;/code&gt; and &lt;code&gt;broadcast&lt;/code&gt;. Let’s look at a small example - in it we won’t see the &lt;code&gt;wait&lt;/code&gt; re-acquire the lock until we call &lt;code&gt;signal&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hi!&amp;#34;&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bye!&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;how are you?&amp;#34;&lt;/span&gt;
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;still waiting?&amp;#34;&lt;/span&gt;
condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# how are you?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;signal&lt;/code&gt; will only notify a single thread, whereas &lt;code&gt;broadcast&lt;/code&gt; will notify all threads. Let’s create two threads and try it &lt;code&gt;signal&lt;/code&gt; instead:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;waiter&lt;/span&gt;(mutex, condition)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hi!&amp;#34;&lt;/span&gt;
      condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bye!&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; waiter(mutex, condition)
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; waiter(mutex, condition)
	
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;how are you?&amp;#34;&lt;/span&gt;
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;still waiting?&amp;#34;&lt;/span&gt;
	
condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# how are you?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We never see the second thread say “bye!”, because only a single &lt;code&gt;signal&lt;/code&gt; call has been made. A &lt;code&gt;signal&lt;/code&gt; call attempts to wake up a single thread. If you try to join the second thread, Ruby will detect a deadlock condition because the &lt;code&gt;wait&lt;/code&gt; will never finish:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# how are you?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:in `join&amp;#39;: No live threads left. Deadlock? (fatal)&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 2 threads, 2 sleeps current:0x0000000001df10f0 main thread:0x0000000001df10f0&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# * #&amp;lt;Thread:0x00007f9373bba9c8 sleep_forever&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#    rb_thread_t:0x0000000001df10f0 native:0x00007f938d474300 int:0   &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# * #&amp;lt;Thread:0x00007f9371c124b8 main.rb:112 sleep_forever&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#    rb_thread_t:0x00000000026ac400 native:0x00007f9371ace6c0 int:0&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#     depended by: tb_thread_id:0x0000000001df10f0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are probably cases where &lt;code&gt;signal&lt;/code&gt; makes sense - only one thread can acquire the lock so it’s cheaper to wake up a single thread than to wake up every thread. But &lt;code&gt;broadcast&lt;/code&gt; covers more scenarios, and fixes our two thread example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;broadcast
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
t2&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# hi!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# how are you?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# still waiting?&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# bye!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A &lt;code&gt;ConditionVariable&lt;/code&gt; can only &lt;code&gt;wait&lt;/code&gt; on a locked mutex:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: Attempt to lock a mutex which is unlocked&amp;#34;, error=#&amp;lt;ThreadError:...&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And only for the thread that owns the mutex:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
condition &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    condition&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(mutex)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
puts thread_status(t)
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;ThreadStatus status=&amp;#34;failed w/ error: Attempt to unlock a mutex which is locked by another thread/fiber&amp;#34;, error=#&amp;lt;ThreadError:...&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can use &lt;code&gt;ConditionVariable#wait&lt;/code&gt; with a timeout in seconds, so we can also recreate the &lt;code&gt;Mutex#sleep&lt;/code&gt; &lt;code&gt;Scheduler&lt;/code&gt; code from &lt;code&gt;rack-timeout&lt;/code&gt; (in a more basic form):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Scheduler&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Schedule&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Data&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;define(&lt;span style=&#34;color:#e6db74&#34;&gt;:block&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;:time&lt;/span&gt;)
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;
    @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
    @cond &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
    @schedules &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;
    start
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;start&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
        @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
          schedule &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @schedules&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;min_by { &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;s&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; s&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time }
          &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; schedule
            now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now
            sleep_duration &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; schedule&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; now
	
            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; sleep_duration &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
              @cond&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(@mutex, sleep_duration)
            &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
            &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; schedule&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time
              schedule&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;block&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call
              @schedules&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;delete(schedule)
            &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
          &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
            @cond&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(@mutex)
          &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;schedule&lt;/span&gt;(seconds, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;block)
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      target_time &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; seconds
      @schedules &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Schedule&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#e6db74&#34;&gt;block&lt;/span&gt;:, &lt;span style=&#34;color:#e6db74&#34;&gt;time&lt;/span&gt;: target_time)
      @cond&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;signal
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
puts &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now
s&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;schedule(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1! &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; }
s&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;schedule(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;2! &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; }
s&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;schedule(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;) { puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3! &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; }
sleep
&lt;span style=&#34;color:#75715e&#34;&gt;# 2024-08-26 21:58:23 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 1! 2024-08-26 21:58:24 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 3! 2024-08-26 21:58:26 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# 5! 2024-08-26 21:58:28 +0000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: part 2&lt;/a&gt;, we looked at an example of coordinating threads using a “CountdownLatch”.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;CountdownLatch&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(count)
    @count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; count
    @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
    @cond &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ConditionVariable&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      @cond&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait(@mutex) &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; @count &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @count }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count_down&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      @count &lt;span style=&#34;color:#f92672&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; @count &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
        @cond&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;broadcast
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
      @count
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;📝 Quick reminder that &lt;code&gt;concurrent-ruby&lt;/code&gt; comes with a countdown latch so there’s no need to use this one ☝🏼. It’s just educational. It is very similar to the concurrent-ruby version though!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’ve seen how &lt;code&gt;wait&lt;/code&gt; and &lt;code&gt;broadcast&lt;/code&gt; work. But why do we need that &lt;code&gt;while @count &amp;gt; 0&lt;/code&gt; check? Shouldn’t it &lt;em&gt;only&lt;/em&gt; get woken up when &lt;code&gt;@count == 0&lt;/code&gt; and &lt;code&gt;@cond.broadcast&lt;/code&gt; is called? Unfortunately, &lt;code&gt;Mutex#sleep&lt;/code&gt; and &lt;code&gt;ConditionVariable#wait&lt;/code&gt; can wake up randomly due to something called &lt;a href=&#34;https://en.wikipedia.org/wiki/Spurious_wakeup&#34;&gt;&lt;strong&gt;Spurious wakeups&lt;/strong&gt;&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Thread runtimes can decide for internal reasons to wake up a condition at random, so you should be ready to handle it - in our case we continually check the expected condition &lt;code&gt;@count &amp;gt; 0&lt;/code&gt; and continue to &lt;code&gt;wait&lt;/code&gt; until it is false. This makes sure if we wake up due to a spurious wakeup we’ll immediately &lt;code&gt;wait&lt;/code&gt; for the condition again.&lt;/p&gt;
&lt;h4 id=&#34;monitor&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/monitor&#34;&gt;&lt;code&gt;Monitor&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A &lt;code&gt;Monitor&lt;/code&gt; is &lt;em&gt;essentially&lt;/em&gt; the same as a &lt;code&gt;Mutex&lt;/code&gt;, but it is also “re-entrant”. What does it mean to be re-entrant? Let’s go back to an example from &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are multi-threaded: part 2&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;monitor&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Result&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;attr_accessor&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:value&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Fibonacci&lt;/span&gt;
  @fib_monitor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Monitor&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; self
    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;(value)
      @fib_monitor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; value }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;
      @fib_monitor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @result }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;calculate&lt;/span&gt;(n)
      @fib_monitor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
        self&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Result&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
        result&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fib(n)
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fib&lt;/span&gt;(n)
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; n &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
	
      fib(n &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; fib(n &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In it, when we call &lt;code&gt;#calculate&lt;/code&gt;, internally it calls &lt;code&gt;#result&lt;/code&gt; and &lt;code&gt;#result=&lt;/code&gt;. &lt;code&gt;calculate&lt;/code&gt; first acquires the lock using &lt;code&gt;synchronize&lt;/code&gt;, then it calls &lt;code&gt;result=&lt;/code&gt; which &lt;em&gt;also&lt;/em&gt; tries to acquire the lock. It is &lt;em&gt;re-entering&lt;/em&gt; the same lock. Let’s change &lt;code&gt;@fib_monitor&lt;/code&gt; to a &lt;code&gt;Mutex&lt;/code&gt; and see what happens:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Fibonacci&lt;/span&gt;
  @fib_monitor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;Fibonacci&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;calculate(&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;)
&lt;span style=&#34;color:#75715e&#34;&gt;# `synchronize&amp;#39;: deadlock; recursive locking (ThreadError)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-mutex-re-entrant.drawio.png&#34; width=&#34;435&#34; height=&#34;151&#34; alt=&#34;&#34;&gt;
&lt;p&gt;We immediately see an error raised: “deadlock; recursive locking”. By changing to a &lt;code&gt;Monitor&lt;/code&gt;, everything works fine.&lt;/p&gt;
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-monitor.drawio.png&#34; width=&#34;435&#34; height=&#34;180&#34; alt=&#34;&#34;&gt;
&lt;p&gt;The &lt;code&gt;redis-rb &lt;/code&gt; gem creates clients that are thread-safe. It uses a &lt;code&gt;Monitor&lt;/code&gt; to do that, likely because it allows &lt;code&gt;synchronize&lt;/code&gt;d methods to call other &lt;code&gt;synchronize&lt;/code&gt;d methods without any recursive deadlocking errors:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Redis&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(options &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {})
    @monitor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Monitor&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
    &lt;span style=&#34;color:#75715e&#34;&gt;# ...&lt;/span&gt;
    inherit_socket &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @options&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;delete(&lt;span style=&#34;color:#e6db74&#34;&gt;:inherit_socket&lt;/span&gt;)
	
    @client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; initialize_client(@options)
    @client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;inherit_socket! &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; inherit_socket
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;synchronize&lt;/span&gt;
    @monitor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt;(@client) }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;send_command&lt;/span&gt;(command, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;block)
    @monitor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      @client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call_v(command, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;block)
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;rescue&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RedisClient&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Error&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; error
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Client&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;translate_error!(error)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#75715e&#34;&gt;# lib/redis/commands/transactions.rb&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;multi&lt;/span&gt;
    synchronize &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;client&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
      client&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;multi &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;raw_transaction&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;yield&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MultiConnection&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(raw_transaction)
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Monitor&lt;/code&gt; comes with some other conveniences around creating &lt;code&gt;ConditionVariable&lt;/code&gt;s related to it, which you can read more about in its documentation.&lt;/p&gt;
&lt;h4 id=&#34;queue&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/queue&#34;&gt;&lt;code&gt;Queue&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Queue&lt;/code&gt; is one of two thread-safe data structures that come out of the box with Ruby. It is a First-in, First-out queue which allows for safe communication between threads. It’s primarily used for implementing producer/consumer patterns between threads:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Queue&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
producer &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    queue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; i
    i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
    sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create_consumer&lt;/span&gt;(name, queue)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      item &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; queue&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pop
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;name&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; got another item &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;item&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; at &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
create_consumer(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Consumer 1&amp;#34;&lt;/span&gt;, queue)
create_consumer(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Consumer 2&amp;#34;&lt;/span&gt;, queue)
	
producer&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 1 got another item 0 at 2024-08-24 20:58:46 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 2 got another item 1 at 2024-08-24 20:58:47 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 1 got another item 2 at 2024-08-24 20:58:48 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 2 got another item 3 at 2024-08-24 20:58:49 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 1 got another item 4 at 2024-08-24 20:58:50 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 2 got another item 5 at 2024-08-24 20:58:51 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 1 got another item 6 at 2024-08-24 20:58:52 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 2 got another item 7 at 2024-08-24 20:58:53 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 1 got another item 8 at 2024-08-24 20:58:54 +0000&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumer 2 got another item 9 at 2024-08-24 20:58:55 +0000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;producer&lt;/code&gt; thread endlessly adds an item to the queue every 1 second, and two &lt;code&gt;consumer&lt;/code&gt; threads &lt;code&gt;pop&lt;/code&gt; items off the queue. If there is no item available, the thread sleeps until one becomes available.&lt;/p&gt;
&lt;p&gt;If it seems like you could pretty easily implement this using a &lt;code&gt;Mutex&lt;/code&gt; and a &lt;code&gt;ConditionVariable&lt;/code&gt;, you’d be right! I’ll leave that for you to try as an example.&lt;/p&gt;
&lt;h4 id=&#34;sized-queue&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/sizedqueue&#34;&gt;&lt;code&gt;SizedQueue&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;SizedQueue&lt;/code&gt; is the other thread-safe data structure available in Ruby, and it’s just another flavor of the base &lt;code&gt;Queue&lt;/code&gt; class. It allows you to create a fixed-size queue - when new items are added the queue blocks until space is available.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;fixed_queue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SizedQueue&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;)
fixed_queue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
fixed_queue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
fixed_queue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
fixed_queue &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# raises &amp;#34;No live threads left. Deadlock?&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a good use-case for throttling your own code to not overwhelm your application. The following code will block if more than 5 items exist in the queue, throttling the producers so consumers can keep up with it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;throttle &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SizedQueue&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Producers&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; i&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;even?
      throttle &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Producer 1: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;i&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pass
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    i &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; i&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;odd?
      throttle &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Producer 2: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;i&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pass
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Consumers&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread 1: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;throttle&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pop&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
    sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread 2: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;throttle&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pop&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
    sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Threads waiting: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;throttle&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;num_waiting&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
  sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 0&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 1: Producer 1: 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 2: Producer 2: 1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 1: Producer 1: 4&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 2: Producer 2: 3&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 1: Producer 1: 6&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 1: Producer 2: 5&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 1&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Threads waiting: 2&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 2: Producer 1: 8&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread 1: Producer 2: 7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You’ll see threads waiting going between 1 and 2, as producers get blocked while consumers slowly consume data from the &lt;code&gt;SizedQueue&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&#34;thread-group&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/threadgroup&#34;&gt;&lt;code&gt;ThreadGroup&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The Ruby docs describe &lt;code&gt;ThreadGroup&lt;/code&gt; as “a means of keeping track of a number of threads as a group.” A thread is automatically a part of the “default” group:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; {} }
t&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;group &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ThreadGroup&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Default&lt;/span&gt; is &lt;span style=&#34;color:#75715e&#34;&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And you can add threads to a new group:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#66d9ef&#34;&gt;loop&lt;/span&gt; {} }
t2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep }
group &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ThreadGroup&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
group&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add(t)
group&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;add(t2)
puts group&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;list
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:1 run&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#&amp;lt;Thread:0x0...main.rb:2 sleep&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I’m mentioning them here for completeness, but you almost &lt;em&gt;never&lt;/em&gt; see them in real use. See &lt;a href=&#34;https://rubyapi.org/3.3/o/threadgroup&#34;&gt;their documentation&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;p&gt;——&lt;/p&gt;
&lt;p&gt;That’s about it! Out of the box, Ruby comes with a pretty small set of Thread primitives. Most code you might see in real use will use either these, or options from the &lt;code&gt;concurrent-ruby&lt;/code&gt; gem. We’ll dig more into concurrent Ruby in “Abstracted, concurrent Ruby” later on in the series.&lt;/p&gt;
&lt;h3 id=&#34;memory-visibility&#34;&gt;Memory visibility&lt;/h3&gt;
&lt;p&gt;The last thing we’ll discuss before finishing up is a concept called “memory visibility”.&lt;/p&gt;
&lt;p&gt;The simplest way to think of memory visibility is “what can each thread can see at any given time”. When threads are running on multiple CPUs, a common optimization is to localize things to the most performant memory caches located on the CPU itself. If that happens, it means two different threads can operate on a shared piece of data, and have completely different views of that data because they have localized, out of sync versions of it.&lt;/p&gt;
&lt;p&gt;In addition, the CPU can actually reorder certain operations to optimize them.&lt;/p&gt;
&lt;p&gt;How can you solve these problems? When you need to make sure each thread sees a consistent, accurate version of a shared piece of data, you utilize something called a memory barrier. How can you use a memory barrier from Ruby? A mutex! As long as you wrap each access to a particular shared resource, you’ll be guaranteed to see a consistent view of it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;AtomicBoolean&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(default &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)
    @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; default
    @mutex &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Mutex&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;true?&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { value &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;false?&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { value &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;make_true&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt; } 
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;make_false&lt;/span&gt;
    @mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize { @value &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; }
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Isolated to a single thread, or for immutable objects, memory visibility doesn’t really matter - it’s another reason attempting to share objects between threads can lead to issues, and avoiding it is better than trying to safely coordinate it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 In CRuby, memory visibility is &lt;em&gt;unlikely&lt;/em&gt; to ever be an issue for you. That’s because there is always a mutex involved when moving between threads: The GVL. We’ll talk more about the GVL in “Thread and its MaNy friends”. But to be on the cautious side, you’re better off synchronizing access consistently for reads/write to any data shared between threads.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;———&lt;/p&gt;
&lt;p&gt;We’ve now dug into the majority of the Thread API. The main piece we’ve only touched lightly, is around interrupting threads. It’s up next in “Interrupting Threads: Colorless, concurrent Ruby”. More soon! 👋🏼&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There are a couple other interfacing for creating a thread, but you basically never see them in use.&lt;/p&gt;
&lt;p&gt;a = 1&lt;/p&gt;
&lt;p&gt;b = 2&lt;/p&gt;
&lt;p&gt;Thread.new(a, b) { |a, b| puts a, b }&lt;/p&gt;
&lt;p&gt;👆 this one has been described in some places as making copies to keep things thread safe - but that’s incorrect - it just passes the reference so it doesn’t provide much value&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You can handle signals in a couple ways that we’ll discuss later&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Spurious: not being what it purports to be; false or fake&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/group-74.jpg)



&gt; 👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt; - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - The Thread API
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my!](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “The Thread API: Concurrent, colorless Ruby”. I’ll update the links as each part is released, and include these links in each post.


- [The Thread API](#the-thread-api)
	- [Don’t let the cute mascot fool you](#the-cute-mascot)
	- [A thread api primer (with examples!)](#thread-api-primer)
		- [Waiting for a thread to complete](#waiting-to-complete)
			- [`join`](#thread-join)
			- [`value`](#thread-value)
			- [`join(timeout_in_seconds)`](#thread-join-timeout)
		- [Error reporting](#thread-error-reporting)
			- [`report_on_exception` and `abort_on_exception`](#thread-exceptions)
		- [Tracking threads](#thread-tracking)
			- [`Thread.list`](#thread-list)
			- [`name`](#thread-name)
			- [`status`, `alive?` and `stop?`](#thread-statuses)
		- [Thread scheduling](#thread-scheduling)
			- [`Thread.pass`, `wakeup`, `Thread.stop`, `run`, and `priority`](#thread-schedule-methods)
		- [Thread shutdown](#thread-shutdown)
			- [`raise` and `kill`](#thread-raise-kill)
			- [`Thread.handle_interrupt`](#thread-handle-interrupt)
			- [`Process._fork`](#process-fork)
	- [Coordinating threads](#thread-coordination)
		- [`Mutex`](#mutex)
		- [`ConditionVariable`](#condition-variable)
		- [`Monitor`](#monitor)
		- [`Queue`](#queue)
		- [`SizedQueue`](#sized-queue)
		- [`ThreadGroup`](#thread-group)
	- [Memory visibility](#memory-visibility)


&lt;h2 id=&#34;the-thread-api&#34;&gt;The Thread API 🧵&lt;/h2&gt;

We&#39;re going to break down threads into three parts:

- The Thread API - All the tools available to you in the Ruby runtime to manage threads, and how they work
- Interrupting Threads - How threads get stuck, and how to shut them down safely
- Thread and its MaNy friends - Thread architecture and the GVL

This post covers the Thread API. We&#39;ll go over every method available to you, why they matter, how to call them, and often how popular open source projects use them.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ab73de82d9.png&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;


&lt;h3 id=&#34;the-cute-mascot&#34;&gt;Don’t let the cute mascot fool you&lt;/h3&gt;


Before we start digging into threads, I’d like to make a small disclaimer: **writing safe, deterministic, bug-free threaded code is hard**.


I think understanding threads is valuable knowledge. After all, whether you explicitly use threads or not, [your Ruby programs are always multi-threaded](https://jpcamara.com/2024/06/04/your-ruby-programs.html). If you’re always in the context of a thread, it’s helpful to know how they work behind the scenes. Even if the most you ever do is set a thread count on a server, it still helps to know how they work - it better informs what changing those numbers can and can’t do for you.


```ruby
Thread.new do
  puts &#34;seems easy enough? 🤷🏻‍♂️&#34;
end
```
Behind the simplicity of the thread interface lives a lot of complexity. An OS thread gives you the literal ability to perform tasks in _parallel_. And once things can run in parallel, our sequential thinking starts to fail us. It’s difficult to correctly read through code step by step if at any point that step by step code can swap out with another separate piece of code. No warning, no ability to determine exactly where your program will switch to next.


It’s the same when multiple people work on some tasks in parallel - you encounter communication breakdown. There can be contention over a shared resource. You can undo someone else’s work, or leave them with inconsistent information that causes them to finish their task, but incorrectly.


![](https://cdn.uploads.micro.blog/98548/2024/90bc5383-008d-42e3-8532-29cf8c6ad832.jpeg)


There’s a cognitive bias known as the [Dunning Kruger Effect](https://en.m.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect). Essentially someone with limited experience in something can overestimate their abilities, or the complexity of the task. `Thread.new {}` feels pretty simple - it’s easy to underestimate what goes into it. Diving into threads together helps us realize they wield a lot of power!


There’s a reason that most threaded code in gems like Rails, SolidQueue and GoodJob use the `concurrent-ruby` gem. Abstractions are your friend. We’ll dig into abstractions later in the series - for now we’ll learn about threads directly. But dont stop here! Learn the foundation, and when you need it yourself, learn the abstractions and tools that make it easier.


![](https://cdn.uploads.micro.blog/98548/2024/24c9850ebd.png)


&gt; **source**: Eli and JP Camara, [https://x.com/logicalcomic](https://x.com/logicalcomic)


This post will dig into all the options available in Ruby out of the box. You’ll learn each thread method and what they’re for, and we’ll discuss how to coordinate your threads once you create them. Let’s go!


&lt;h3 id=&#34;thread-api-primer&#34;&gt;A &lt;a href=&#34;https://rubyapi.org/3.3/o/thread&#34;&gt;thread&lt;/a&gt; api primer (with examples!)&lt;/h3&gt;


We&#39;ll start off with some details on how you interact with threads. Let&#39;s create a thread!


```ruby
Thread.new do
  sleep 10
  puts &#34;finished!&#34;
end
```
Run that code[^1] 👆🏼… hmmm, nothing happens. Nothing prints and the program exits silently. What happened?


Everything starts off running in the “main thread”, which is accessible by calling `Thread.main`. When a new thread is created it’s like a branch off of the main thread. Those branches exist independently and the main thread doesn’t wait for them to finish by default.


&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-without-join.drawio.png&#34; width=&#34;453&#34; height=&#34;409&#34; alt=&#34;&#34;&gt;


&lt;h3 id=&#34;waiting-to-complete&#34;&gt;Waiting for a thread to complete&lt;/h3&gt;


&lt;h4 id=&#34;thread-join&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-join&#34;&gt;&lt;code&gt;join&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


To wait on a specific thread, you `join` that thread and the current thread together. Here we have the main thread join with `t`:


```ruby
t = Thread.new do
  sleep 10
  puts &#34;finished!&#34;
end
# We&#39;re at the top level in `Thread.main`, which &#34;joins&#34; with `t` until it finishes
t.join
# finished!
```
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-with-join.drawio.png&#34; width=&#34;529&#34; height=&#34;448&#34; alt=&#34;&#34;&gt;


When the thread finishes, the thread object is returned from `join`.


———


&lt;h4 id=&#34;thread-value&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-value&#34;&gt;&lt;code&gt;value&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


You can also join on a thread and ask it for the last value returned using `value`:


```ruby
t = Thread.new do
  sleep 10
  File.read(a_file_path)
end
puts t.value # contents of `a_file_path`
```
The simplest way to run some work concurrently is to start up several threads and then iterate over each one calling `join` or `value`. We’ll make 4 http requests here, and the overall time will take as long as the longest request:


```ruby
# frozen_string_literal: true
	
require &#34;net/http&#34;
require &#34;json&#34;
	
def generate_uuid_thread
  url = &#34;https://httpbin.org/uuid&#34;
  Thread.new do
    response = Net::HTTP.get(URI(url))
    JSON.parse(response)[&#34;uuid&#34;]
  end
end
	
uuids = 4.times.map do
  generate_uuid_thread
end.map(&amp;:value)
puts uuids
# 6b76167d-9bac-45dc-8d0b-5b7af865b843
# a025a125-51a5-4773-b78d-ab93fba02eb3
# 98befb95-adf7-4c89-9fd0-d10f6b2a3d7a
# 95f486fd-5bc6-46fe-b65a-c52a87140dfb
```
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-page-4-parallel-io.drawio.png&#34; width=&#34;600&#34; height=&#34;449&#34; alt=&#34;&#34;&gt;


Make sure you let each thread start first! If you join too early, you end up running them sequentially:


```ruby
uuids = []
uuids &lt;&lt; generate_uuid_thread.value
uuids &lt;&lt; generate_uuid_thread.value
uuids &lt;&lt; generate_uuid_thread.value
uuids &lt;&lt; generate_uuid_thread.value
puts uuids
# 6b76167d-9bac-45dc-8d0b-5b7af865b843
# a025a125-51a5-4773-b78d-ab93fba02eb3
# 98befb95-adf7-4c89-9fd0-d10f6b2a3d7a
# 95f486fd-5bc6-46fe-b65a-c52a87140dfb
```
The end result looks identical, and the _output_ is but the _execution_ is totally different. When we create all the threads up front, certain operations can run in parallel:


- Threads 1 through 4 get created and are “runnable”
- The main thread blocks on the first iteration to call `map(&amp;:join)`
- As each thread blocks on the HTTP call, they run in parallel
- The loop takes as long as the longest thread, so if it takes us 50ms for 3 requests, and the 4th is 100ms, we spend around 100ms total


Calling `generate_uuid_thread.value` one at a time, we’re just running the code sequentially:


- Thread 1 gets created and run, returning its `value`
- Thread 2 gets created and run, returning its `value`
- Same for threads 3 and 4
- We’re running sequentially, so the threads provided no value. It would take roughly 250ms. If anything, the threads likely _added_ overhead.


&gt; ⚠️ Never _actually_ generate a uuid using a web service, that’s crazy 🤣


———


&lt;h4 id=&#34;thread-join-timeout&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-join&#34;&gt;&lt;code&gt;join(timeout_in_seconds)&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


You can also limit the amount of time you `join`. If you’re calling `join`, typically you just want to wait however long it takes. But using `join(seconds)` could be useful to periodically pop into some other work or alert that you’ve been running too long. `join(seconds)` will return the thread while it is still running, and return `nil` once it finishes. 


```ruby
t = Thread.new do
  sleep 10
end
t2 = Thread.new do
  sleep 20
end
	
while t2.alive?
  puts &#34;wait a bit more... #{t2.join(5)}&#34;
end
puts &#34;done!&#34;
# wait a bit more... 
# wait a bit more... 
# wait a bit more... 
# wait a bit more... #&lt;Thread:0x0... main.rb:116 dead&gt;
# done!
	
def fib(n)
  return n if n &lt;= 1
  fib(n - 1) + fib(n - 2)
end
	
t2 = Thread.new do
  fib(40)
end
while t2.alive?
  puts &#34;wait a bit more... #{t2.join(0.01)}&#34;
end
puts &#34;done! #{t2.value}&#34; # get returned even after a join
# wait a bit more... 
# wait a bit more... 
# wait a bit more... 
# wait a bit more... 
# wait a bit more... 
# done! 9227465
```
You could use it to intermittently check for a thread finishing without wasting too many CPU cycles:


```ruby
t = Thread.new do
  sleep 5
end
while t.join(1)
  puts &#34;still waiting...&#34;
end
puts &#34;done!&#34;
# still waiting...
# still waiting...
# still waiting...
# still waiting...
# done!
```
In the [Puma web server](https://github.com/puma/puma), it uses `join(seconds)` to manage [shutting down its thread pool](https://github.com/puma/puma/blob/master/lib/puma/thread_pool.rb). It iterates over each thread, adjusting the `join` timeout based on how much time has elapsed since starting the method:


```ruby
join = -&gt;(inner_timeout) do
  start = Process.clock_gettime(
    Process::CLOCK_MONOTONIC
  )
  threads.reject! do |t|
    elapsed = Process.clock_gettime(
      Process::CLOCK_MONOTONIC
    ) - start
    t.join inner_timeout - elapsed
  end
end
	
# Wait +timeout+ seconds for threads to finish.
join.call(timeout)
```
It uses `join(timeout)` to try and let each thread finish before forcing a shutdown with `raise` or `kill`. The `reject!` removes any threads that finish during that time - `nil` returned from `join` means the thread is still running, otherwise the thread object is returned (removing it from the array).


&gt; 📝 A “thread pool” is a reusable, typically fixed size set of threads. Rather than create brand new threads every time they are needed, a thread pool saves on thread creation by reusing threads to perform operations. They are generally used to save on thread creation cost and limit the number of threads running at a time. `concurrent-ruby` comes with several types of thread pools.


&lt;h3 id=&#34;thread-error-reporting&#34;&gt;Error Reporting&lt;/h3&gt;


&lt;h4 id=&#34;thread-exceptions&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-report_on_exception&#34;&gt;&lt;code&gt;report_on_exception&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-abort_on_exception&#34;&gt;&lt;code&gt;abort_on_exception&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


What happens if something fails in your thread?


```ruby
t = Thread.new do
  raise &#34;oops!&#34;
end
sleep # does the program sleep forever, or raise an error?
```
Running that 👆🏼, it just runs forever. Even though our thread has failed, it never impacts the program. We will see an error printed in our console, however:


```
terminated with exception (report_on_exception is true):
main.rb:2:in `block in &lt;main&gt;`: oops! (RuntimeError)
```
You can control whether it fails silently, but the default is to report the error. Keep that default - silent errors are not your friend. But if you _really_ needed to, you can change it globally or with a per thread setting:


```ruby
# No threads log their errors ☠️ 
Thread.report_on_exception = false
	
# This individual thread does not log
t.report_on_exception = true
```
What if you want to report it in the current thread? Similar to `join`ing execution with a thread to wait for it to finish, you need to join with it to raise its error:


```ruby
t = Thread.new do
  raise &#34;oops!&#34;
end
t.join # or .value, raises &#34;oops!&#34;
```
You can also force the thread to raise, even when running independently:


```ruby
t = Thread.new do
  sleep 5
  raise &#34;listen to me!&#34;
end
t.abort_on_exception = true
sleep 10 # t blows up the program!
```
This can be set globally as well:


```ruby
Thread.abort_on_exception = true
t = Thread.new do
  sleep 5
  raise &#34;listen to me!&#34;
end
sleep 10 # t blows up the program!
```
Rack timeout uses it in [its Scheduler thread](https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb), applying it using `Thread.current`:


```ruby
def run_loop!
  Thread.current.abort_on_exception = true
```
In general, you don’t see this much in a production setting. But it could be useful if you’re writing a one-off script and you want any thread that fails to kill the program.


&lt;h3 id=&#34;thread-tracking&#34;&gt;Tracking threads&lt;/h3&gt;

Can we find out what threads are running?

&lt;h4 id=&#34;thread-list&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-list&#34;&gt;&lt;code&gt;Thread.list&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

`Thread.list` will give you every thread that hasn&#39;t already finished:

```ruby
Thread.new { sleep 0.1 }
Thread.new { sleep 0.1 }
Thread.new { sleep 0.1 }
Thread.new {}
puts Thread.list
	
#&lt;Thread:0x0... run&gt;
#&lt;Thread:0x0... main.rb:1 sleep&gt;
#&lt;Thread:0x0... main.rb:2 sleep&gt;
#&lt;Thread:0x0... main.rb:3 sleep&gt;
#&lt;Thread:0x0... main.rb:4 dead&gt;
```
&lt;h4 id=&#34;thread-name&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-name&#34;&gt;&lt;code&gt;name&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


Ok… but how can you differentiate them? There’s a few ways you can achieve that:


1. File information
2. Setting a name
3. Inheritance


File information gets printed by default, and shows you the name of the file and what line the thread was started on:


```ruby
t = Thread.new { sleep 0.1 }
t2 = Thread.new { sleep 0.1 }
t3 = Thread.new { sleep 0.1 }
t4 = Thread.new { sleep 0.1 }
puts Thread.list
	
#&lt;Thread:0x0...main.rb:1 sleep&gt;
#&lt;Thread:0x0...main.rb:2 sleep&gt;
#&lt;Thread:0x0...main.rb:3 sleep&gt;
#&lt;Thread:0x0...main.rb:4 sleep&gt;
```
The file info is useful, but often your threads are all started from the same place, so it doesn’t tell you much:


```ruby
def start_thread
  Thread.new { sleep 0.1 }
end
t = start_thread
t2 = start_thread
t3 = start_thread
t4 = start_thread
puts Thread.list
	
#&lt;Thread:0x0...main.rb:2 sleep&gt;
#&lt;Thread:0x0...main.rb:2 sleep&gt;
#&lt;Thread:0x0...main.rb:2 sleep&gt;
#&lt;Thread:0x0...main.rb:2 sleep&gt;
```
In that case, you can set a `name` on each thread to differentiate them:


```ruby
Thread.main.name = &#34;main&#34;
t.name = &#34;first!&#34;
t2.name = &#34;second!&#34;
t3.name = &#34;third!&#34;
t4.name = &#34;fourth!&#34;
puts Thread.list
	
#&lt;Thread:0x0...@first!  main.rb:2 sleep&gt;
#&lt;Thread:0x0...@second! main.rb:2 sleep&gt;
#&lt;Thread:0x0...@third!  main.rb:2 sleep&gt;
#&lt;Thread:0x0...@fourth! main.rb:2 sleep&gt;
```
The [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) gem uses this to help differentiate threads created in its [ThreadPoolExecutor](https://github.com/ruby-concurrency/concurrent-ruby/blob/master/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb):


```ruby
# concurrent/executor/ruby_thread_pool_executor.rb
[@thread.name](http://thread.name) = [pool.name, &#39;worker&#39;, id].compact.join(&#39;-&#39;)
#...
	
require &#34;concurrent&#34;
	
pool = Concurrent::ThreadPoolExecutor.new(
  name: &#34;🤖&#34;
)
pool.post { sleep 0.1 }
pool.post { sleep 0.1 }
pool.post { sleep 0.1 }
pool.post { sleep 0.1 }
puts Thread.list
#&lt;Thread:0x0...@🤖-worker-1...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&gt;
#&lt;Thread:0x0...@🤖-worker-2...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&gt;
#&lt;Thread:0x0...@🤖-worker-3...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&gt;
#&lt;Thread:0x0...@🤖-worker-4...concurrent/executor/ruby_thread_pool_executor.rb:339 sleep_forever&gt;
```
The [Honeybadger gem](https://github.com/honeybadger-io/honeybadger-ruby) runs a [background thread](https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/worker.rb) when sending errors to their error reporting service. To differentiate their thread, they use inheritance. The information about each thread includes the class so it makes it easy to identify:


```ruby
module Honeybadger
  class Worker
    class Thread &lt; ::Thread; end
  end
end
	
Honeybadger::Worker::Thread.new { sleep 0.1 }
puts Thread.list
#&lt;Thread:0x0... run&gt;
#&lt;Honeybadger::Worker::Thread:0x0... main.rb:119 sleep&gt;
```
———


&lt;h4 id=&#34;thread-statuses&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-status&#34;&gt;&lt;code&gt;status&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-alive-3F&#34;&gt;&lt;code&gt;alive?&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-stop-3F&#34;&gt;&lt;code&gt;stop?&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


Can you keep track of the status of a running thread? Yep! Threads operate in one of 5 states - they’re not the _most_ intuitive, but they’re what you’ve got:


- `“run”` - the thread is running
- `“sleep”` - the thread is “sleeping”. There is some blocking operation going on or the thread went to sleep or was put to sleep by the thread scheduler 
- `“aborting”` - the thread has failed but hasn’t finished running yet 
- `nil` - an error was raised and the thread is dead
- `false` - the thread finished normally


Let’s demonstrate some statuses:


```ruby
a = Thread.new { raise(&#34;bye bye&#34;) }
b = Thread.new { Thread.stop }
c = Thread.new {}
d = Thread.new { 
  IO.select(nil, nil, nil, 3)
}
d.join(1) # wait on d for 1 second
puts a.status.class          #=&gt; NilClass
puts b.status                #=&gt; &#34;sleep&#34;
puts c.status                #=&gt; false
puts d.status                #=&gt; &#34;sleep&#34;
puts Thread.current.status   #=&gt; &#34;run&#34;
```
You’ll notice a few things about the above:


1. I didn’t show “aborting”. That’s because I’m not sure regular code could ever see that status. Catching a failed thread that hasn’t finished yet seems pretty hard to do. Internally the CRuby thread needs to be in a `to_kill` state. I would love it if someone knows a way to demonstrate it, though! It would perhaps require the assistance of a core CRuby wizard 🧙‍♀️.
2. “sleep” is not specific to the `sleep` method. In thread `d` we are making an `IO.select` call that takes three seconds. So the thread blocks while waiting, hence it is “sleep”ing.
3. Since you can’t run Ruby code in multiple threads in parallel, you _pretty_ much would only ever see “run” on the current, active thread.


It would be nice if the information was a bit more readable. We can put together a little helper to make the output clearer. We’ll also add in one more internal status not directly exposed by `status`:


```ruby
ThreadStatus = Data.define(
  :status, :error
)
	
def thread_status(thread)
  error = nil
  status = case thread.status
    when NilClass
      error = begin
        thread.join
      rescue =&gt; e
        e
      end
      &#34;failed w/ error: #{error}&#34;
    when FalseClass then &#34;finished&#34;
    when &#34;run&#34; then &#34;running&#34;
    when &#34;sleep&#34;
      parse_thread_sleep_status(thread)
  else thread.status
  end
  ThreadStatus.new(status:, error:)
end
	
def parse_thread_sleep_status(thread)
  status = thread.to_s
  status[status.index(&#34;sleep&#34;)..-2].sub(
    &#34;sleep&#34;, &#34;sleeping&#34;
  )
end
	
# our previous thread code... then...
puts thread_status(a)
puts thread_status(b)
puts thread_status(c)
puts thread_status(d)
puts thread_status(Thread.current)
	
#&lt;ThreadStatus status=&#34;failed w/ error: bye bye&#34;, error=#&lt;RuntimeError: bye bye&gt;
#&lt;ThreadStatus status=&#34;sleeping_forever&#34;, error=nil&gt;
#&lt;ThreadStatus status=&#34;finished&#34;, error=nil&gt;
#&lt;ThreadStatus status=&#34;sleeping&#34;, error=nil&gt;
#&lt;ThreadStatus status=&#34;running&#34;, error=nil&gt;
```
Using our new helper, we get a bit more readability and depth into our thread statuses. 


- Using `join` we are able to return more information about the failed thread - “failed” instead of nil, and the actual error it failed with
- We return “finished” instead of false for a successful finish
- `sleep_forever` let’s us differentiate actively blocked threads (like one doing `IO.select`) from a thread that is actually stopped, and won’t run again without intervention. We’ll talk more about `Thread.stop` in the next section


In addition to `status`, we can also use `alive?` and `stop?` to check on a threads status:


```ruby
t = Thread.new {}
t2 = Thread.new { loop {} }
t3 = Thread.new { Thread.stop }
t.name = &#34;quick&#34;
t2.name = &#34;slow&#34;
t3.name = &#34;on ice&#34;
[t, t2, t3].each do |thread|
  # make sure it gets a chance to run
  thread.join(0.01)
  puts &#34;#{thread.name}: &#34; \
    &#34;alive? #{thread.alive?}, &#34; \
    &#34;stopped? #{thread.stop?}&#34;
end
# quick:  alive? false, stopped? true
# slow:   alive? true,  stopped? false
# on ice: alive? true,  stopped? true
```
&lt;h3 id=&#34;thread-scheduling&#34;&gt;Thread Scheduling&lt;/h3&gt;

There are several methods for either taking direct action, or suggesting action to the thread scheduler. Before using them, keep in mind that &lt;strong&gt;you’re probably not smarter than the Ruby thread scheduler&lt;/strong&gt;. It tries to do what makes the most sense for the runtime, and it’s been tuned extensively. But these tools exist, and they get used, so let’s discuss them a bit.

&lt;h4 id=&#34;thread-schedule-methods&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-pass&#34;&gt;&lt;code&gt;Thread.pass&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-wakeup&#34;&gt;&lt;code&gt;wakeup&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-stop&#34;&gt;&lt;code&gt;Thread.stop&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-run&#34;&gt;&lt;code&gt;run&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-priority&#34;&gt;&lt;code&gt;priority&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;

`pass` and `wakeup` are kind of like nudges to the runtime. They request a particular action, but the scheduler does not have to honor them. `Thread.pass` tells the thread scheduler it can “pass” control to another thread:


```ruby
t = Thread.new do
  Thread.pass
  puts &#34;hi!&#34;
end
t2 = Thread.new do
  puts &#34;bye!&#34;
end
t.join
t2.join
# Most of the time you will see:
# bye!
# hi!
# but it&#39;s not guaranteed
```
`wakeup` marks a thread as eligible for scheduling. It’s up to the thread scheduler whether that happens:


```ruby
t = Thread.new do
  Thread.stop
  puts &#34;hi!&#34;
end
t.join(1)
t.wakeup
t.join
# hi!
```
`Thread.stop` and `run` are more direct commands to the thread scheduler:


```ruby
t = Thread.new do
  now = Time.now
  sleep 10
  puts &#34;Seconds slept: #{Time.now - now}&#34;
end
t.join(1)
t.run
t.join
# Seconds slept: 1.000076481
```
Only one second has passed, but `run` caused the `sleep` to finish early.


```ruby
t = Thread.new do
  Thread.stop
  puts &#34;done!&#34;
end
t.join(0.1)
puts thread_status(t)
puts &#34;alive? #{t.alive?}&#34;
puts &#34;stopped? #{t.stop?}&#34;
t.run
t.join
#&lt;ThreadStatus status=&#34;sleeping_forever&#34;&gt;
# alive? true
# stopped? true
# done!
```
`priority` gives a hint to the scheduler of which thread should be given more runtime. The thread docs have a good example of this:


```ruby
count1 = count2 = 0
a = Thread.new do
      loop { count1 += 1 }
    end
a.priority = -1
	
b = Thread.new do
      loop { count2 += 1 }
    end
b.priority = -2
sleep 1
puts count1 #=&gt; 21472634
puts count2 #=&gt; 14256235
```
The threads run forever, but thread `a` gets higher priority so it adds to the counter more often.


For an open source example of `run`, you can check the [`rack-timeout` scheduler](https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb):


```ruby
def schedule(event)
  @mx_events.synchronize { 
    @events &lt;&lt; event 
  }
  runner.run  # wakes up the runner thread so it can recalculate sleep length taking this new event into consideration
  return event
end
```
`Thread.pass` is a recommendation from Mike Perham if you have your [jobs hogging CPU](https://github.com/sidekiq/sidekiq/discussions/5039):


```ruby
class ExpensiveJob
  include Sidekiq::Job
	
  def perform
    loop do
      # expensive stuff
      Thread.pass if occasional_condition
    end
  end
end
```
&lt;h3 id=&#34;thread-shutdown&#34;&gt;Thread Shutdown&lt;/h3&gt;


&lt;h4 id=&#34;thread-raise-kill&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-raise&#34;&gt;&lt;code&gt;raise&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-i-kill&#34;&gt;&lt;code&gt;kill&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ab73de82d9.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;


&gt; You want to kill… me? 🥺


⚠️ **TL;DR** You shouldn’t use these methods unless you _really_ know what you’re doing. Instead, [interrupt your thread safely](#interrupt-safely). Incidentally, you should also [avoid the timeout module](#dont-use-timeout). We’ll dig deep into `raise` and `kill` in the next post on “Interrupting Threads”.


&lt;strong id=&#34;interrupt-safely&#34;&gt;Interrupt your thread safely&lt;/strong&gt;


Instead of killing your thread, set it up to be interruptible. Most mature, threaded frameworks operate this way.


```ruby
still_kickin = Concurrent::AtomicBoolean.new(true)
Thread.new do
  while still_kickin.true?
    # more work!
  end
end
	
still_kickin.make_false
```
&lt;strong id=&#34;dont-use-timeout&#34;&gt;Don&#39;t use &lt;code&gt;timeout&lt;/code&gt;&lt;/strong&gt;


If you see this in code, be concerned:


```ruby
require &#34;timeout&#34;
	
Timeout.timeout(1) do
  # 😱 
end
```
For some reason, the [`timeout`](https://github.com/ruby/timeout) gem itself doesn’t warn about any issues. But [Mike Perham summarizes it best](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/):


![](https://cdn.uploads.micro.blog/98548/2024/727697a3-b461-4705-a1f1-8d0ed257857e.jpeg)


There’s nothing that exactly matches what timeout offers: a blanket way of timing out _any_ operation after the specified time limit. But instead of using the `timeout` gem, there is a repository called [The Ultimate Guide to Ruby Timeouts](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts). It shows you how to set timeouts safely for basically _every_ blocking operation you could care about timing out. For instance, how to properly handle timeouts using the [`redis`](https://github.com/redis/redis-rb) gem:


```ruby
Redis.new(
  connect_timeout: 1, 
  timeout: 1,
  #...
)
```
The one piece mentioned in that repository you should leave alone: `Net::HTTP` `open_timeout`. Behind the scenes it uses the `timeout` module 🙅‍♂️. Leave the 60 second default, it should almost never impact you, and you’re probably worse off lowering it.


———


&lt;h4 id=&#34;thread-handle-interrupt&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread#method-c-handle_interrupt&#34;&gt;&lt;code&gt;Thread.handle_interrupt&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


A thread can be externally “interrupted” by a few things:


1. `Thread#kill`
2. `Thread#raise`
3. Your program being exited
4. A signal, like Ctrl+C


`handle_interrupt` gives you the ability to control how your program reacts to 1-3[^2].


Because it primarily matters in the context of `raise` and `kill`, we’ll discuss it in the next post on “Interrupting Threads”.


———


&lt;h4 id=&#34;process-fork&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/process#method-c-_fork&#34;&gt;&lt;code&gt;Process._fork&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


`Process#_fork` isn’t a thread api, but it’s good be aware of for your threaded code.


What’s happens to a thread when a process forks?


&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-fork.drawio.png&#34; width=&#34;519&#34; height=&#34;220&#34; alt=&#34;&#34;&gt;


```ruby
t = Thread.new { sleep }
fork do
  puts &#34;inside fork: #{thread_status(t)}&#34;
end
puts &#34;outside fork: #{thread_status(t)}&#34;
# outside fork: #&lt;ThreadStatus status=&#34;running&#34;&gt;,  pid: 362
# inside fork:  #&lt;ThreadStatus status=&#34;finished&#34;&gt;, pid: 367
```
You can’t bring your threads with you when you fork. But you can recreate them, using `_fork`!


```ruby
module OnFork
  def _fork
    pid = super
    if pid == 0
      # your code to restart threads
    end
    pid
  end
end
	
Process.singleton_class.prepend(OnFork)
```
It’s a little strange of a setup. You’re hooking into the inheritance chain for `Process._fork`, so you need to call `super` directly. No one calls `_fork` directly, `super` ultimately returns the result of `fork` itself. If the result is `0`, we’re in the forked process, which means we can perform any kind of post-fork action. In the case of managing threads, that would involve recreating them.


The `connection_pool` gem uses this to run an `after_fork` method. It uses it to close out connections.


```ruby
module ForkTracker
  def _fork
    pid = super
    if pid == 0
      ConnectionPool.after_fork
    end
    pid
  end
end
	
Process.singleton_class.prepend(
  ForkTracker
)
```
The [`redis-client` gem](https://github.com/redis-rb/redis-client/blob/master/lib/redis_client.rb) uses `_fork` to track the `pid` in `PIDCache`, so it can determine whether it needs to close the inherited socket (threads are not inherited when forking, but file descriptors are).


```ruby
def ensure_connected(retryable: true)
  close if !config.inherit_socket &amp;&amp; @pid != PIDCache.pid
```
&lt;h3 id=&#34;thread-coordination&#34;&gt;Coordinating Threads&lt;/h3&gt;


Now that we know the different methods of interacting with a thread directly, how can we coordinate threads together safely?


&gt; 📝 If you can avoid it, don’t coordinate at all! Immutable structures, or isolated work are your friends.


&lt;h4 id=&#34;mutex&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/mutex&#34;&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


`Mutex` is the core thread coordination primitive in Ruby. It stands for **mut**ual **ex**clusion, and it allows you to control single thread access to a particular resource. A thread or fiber acquires a lock on the mutex, and it is the only thing that can unlock that mutex.


&gt; 📝 If you know database locks, a `Mutex` basically operates like an exclusive lock.


&gt; 📝 You’re better off not sharing objects, it keeps things simpler. I’ll reference my own note from “Your Ruby programs are always multi-threaded: Part 2”:
&gt; 
&gt; My personal metric is that the right amount of mutexes in my code is zero. If I am using a mutex, I think hard to figure out a way to avoid it because it means I’m opening up myself and future devs to a lot of cognitive overhead: you need to think critically anytime you make a change relating to mutex code.
&gt; 
&gt; If you’re a library or framework author they may be unavoidable at some point to do interesting or useful things. In my own application code, I can pretty much _always_ avoid them.


Earlier we used a class from `concurrent-ruby` called `AtomicBoolean` to implement an interruptible thread. What if we wanted to implement it ourselves?


```ruby
class AtomicBoolean
  def initialize(default = false)
    @value = default
    @mutex = Mutex.new
  end
	
  def true?
    @mutex.synchronize { value == true }
  end
	
  def false?
    @mutex.synchronize { value == false }
  end
	
  def make_true
    @mutex.synchronize { @value = true } 
  end
	
  def make_false
    @mutex.synchronize { @value = false }
  end
end
```
To make sure our data stays consistent, we `synchronize` every access. That way we know we can’t corrupt anything and we have a consistent view of ``@value```:


```ruby
still_kickin = AtomicBoolean.new(true)
Thread.new do
  while still_kickin.true?
    # more work!
  end
end
	
still_kickin.make_false
```
It doesn’t make sure we do things in any expected _order_, but it’s a foolproof way of having proper access and visibility.


&gt; 📝 Truthfully, CRuby doesn’t need this kind of corruption guarantee. But true parallel Ruby runtimes like JRuby and TruffleRuby do.


`synchronize` is the probably the only method you’ll find yourself using on a `Mutex`. Look in different projects and that’s 95% of all `Mutex` usage. But there are more methods available you may see on occasion:


- `lock`
- `unlock`
- `try_lock`
- `owned?`
 - `locked?`
 - `sleep`


`lock` and `unlock` can be used to recreate what `synchronize` does:


```ruby
def synchronize(mutex)
  mutex.lock
  yield
ensure
  mutex.unlock
end
	
synchronize(mutex) do
  # locked work
end
```
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-mutex.drawio.png&#34; width=&#34;435&#34; height=&#34;105&#34; alt=&#34;&#34;&gt;


`try_lock` lets you attempt a lock without blocking. When you call `lock` or `synchronize`, your code will block until you are able to acquire a lock: 


```ruby
mutex = Mutex.new
t = Thread.new do
  mutex.lock
  loop {} # runs forever, never releasing
ensure
  mutex.unlock
end
t2 = Thread.new do
  mutex.synchronize do
    # do some work
  end
end
t.join(1)
t2.join # t never releases the lock, so t2 runs forever
```
But `try_lock` will just return a boolean if the lock worked:


```ruby
mutex = Mutex.new
t = Thread.new { mutex.lock; loop {} }
t2 = Thread.new do
  if mutex.try_lock
    # do some work
  else
    raise &#34;Couldn&#39;t acquire the lock!&#34;
  end
ensure
  mutex.unlock if mutex.owned?
end
t.join(1)
t2.join # tries to acquire the lock, and raises an error because it can&#39;t
```
Notice the `owned?` call in the `ensure`? We don’t know if the lock was successfully acquired, so we only call `unlock` if the lock is `owned?`. Being `owned?` means the current thread successfully acquired the lock, and is the current “owner”. If you tried to call `unlock` on a thread that wasn’t the owner, an error would be raised.


`locked?` allows you to check if the lock is owned by _some_ thread. Seems susceptible to some race conditions, but you could use it to determine if you need to perform an action. The [`aws-sdk-core`](https://github.com/aws/aws-sdk-ruby/blob/8a53163418da7273eb990740b78174c2480b5eef/gems/aws-sdk-core/lib/aws-sdk-core/refreshing_credentials.rb#L73) uses it to determine whether to create a thread for an “async refresh”. If the thread has already started and is refreshing, `locked?` will be `true` and no thread will be created:


```ruby
unless @mutex.locked?
  Thread.new do
    @mutex.synchronize do
      # refresh async
    end
  end
end
```
Last we have `sleep(timeout = nil)`, which releases the lock for `timeout` seconds, or runs forever if given `nil`. This is exactly what the [`rack-timeout` gem uses](https://github.com/zombocom/rack-timeout/blob/main/lib/rack/timeout/support/scheduler.rb#L90) internally to create a `Scheduler` class which it uses to schedule request timeouts:


```ruby
def initialize
  @mx_events = Mutex.new
  # ...
end
	
def run_loop!
  loop do # begin event reader loop
    @mx_events.synchronize {
      @events.reject!(&amp;:cancelled?)
      sleep_for = [@events.map](http://events.map)(&amp;:monotime).min
      @mx_events.sleep sleep_for
```
It acquires a lock using `synchronize` to safely operate on the `@events` array. It then finds the event with the shortest wait time, and `sleep`s the mutex for that period. That way other events can be added to the `@events` array using the appropriate lock, even while waiting. This supports the scheduler interface:


```ruby
require &#34;rack-timeout&#34;
	
Scheduler = Rack::Timeout::Scheduler
Scheduler.run_in(5) { puts &#34;I did a thing last!&#34; }
Scheduler.run_in(3) { puts &#34;whoop whoop, i&#39;m second&#34; }
Scheduler.run_in(1) { puts &#34;yowza, i&#39;m first&#34; }
# yowza, i&#39;m first
# whoop whoop, i&#39;m second
# I did a thing last!
```
When you call `run_in`, a new event is appended to the `@events` array. `run` is called on the thread with the `sleep`ing mutex , which causes `@mx_events.sleep` to wakeup and `run_loop!` to iterate again, checking for any events to fire and scheduling the shortest even duration to wait again using `@mx_events.sleep`.


```ruby
def schedule(event)
  @mx_events.synchronize { @events &lt;&lt; event }
  runner.run  # wakes up the runner thread so it can recalculate sleep length taking this new event into consideration
  return event
end
```
Similar to the principle of a database lock, the shorter you can keep the mutex lock the better for performance.


&lt;h4 id=&#34;condition-variable&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/conditionvariable&#34;&gt;&lt;code&gt;ConditionVariable&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


Similar to `Mutex#sleep`, a `ConditionVariable`s purpose is to let you release a lock and sleep - you do that using the `wait` method. The difference is that it provides a direct communication mechanism to wake up: `signal` and `broadcast`. Let’s look at a small example - in it we won’t see the `wait` re-acquire the lock until we call `signal`:


```ruby
mutex = Mutex.new
condition = ConditionVariable.new
t = Thread.new do
  mutex.synchronize do
    puts &#34;hi!&#34;
    condition.wait(mutex)
    puts &#34;bye!&#34;
  end
end
t.join(5)
puts &#34;how are you?&#34;
t.join(1)
puts &#34;still waiting?&#34;
condition.signal
t.join
# hi!
# how are you?
# still waiting?
# bye!
```
`signal` will only notify a single thread, whereas `broadcast` will notify all threads. Let’s create two threads and try it `signal` instead:

```ruby
def waiter(mutex, condition)
  Thread.new do
    mutex.synchronize do
      puts &#34;hi!&#34;
      condition.wait(mutex)
      puts &#34;bye!&#34;
    end
  end
end
	
mutex = Mutex.new
condition = ConditionVariable.new
t = waiter(mutex, condition)
t2 = waiter(mutex, condition)
	
t.join(5)
puts &#34;how are you?&#34;
t2.join(1)
puts &#34;still waiting?&#34;
	
condition.signal
sleep 1
# hi!
# hi!
# how are you?
# still waiting?
# bye!
```
We never see the second thread say “bye!”, because only a single `signal` call has been made. A `signal` call attempts to wake up a single thread. If you try to join the second thread, Ruby will detect a deadlock condition because the `wait` will never finish:

```ruby
condition.signal
t.join
t2.join
# hi!
# hi!
# how are you?
# still waiting?
# bye!
# main.rb:in `join&#39;: No live threads left. Deadlock? (fatal)
# 2 threads, 2 sleeps current:0x0000000001df10f0 main thread:0x0000000001df10f0
# * #&lt;Thread:0x00007f9373bba9c8 sleep_forever&gt;
#    rb_thread_t:0x0000000001df10f0 native:0x00007f938d474300 int:0   
# * #&lt;Thread:0x00007f9371c124b8 main.rb:112 sleep_forever&gt;
#    rb_thread_t:0x00000000026ac400 native:0x00007f9371ace6c0 int:0
#     depended by: tb_thread_id:0x0000000001df10f0
```
There are probably cases where `signal` makes sense - only one thread can acquire the lock so it’s cheaper to wake up a single thread than to wake up every thread. But `broadcast` covers more scenarios, and fixes our two thread example:

```ruby
condition.broadcast
t.join
t2.join
# hi!
# hi!
# how are you?
# still waiting?
# bye!
# bye!
```
A `ConditionVariable` can only `wait` on a locked mutex:

```ruby
mutex = Mutex.new
condition = ConditionVariable.new
t = Thread.new do
  condition.wait(mutex)
end
sleep 1
puts thread_status(t)
#&lt;ThreadStatus status=&#34;failed w/ error: Attempt to lock a mutex which is unlocked&#34;, error=#&lt;ThreadError:...&gt;
```
And only for the thread that owns the mutex:

```ruby
mutex = Mutex.new
condition = ConditionVariable.new
t = nil
mutex.synchronize do
  t = Thread.new do
    condition.wait(mutex)
  end
  sleep 1
end
puts thread_status(t)
#&lt;ThreadStatus status=&#34;failed w/ error: Attempt to unlock a mutex which is locked by another thread/fiber&#34;, error=#&lt;ThreadError:...&gt;
```
We can use `ConditionVariable#wait` with a timeout in seconds, so we can also recreate the `Mutex#sleep` `Scheduler` code from `rack-timeout` (in a more basic form):


```ruby
class Scheduler
  Schedule = Data.define(:block, :time)
	
  def initialize
    @mutex = Mutex.new
    @cond = ConditionVariable.new
    @schedules = []
    start
  end
	
  def start
    Thread.new do
      loop do
        @mutex.synchronize do
          schedule = @schedules.min_by { |s| s.time }
          if schedule
            now = Time.now
            sleep_duration = schedule.time - now
	
            if sleep_duration &gt; 0
              @cond.wait(@mutex, sleep_duration)
            end
	
            if Time.now &gt;= schedule.time
              schedule.block.call
              @schedules.delete(schedule)
            end
          else
            @cond.wait(@mutex)
          end
        end
      end
    end
  end
	
  def schedule(seconds, &amp;block)
    @mutex.synchronize do
      target_time = Time.now + seconds
      @schedules &lt;&lt; Schedule.new(block:, time: target_time)
      @cond.signal
    end
  end
end
	
s = Scheduler.new
puts Time.now
s.schedule(1) { puts &#34;1! #{Time.now}&#34; }
s.schedule(3) { puts &#34;2! #{Time.now}&#34; }
s.schedule(5) { puts &#34;3! #{Time.now}&#34; }
sleep
# 2024-08-26 21:58:23 +0000
# 1! 2024-08-26 21:58:24 +0000
# 3! 2024-08-26 21:58:26 +0000
# 5! 2024-08-26 21:58:28 +0000
```
In [Your Ruby programs are always multi-threaded: part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html), we looked at an example of coordinating threads using a “CountdownLatch”.


```ruby
class CountdownLatch
  def initialize(count)
    @count = count
    @mutex = Mutex.new
    @cond = ConditionVariable.new
  end
	
  def wait
    @mutex.synchronize do
      @cond.wait(@mutex) while @count &gt; 0
    end
  end
	
  def count
    @mutex.synchronize { @count }
  end
	
  def count_down
    @mutex.synchronize do
      @count -= 1
      if @count == 0
        @cond.broadcast
      end
      @count
    end
  end
end
```
&gt; 📝 Quick reminder that `concurrent-ruby` comes with a countdown latch so there’s no need to use this one ☝🏼. It’s just educational. It is very similar to the concurrent-ruby version though!


We’ve seen how `wait` and `broadcast` work. But why do we need that `while @count &gt; 0` check? Shouldn’t it _only_ get woken up when `@count == 0` and `@cond.broadcast` is called? Unfortunately, `Mutex#sleep` and `ConditionVariable#wait` can wake up randomly due to something called [**Spurious wakeups**](https://en.wikipedia.org/wiki/Spurious_wakeup)[^3]. 


Thread runtimes can decide for internal reasons to wake up a condition at random, so you should be ready to handle it - in our case we continually check the expected condition `@count &gt; 0` and continue to `wait` until it is false. This makes sure if we wake up due to a spurious wakeup we’ll immediately `wait` for the condition again.


&lt;h4 id=&#34;monitor&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/monitor&#34;&gt;&lt;code&gt;Monitor&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


A `Monitor` is _essentially_ the same as a `Mutex`, but it is also “re-entrant”. What does it mean to be re-entrant? Let’s go back to an example from [Your Ruby programs are multi-threaded: part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html):


```ruby
require &#34;monitor&#34;
	
class Result
  attr_accessor :value
end
	
class Fibonacci
  @fib_monitor = Monitor.new
	
  class &lt;&lt; self
    def result=(value)
      @fib_monitor.synchronize { @result = value }
    end
	
    def result
      @fib_monitor.synchronize { @result }
    end
	
    def calculate(n)
      @fib_monitor.synchronize do
        self.result = Result.new
        result.value = fib(n)
      end
    end
	
    def fib(n)
      return n if n &lt;= 1
	
      fib(n - 1) + fib(n - 2)
    end
  end
end
```
In it, when we call `#calculate`, internally it calls `#result` and `#result=`. `calculate` first acquires the lock using `synchronize`, then it calls `result=` which _also_ tries to acquire the lock. It is _re-entering_ the same lock. Let’s change `@fib_monitor` to a `Mutex` and see what happens:


```ruby
class Fibonacci
  @fib_monitor = Mutex.new
end
	
Fibonacci.calculate(10)
# `synchronize&#39;: deadlock; recursive locking (ThreadError)
```
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-mutex-re-entrant.drawio.png&#34; width=&#34;435&#34; height=&#34;151&#34; alt=&#34;&#34;&gt;


We immediately see an error raised: “deadlock; recursive locking”. By changing to a `Monitor`, everything works fine.


&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-thread-api-2-monitor.drawio.png&#34; width=&#34;435&#34; height=&#34;180&#34; alt=&#34;&#34;&gt;


The `redis-rb ` gem creates clients that are thread-safe. It uses a `Monitor` to do that, likely because it allows `synchronize`d methods to call other `synchronize`d methods without any recursive deadlocking errors:


```ruby
class Redis
  def initialize(options = {})
    @monitor = Monitor.new
    # ...
    inherit_socket = @options.delete(:inherit_socket)
	
    @client = initialize_client(@options)
    @client.inherit_socket! if inherit_socket
  end
	
  def synchronize
    @monitor.synchronize { yield(@client) }
  end
	
  def send_command(command, &amp;block)
    @monitor.synchronize do
      @client.call_v(command, &amp;block)
    end
  rescue ::RedisClient::Error =&gt; error
    Client.translate_error!(error)
  end
	
  # lib/redis/commands/transactions.rb
  def multi
    synchronize do |client|
      client.multi do |raw_transaction|
        yield MultiConnection.new(raw_transaction)
      end
    end
  end
```
`Monitor` comes with some other conveniences around creating `ConditionVariable`s related to it, which you can read more about in its documentation.


&lt;h4 id=&#34;queue&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/queue&#34;&gt;&lt;code&gt;Queue&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


`Queue` is one of two thread-safe data structures that come out of the box with Ruby. It is a First-in, First-out queue which allows for safe communication between threads. It’s primarily used for implementing producer/consumer patterns between threads:


```ruby
queue = Queue.new
producer = Thread.new do
  i = 0
  loop do
    queue &lt;&lt; i
    i += 1
    sleep 1
  end
end
	
def create_consumer(name, queue)
  Thread.new do
    loop do
      item = queue.pop
      puts &#34;#{name} got another item #{item} at #{Time.now}&#34;
    end
  end
end
	
create_consumer(&#34;Consumer 1&#34;, queue)
create_consumer(&#34;Consumer 2&#34;, queue)
	
producer.join
# Consumer 1 got another item 0 at 2024-08-24 20:58:46 +0000
# Consumer 2 got another item 1 at 2024-08-24 20:58:47 +0000
# Consumer 1 got another item 2 at 2024-08-24 20:58:48 +0000
# Consumer 2 got another item 3 at 2024-08-24 20:58:49 +0000
# Consumer 1 got another item 4 at 2024-08-24 20:58:50 +0000
# Consumer 2 got another item 5 at 2024-08-24 20:58:51 +0000
# Consumer 1 got another item 6 at 2024-08-24 20:58:52 +0000
# Consumer 2 got another item 7 at 2024-08-24 20:58:53 +0000
# Consumer 1 got another item 8 at 2024-08-24 20:58:54 +0000
# Consumer 2 got another item 9 at 2024-08-24 20:58:55 +0000
```
The `producer` thread endlessly adds an item to the queue every 1 second, and two `consumer` threads `pop` items off the queue. If there is no item available, the thread sleeps until one becomes available.


If it seems like you could pretty easily implement this using a `Mutex` and a `ConditionVariable`, you’d be right! I’ll leave that for you to try as an example.


&lt;h4 id=&#34;sized-queue&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread/sizedqueue&#34;&gt;&lt;code&gt;SizedQueue&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


`SizedQueue` is the other thread-safe data structure available in Ruby, and it’s just another flavor of the base `Queue` class. It allows you to create a fixed-size queue - when new items are added the queue blocks until space is available.


```ruby
fixed_queue = SizedQueue.new(3)
fixed_queue &lt;&lt; 1
fixed_queue &lt;&lt; 2
fixed_queue &lt;&lt; 3
fixed_queue &lt;&lt; 4 # raises &#34;No live threads left. Deadlock?&#34;
```
This is a good use-case for throttling your own code to not overwhelm your application. The following code will block if more than 5 items exist in the queue, throttling the producers so consumers can keep up with it:


```ruby
throttle = SizedQueue.new(5)
	
# Producers
Thread.new do
  i = 0
  loop do
    i += 1
    if i.even?
      throttle &lt;&lt; &#34;Producer 1: #{i}&#34;
      Thread.pass
    end
  end
end
Thread.new do
  i = 0
  loop do
    i += 1
    if i.odd?
      throttle &lt;&lt; &#34;Producer 2: #{i}&#34;
      Thread.pass
    end
  end
end
	
# Consumers
Thread.new do
  loop do
    puts &#34;Thread 1: #{throttle.pop}&#34;
    sleep 1
  end
end
Thread.new do
  loop do
    puts &#34;Thread 2: #{throttle.pop}&#34;
    sleep 2
  end
end
	
loop do
  puts &#34;Threads waiting: #{throttle.num_waiting}&#34;
  sleep 0.5
end
	
# Threads waiting: 0
# Thread 1: Producer 1: 2
# Thread 2: Producer 2: 1
# Threads waiting: 2
# Thread 1: Producer 1: 4
# Threads waiting: 2
# Threads waiting: 1
# Thread 2: Producer 2: 3
# Thread 1: Producer 1: 6
# Threads waiting: 1
# Threads waiting: 2
# Thread 1: Producer 2: 5
# Threads waiting: 1
# Threads waiting: 2
# Thread 2: Producer 1: 8
# Thread 1: Producer 2: 7
```
You’ll see threads waiting going between 1 and 2, as producers get blocked while consumers slowly consume data from the `SizedQueue`.


&lt;h4 id=&#34;thread-group&#34;&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/threadgroup&#34;&gt;&lt;code&gt;ThreadGroup&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;


The Ruby docs describe `ThreadGroup` as “a means of keeping track of a number of threads as a group.” A thread is automatically a part of the “default” group:


```ruby
t = Thread.new { loop {} }
t.group == ThreadGroup::Default is # true
```
And you can add threads to a new group:


```ruby
t = Thread.new { loop {} }
t2 = Thread.new { sleep }
group = ThreadGroup.new
group.add(t)
group.add(t2)
puts group.list
#&lt;Thread:0x0...main.rb:1 run&gt;
#&lt;Thread:0x0...main.rb:2 sleep&gt;
```
I’m mentioning them here for completeness, but you almost _never_ see them in real use. See [their documentation](https://rubyapi.org/3.3/o/threadgroup) to learn more.


——


That’s about it! Out of the box, Ruby comes with a pretty small set of Thread primitives. Most code you might see in real use will use either these, or options from the `concurrent-ruby` gem. We’ll dig more into concurrent Ruby in “Abstracted, concurrent Ruby” later on in the series.


&lt;h3 id=&#34;memory-visibility&#34;&gt;Memory visibility&lt;/h3&gt;


The last thing we’ll discuss before finishing up is a concept called “memory visibility”.


The simplest way to think of memory visibility is “what can each thread can see at any given time”. When threads are running on multiple CPUs, a common optimization is to localize things to the most performant memory caches located on the CPU itself. If that happens, it means two different threads can operate on a shared piece of data, and have completely different views of that data because they have localized, out of sync versions of it.


In addition, the CPU can actually reorder certain operations to optimize them.


How can you solve these problems? When you need to make sure each thread sees a consistent, accurate version of a shared piece of data, you utilize something called a memory barrier. How can you use a memory barrier from Ruby? A mutex! As long as you wrap each access to a particular shared resource, you’ll be guaranteed to see a consistent view of it:


```ruby
class AtomicBoolean
  def initialize(default = false)
    @value = default
    @mutex = Mutex.new
  end
	
  def true?
    @mutex.synchronize { value == true }
  end
	
  def false?
    @mutex.synchronize { value == false }
  end
	
  def make_true
    @mutex.synchronize { @value = true } 
  end
	
  def make_false
    @mutex.synchronize { @value = false }
  end
end
```
Isolated to a single thread, or for immutable objects, memory visibility doesn’t really matter - it’s another reason attempting to share objects between threads can lead to issues, and avoiding it is better than trying to safely coordinate it.


&gt; 📝 In CRuby, memory visibility is _unlikely_ to ever be an issue for you. That’s because there is always a mutex involved when moving between threads: The GVL. We’ll talk more about the GVL in “Thread and its MaNy friends”. But to be on the cautious side, you’re better off synchronizing access consistently for reads/write to any data shared between threads.


———


We’ve now dug into the majority of the Thread API. The main piece we’ve only touched lightly, is around interrupting threads. It’s up next in “Interrupting Threads: Colorless, concurrent Ruby”. More soon! 👋🏼 


[^1]:	There are a couple other interfacing for creating a thread, but you basically never see them in use.


    a = 1


    b = 2


    Thread.new(a, b) { |a, b| puts a, b }


    👆 this one has been described in some places as making copies to keep things thread safe - but that’s incorrect - it just passes the reference so it doesn’t provide much value 


[^2]:	You can handle signals in a couple ways that we’ll discuss later


[^3]:	Spurious: not being what it purports to be; false or fake
</source:markdown>
    </item>
    
    <item>
      <title>Ruby methods are colorless</title>
      <link>https://jpcamara.com/2024/07/16/ruby-methods-are-colorless.html</link>
      <pubDate>Tue, 16 Jul 2024 18:25:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/07/16/ruby-methods-are-colorless.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/aa82bee7-c210-489e-9b5c-b1fa798f2d8d.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ruby methods are colorless&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “Ruby methods are colorless”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#some-context&#34;&gt;First, some context&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#function-colors&#34;&gt;Function colors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#paved-with-callbacks&#34;&gt;The road to hell is paved with callbacks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#invasive-async-await&#34;&gt;Invasive async/await&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#what-makes-ruby-colorless&#34;&gt;What makes Ruby colorless?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#nested-concurrency&#34;&gt;A nested concurrency model 🪆&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#threads-and-fibers&#34;&gt;Colorless threads 🧵 and Fibers 🌾 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#to-be-concurrent&#34;&gt;What does it mean to be concurrent?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#colorless-calls&#34;&gt;Colorless calls&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#async-aside&#34;&gt;A quick aside on Async&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#digging-deep&#34;&gt;Digging deep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ruby-history-corner&#34;&gt;PS: A historical sidenote - Ruby had its own callback phase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;A quick note on Ruby runtimes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m approaching this series from the perspective of CRuby. This is the original and most popular/common Ruby runtime. I’m also focusing primarily on Ruby 3.2/3.3+. This matters because it informs how things like Threads and Fibers behave.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;some-context&#34;&gt;First, some context&lt;/h2&gt;
&lt;p&gt;Last year I &lt;a href=&#34;https://twitter.com/ThePrimeagen/status/1689289653680033792&#34;&gt;read a tweet&lt;/a&gt; from &lt;a href=&#34;https://twitter.com/ThePrimeagen&#34;&gt;@ThePrimagen&lt;/a&gt; about his experience with &lt;a href=&#34;https://go.dev/&#34;&gt;Go&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. In it he mentioned some strengths and weaknesses (cheekily), and then he mentioned a phrase I’d never heard before: “colorless functions”.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/03867f06ca.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;real talk, colorless functions [are] amazing and I think that most people don’t realize how great it really is&lt;/p&gt;
&lt;p&gt;&amp;ndash; @ThePrimeagen&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;function-colors&#34;&gt;Function colors&lt;/h3&gt;
&lt;p&gt;Curious to understand what a “colorless function” is, a quick search returned this article as the first result: &lt;a href=&#34;https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/&#34;&gt;What color is your function?&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;In it, &lt;a href=&#34;https://twitter.com/munificentbob&#34;&gt;Bob Nystrom&lt;/a&gt; goes through a fictional language (spoiler: it’s Javascript/node.js) that breaks functions down into one of two “colors”:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Every function has a color&lt;/li&gt;
&lt;li&gt;The way you call a function depends on its color&lt;/li&gt;
&lt;li&gt;You can only call a red function from within a red function&lt;/li&gt;
&lt;li&gt;Red functions are more painful to call&lt;/li&gt;
&lt;li&gt;Some core library functions are red&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It’s an allegory&amp;hellip; red functions are asynchronous ones&lt;/p&gt;
&lt;p&gt;&amp;ndash; Nystrom&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Red and blue functions equate to asynchronous and synchronous functions. And having to indicate the color of your function infects the rest of your code and the API decisions you make.&lt;/p&gt;
&lt;h3 id=&#34;paved-with-callbacks&#34;&gt;The road to hell is paved with callbacks&lt;/h3&gt;
&lt;p&gt;Nystrom’s article was written in 2015 while Javascript/node was still in its &lt;a href=&#34;http://callbackhell.com/&#34;&gt;callback hell&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; phase. Any operation that could block, like reading a file, making an HTTP call, or querying a database had to be done using a callback. Callbacks proliferated throughout your code - so asynchronous, red functions were plentiful and painful to interact with.&lt;/p&gt;
&lt;p&gt;In JavaScript, everything happens in a single-ish threaded context called the “event loop”. You can’t block the loop - so you would set a callback to know when a blocking call completed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;readFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) =&amp;gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) { &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;; }
  &lt;span style=&#34;color:#a6e22e&#34;&gt;saveFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;file&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) =&amp;gt; {
    &lt;span style=&#34;color:#75715e&#34;&gt;// and on and on...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;  });
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After callbacks, he alludes to what would be the subsequent &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises&#34;&gt;Promises&lt;/a&gt; phase of Javascript/node.js with his section “&lt;em&gt;I promise the future is better&lt;/em&gt;”. An ergonomic improvement to callbacks but still cumbersome, and does not solve most red/blue pain points:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;readFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;)
  .&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;) =&amp;gt; {
    &lt;span style=&#34;color:#a6e22e&#34;&gt;saveFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;)
      .&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;file&lt;/span&gt;) =&amp;gt; {})
      .&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) =&amp;gt; {})
  })
  .&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) =&amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally he describes &lt;a href=&#34;https://javascript.info/async-await&#34;&gt;the &lt;code&gt;async/await&lt;/code&gt; phase&lt;/a&gt; in “&lt;em&gt;I’m awaiting a solution&lt;/em&gt;”, which leads us to modern Javascript:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt;);
} &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;);
}
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;file&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;saveFile&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;content&lt;/span&gt;);
} &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;invasive-async-await&#34;&gt;Invasive async/await&lt;/h3&gt;
&lt;p&gt;I’ll edit “&lt;em&gt;I’m awaiting a solution&lt;/em&gt;” for modern JS, where he notes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Synchronous functions return values, async ones return &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt; &lt;em&gt;(Promise containing type T)&lt;/em&gt; wrappers around the value.&lt;/li&gt;
&lt;li&gt;Sync functions are just called, async ones need an &lt;code&gt;await&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If you call an async function you’ve got this wrapper object when you actually want the &lt;code&gt;T&lt;/code&gt;. You can’t unwrap it unless you make &lt;em&gt;your&lt;/em&gt; function async and await it.”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;ndash; Nystrom&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;#3 is the particularly infectious bit. Async code bubbles all the way to the top. If you want to use &lt;code&gt;await&lt;/code&gt;, then you have to mark your function as &lt;code&gt;async&lt;/code&gt;. Then if someone else calling your function wants to use &lt;code&gt;await&lt;/code&gt;, they &lt;em&gt;also&lt;/em&gt; have to mark themselves as &lt;code&gt;async&lt;/code&gt;, on and on until the root of the call chain. If at any point you don’t then you have to use the &lt;code&gt;async&lt;/code&gt; result (in JavaScript’s case a &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readFile&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Promise&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;read&lt;/span&gt;();
}
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;iCallReadFile&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Promise&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;readFile&lt;/span&gt;();
}
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;iCallICallReadFile&lt;/span&gt;()&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; Promise&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;string&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;iCallReadFile&lt;/span&gt;();
}
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;iGiveUp&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;callback&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;iCallICallReadFile&lt;/span&gt;()
    .&lt;span style=&#34;color:#a6e22e&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;callback&lt;/span&gt;)
    .&lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;) =&amp;gt; {})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once your language is broken down into async and synchronous functions, you can’t escape it.&lt;/p&gt;
&lt;p&gt;Even more onerous, if it isn’t built into your language core like JavaScript/node.js, adding it later means modifying your entire runtime, libraries and codebases to understand it. This was (still can be?) a painful transition for languages like Python and Rust.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// et tu, Rust?
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;fn&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; content &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; read_file().&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;what-makes-ruby-colorless&#34;&gt;What makes Ruby colorless?&lt;/h2&gt;
&lt;p&gt;So what does a “blue” (synchronous) method look like in Ruby?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;File&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read(path)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And what does that same “red” (asynchronous) call look like?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;File&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;read(path)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wait. Is that right? Surely there must be more nuance?&lt;/p&gt;
&lt;p&gt;HTTP?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Net&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;HTTP&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(
  &lt;span style=&#34;color:#66d9ef&#34;&gt;URI&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://example.com&amp;#34;&lt;/span&gt;)
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Shell calls!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;output &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`cat file.txt`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Process spawning??&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;pid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fork
  &lt;span style=&#34;color:#75715e&#34;&gt;# good stuff&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait pid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mutex locks?!?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;mutex&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;synchronize {}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;DNS resolution!??!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;ip &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Resolv&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;getaddress &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ruby-lang.org&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sleep!?!!!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;😮‍💨😮‍💨😮‍💨&lt;/p&gt;
&lt;p&gt;Well that’s nice! There is no difference between asynchronous and synchronous methods, so no color. We get asynchronous behavior for free. Blog post over!?&lt;/p&gt;
&lt;p&gt;Well&amp;hellip; not &lt;em&gt;exactly&lt;/em&gt;. We still don’t know &lt;em&gt;how&lt;/em&gt; we get that behavior.&lt;/p&gt;
&lt;p&gt;In the section “&lt;em&gt;What language isn’t colored?&lt;/em&gt;” Nystrom specifically mentions Ruby being one of a few colorless languages&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;hellip;languages that don’t have this problem: [Java,] Go, Lua, and Ruby.&lt;/p&gt;
&lt;p&gt;Any guess what they have in common?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Threads&lt;/em&gt;. Or, more precisely: multiple independent callstacks that can be switched between. It isn’t strictly necessary for them to be operating system threads. Goroutines in Go, coroutines in Lua, and fibers in Ruby are perfectly adequate.&lt;/p&gt;
&lt;p&gt;&amp;ndash; Nystrom&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Ruby is colorless, and to be colorless you need independent call stacks that you can switch between. And apparently Threads and Fibers can give you that.&lt;/p&gt;
&lt;p&gt;What does that mean and what does that enable? And since Nystrom’s article was written in 2015 - is there anything new to Ruby since then?&lt;/p&gt;
&lt;h3 id=&#34;nested-concurrency&#34;&gt;A nested concurrency model 🪆&lt;/h3&gt;
&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;, we briefly discussed that Ruby has a layered concurrency model and that the best way to describe it is as a nesting doll - as you pull away the outer layers, you find new layers of concurrency inside.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/a44a601478.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;Here’s my key for mapping icons to concurrency&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;💎 represents a Ruby process, which is essentially another instance of Ruby itself&lt;/li&gt;
&lt;li&gt;🦖 represents a Ruby Ractor. Ractor stands for Ruby Actor, as in the Actor model. But I always think of the word “Raptor” instead, so I represent it with a Dinosaur 🤷🏻‍♂️&lt;/li&gt;
&lt;li&gt;🧵 represents a Ruby Thread. It’s a thread icon, for obvious reasons&lt;/li&gt;
&lt;li&gt;🌾 represents a Ruby Fiber. I think of wheat when I think of Fibers, hence 🌾&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a refresher, in your own Ruby code, you can easily inspect each layer directly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Process &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pid&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;  Ractor &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Ractor&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;    Thread &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;      Fiber &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Process 123&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#   Ractor #&amp;lt;Ractor:#1 running&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#     Thread #&amp;lt;Thread:0x0... run&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;#       Fiber #&amp;lt;Fiber:0x0... (resumed)&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your code is always operating within a specific instance of each layer. Mostly you can access those instances with a call to &lt;code&gt;current&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each layer has unique characteristics when it comes to running concurrent code. Some can operate in parallel, executing independent Ruby code at the exact same time. Others run your Ruby code concurrently, moving back and forth between them but never running simultaneously.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/1f6316498b.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;source&lt;/em&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The outermost layer is a &lt;strong&gt;Process 💎&lt;/strong&gt;. Every program on every common operating system (Linux, Windows, macOS) runs inside a process - it’s the top-level unit of execution in the OS. You can have as many processes as your OS will allow - but the number of CPU cores determine how many processes can run in parallel. Multiple Ruby processes can run in parallel but they live in isolated memory spaces - they have no simple way of communicating with each other.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;pid &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fork &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait pid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Internally, a Ruby process is made up of &lt;strong&gt;Ractors 🦖&lt;/strong&gt;. Ractors don’t have a direct parallel in the OS - they’re a unique Ruby structure which wrap native OS threads&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;. Multiple Ractors can run Ruby code in parallel, and they have strict sharing rules to keep them memory safe - so you can only communicate between them by passing messages. Those messages can copy data, or they can move objects completely between Ractors and change ownership. Ractors &lt;em&gt;sound&lt;/em&gt; pretty nice, but are still experimental. Regardless, every Ruby process starts out with a single Ractor that your Ruby code runs inside of.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;ractor &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Ractor&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
 &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
ractor&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;take
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Inside of Ractors, you have &lt;strong&gt;Threads 🧵&lt;/strong&gt;. Threads are created 1:1 with threads on your operating system&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;. These threads share the same memory space with each other, and every thread created within a process shares the same memory space as the process, and spends its lifetime there. Because threads share the same memory space they have to be carefully coordinated to safely manage state. Ruby threads cannot run CPU-bound Ruby code in parallel, but they can parallelize for blocking operations. Threads run &lt;em&gt;preemptively&lt;/em&gt;, which means they can swap out of your code at any time, and move to other Ruby code. Every Ruby Ractor starts out with a single thread that your Ruby code runs inside of.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;thread &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
thread&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Inside of threads, you have &lt;strong&gt;Fibers 🌾&lt;/strong&gt;. Fibers are another structure with no direct OS equivalent (though they have parallels in other languages). Fibers are a tool intended for lightweight, cooperative concurrency. Like Ruby threads, Fibers share a memory space, but their coordination is more deterministic. Also like Ruby threads, Fibers cannot run CPU-bound Ruby code in parallel, but using a FiberScheduler can parallelize for blocking operations. Every Ruby thread starts out with a single fiber that your Ruby code runs inside of.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;fiber &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
fiber&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;resume &lt;span style=&#34;color:#75715e&#34;&gt;# transfer / Fiber.schedule&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When a Ruby program starts, all four layers start with it. The &lt;strong&gt;Process 💎&lt;/strong&gt; starts, it creates a &lt;strong&gt;Ractor 🦖&lt;/strong&gt;, which creates a &lt;strong&gt;Thread 🧵&lt;/strong&gt;, which creates a &lt;strong&gt;Fiber 🌾&lt;/strong&gt;. Ultimately, your Ruby code is in the context of all 4, but most specifically the fiber. Each layer is always active in a Ruby program whether you use them directly or not&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;. We can utilize each in unique ways as well.&lt;/p&gt;
&lt;p&gt;That’s a lot! In subsequent posts you’ll learn why each layer exists and the values each provides. Every layer will get some discussion - but as it relates to asynchronous, colorless programming, threads and fibers are the most relevant.&lt;/p&gt;
&lt;h3 id=&#34;threads-and-fibers&#34;&gt;Colorless threads 🧵 and fibers 🌾&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/group-83-1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;source&lt;/em&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/thread&#34;&gt;Threads&lt;/a&gt; 🧵 are one of the oldest and most common units of concurrency in Ruby. They’re the OG for splitting up concurrent chunks of work. They’ve been in Ruby since Ruby 1.1&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt; and have enabled colorless calls for many years.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rubyapi.org/3.3/o/fiber&#34;&gt;Fibers&lt;/a&gt; 🌾 aren’t too young themselves, but are about 10 years younger than their older sibling Threads. They were released in Ruby 1.9, but remained a bit of an esoteric option for a long time. They offered a form of concurrency, but for most use-cases it didn’t really help anyone - unlike Threads, they didn’t enable colorless programming at first.&lt;/p&gt;
&lt;p&gt;But ever since Ruby 3, Fibers have been given superpowers in the form of the FiberScheduler. By using a FiberScheduler, Fibers can now provide seamless, colorless programming.&lt;/p&gt;
&lt;p&gt;So both threads and fibers offer colorless, concurrent programming. We’ve got a definition for colorless, but what about concurrency?&lt;/p&gt;
&lt;h3 id=&#34;to-be-concurrent&#34;&gt;What does it mean to be concurrent?&lt;/h3&gt;
&lt;p&gt;The most popular source for a definition of concurrency is from the creator of Go, Rob Pike, in his talk &lt;a href=&#34;https://youtu.be/oV9rvDllKEg?si=L3aitDpilMRk5Bfn&#34;&gt;concurrency is not parallelism&lt;/a&gt;. Another member of the Go team summarized it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When people hear the word concurrency they often think of parallelism, a related but quite distinct concept. In programming, &lt;strong&gt;concurrency is the composition of independently executing processes&lt;/strong&gt;, while parallelism is the simultaneous execution of (possibly related) computations. &lt;strong&gt;Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: &lt;a href=&#34;https://go.dev/blog/waza-talk&#34;&gt;https://go.dev/blog/waza-talk&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They describe it as a way of composing tasks. It allows you to spread out and coordinate work. How that work is ultimately executed is not relevant to whether the code is concurrent or not.&lt;/p&gt;
&lt;p&gt;Look at the activity monitor for your OS right now. If you were to count the number of processes and threads, it would vastly exceed the number of available cores. And yet everything still chugs along smoothly. That’s concurrency in action.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/activity-monitor-final.png&#34; width=&#34;60%&#34; height=&#34;60%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;There are over 200 threads associated with just these 5 apps. I don’t have 200 CPU cores, so only a handful of them could ever be active in parallel. But all of them &lt;em&gt;could&lt;/em&gt; operate &lt;em&gt;concurrently&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Having said all that - we do &lt;em&gt;want&lt;/em&gt; our work to happen as parallel as possible in most cases. We want to maximize our resources and parallelize as much as we can.&lt;/p&gt;
&lt;p&gt;In Rob Pike’s concurrency talk, he used the Go mascot, the gopher, to demonstrate how his task (incinerating obsolete language manuals 😅) was composed, and how it could be run in parallel, or with one gopher at a time, but it was still concurrent:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/draggedimage.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: &lt;a href=&#34;https://go.dev/talks/2012/waza.slide&#34;&gt;https://go.dev/talks/2012/waza.slide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Go’s gophers are kind of fun. Let’s use some concurrency mascots for Ruby to show our own example.&lt;/p&gt;
&lt;h3 id=&#34;process&#34;&gt;Process!&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/process.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;ractor&#34;&gt;Ractor!&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ractor.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;thread&#34;&gt;Thread!&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/thread.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;fiber&#34;&gt;Fiber!&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/fiber.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;cpu&#34;&gt;CPU!&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/cpu.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;source&lt;/em&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a baseline, Processes and Ractors can run in parallel for as many cores as are available:&lt;/p&gt;
&lt;p&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-2.png&#34; width=&#34;488&#34; height=&#34;283&#34; alt=&#34;&#34;&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio.png&#34; width=&#34;488&#34; height=&#34;273&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;But Threads and Fibers, while concurrent, effectively only parallelize against one core at a time&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-1.png&#34; width=&#34;488&#34; height=&#34;365&#34; alt=&#34;&#34;&gt;
&lt;p&gt;What happens when we start scaling Processes and Ractors past the number of available cores?&lt;/p&gt;
&lt;p&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-3.png&#34; width=&#34;600&#34; height=&#34;252&#34; alt=&#34;&#34;&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-4.png&#34; width=&#34;600&#34; height=&#34;245&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Once we exceed the ability to parallelize, we are concurrent in the same way as Threads and Fibers! This model allows our units of concurrency adapt to the environment - when cores are available they can run in parallel, and when cores are not available they can swap between each other, transparently to the program itself. Every program eventually becomes concurrent as it scales, at least for CPU-bound processing.&lt;/p&gt;
&lt;p&gt;But this leaves Threads and Fibers looking pretty limited. They allow you to breakup your work into independent, interleaving tasks, operating concurrently on Ruby code. But what good is that since they &lt;em&gt;never&lt;/em&gt; operate in parallel&amp;hellip; or do they?&lt;/p&gt;
&lt;h3 id=&#34;colorless-calls&#34;&gt;Colorless calls&lt;/h3&gt;
&lt;p&gt;How does this all relate to colorless methods?&lt;/p&gt;
&lt;p&gt;Let’s see with an example. In our example, we’re just traversing a few different websites we care about, and we want to retrieve them as efficiently as possible.&lt;/p&gt;
&lt;p&gt;First we try it with threads. For demonstration purposes, we use the &lt;code&gt;httpbin.com/delay&lt;/code&gt; endpoint to simulate delays in responses.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;log_then_get&lt;/span&gt;(url)
  puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Requesting &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;url&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;...&amp;#34;&lt;/span&gt;
  get(url)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get&lt;/span&gt;(uri)
  response &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Net&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;HTTP&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(uri)
  puts caller(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)
  response
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_http_thread&lt;/span&gt;(url)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    log_then_get(&lt;span style=&#34;color:#66d9ef&#34;&gt;URI&lt;/span&gt;(url))
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_http_via_threads&lt;/span&gt;
  threads &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;
  threads &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_thread(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=1&amp;#34;&lt;/span&gt;
  )
  threads &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_thread(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=2&amp;#34;&lt;/span&gt;
  )
  threads &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_thread(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=3&amp;#34;&lt;/span&gt;
  )
  threads &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_thread(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=4&amp;#34;&lt;/span&gt;
  )
  threads&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:value&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now
get_http_via_threads
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Thread runtime: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code is doing a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It’s split into multiple methods to create a callstack (effectively, a backtrace). This callstack allows us to demonstrate that we maintain the position and state of each method call, even when we switch between threads.&lt;/li&gt;
&lt;li&gt;It creates four threads and appends them to an array. After those threads are initialized they go into a ready state, available to be run by the thread scheduler.&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;value&lt;/code&gt;on each thread. This blocks the program until each thread finishes and returns the last value returned from the thread. Once we block the main thread with &lt;code&gt;value&lt;/code&gt;, the other threads have an immediate opportunity to start running. In this case we block until each thread finishes making an HTTP call.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# &amp;gt; bundle exec ruby main.rb&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=1...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=2...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=3...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=4...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:18:in `block in get_http_thread&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:18:in `block in get_http_thread&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:18:in `block in get_http_thread&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:18:in `block in get_http_thread&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread runtime: 3.340238554&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Second we try it with fibers:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;net/http&amp;#34;&lt;/span&gt;
require &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;async&amp;#34;&lt;/span&gt;
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Same #log_then_get&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Same #get&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_http_fiber&lt;/span&gt;(url, responses)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;schedule &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    responses &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; log_then_get(&lt;span style=&#34;color:#66d9ef&#34;&gt;URI&lt;/span&gt;(url))
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_http_via_fibers&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_scheduler(&lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new)
  responses &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt;
  responses &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_fiber(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=1&amp;#34;&lt;/span&gt;, responses
  )
  responses &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_fiber(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=2&amp;#34;&lt;/span&gt;, responses
  )
  responses &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_fiber(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=3&amp;#34;&lt;/span&gt;, responses
  )
  responses &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_fiber(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;https://httpbin.org/delay/3?ex=4&amp;#34;&lt;/span&gt;, responses
  )
  responses
&lt;span style=&#34;color:#66d9ef&#34;&gt;ensure&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_scheduler(&lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now
get_http_via_fibers
puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Fiber runtime: &lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Time&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;now &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; now&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Same as before, this code is doing a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We are split into multiple methods to create a callstack. Same as with threads, this callstack allows us to demonstrate that we maintain the position and state of each method call, even when we switch between fibers.&lt;/li&gt;
&lt;li&gt;It schedules four Fibers. We append to an array inside the &lt;code&gt;Fiber.schedule&lt;/code&gt; to get the result. Unlike threads, a scheduled fiber will start running its block immediately.&lt;/li&gt;
&lt;li&gt;The fibers will coordinate between themselves and the current fiber. When we unset the FiberScheduler using &lt;code&gt;set_scheduler(nil)&lt;/code&gt; in our &lt;code&gt;ensure&lt;/code&gt;, the &lt;code&gt;Async::Scheduler&lt;/code&gt; makes sure all fibers have finished running before returning.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This results in the following, similar backtrace:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# &amp;gt; bundle exec ruby main.rb&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# &lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=1...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=2...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=3...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Requesting https://httpbin.org/delay/3?ex=4...&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:24:in `block in get_http_fiber&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:160:in `block in run&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:24:in `block in get_http_fiber&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:160:in `block in run&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:24:in `block in get_http_fiber&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:160:in `block in run&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:12:in `get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:7:in `log_then_get&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# main.rb:24:in `block in get_http_fiber&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:160:in `block in run&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Fiber runtime: 3.291355669&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we visualize what’s happening, we see how things are getting coordinated. It’s nearly identical between the two:&lt;/p&gt;
&lt;p&gt;Threads:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Fibers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;👆🏼This diagram does look pretty sequential - that’s odd huh?&lt;/p&gt;
&lt;p&gt;And here is what happens once we are waiting for a response:&lt;/p&gt;
&lt;p&gt;Threads:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Fibers:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;👆🏼Oh, that seems better!&lt;/p&gt;
&lt;p&gt;Running these HTTP methods is the same if I run it with no threads/fibers, 4 threads/fibers, or 400 threads/fibers. The code never changes, the execution context is all we need to start operating asynchronously. &lt;em&gt;Something&lt;/em&gt; is coordinating for us behind-the-scenes, and we’re able to handle our HTTP calls in parallel 🙌🏼.&lt;/p&gt;
&lt;p&gt;This lines up with our timing. In each example we ran 4 HTTP calls, all guaranteed to block for around 3 seconds. Despite that, each example finished in roughly 3 seconds, rather than the 12 seconds it would take if they had run sequentially.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Fiber runtime: 3.291355669&lt;/span&gt;
&lt;span style=&#34;color:#75715e&#34;&gt;# Thread runtime: 3.340238554&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To paraphrase Bob Nystrom’s article one last time, we can apply his original points about Go almost perfectly to Ruby threads/fibers:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As soon as you do any IO operation, it just parks that thread/fiber and resumes any other one that isn’t blocked on IO.&lt;/p&gt;
&lt;p&gt;If you look at the IO operations in the standard library, they seem synchronous. In other words, they just do work and then return a result when they are done. But it&amp;rsquo;s not that they&amp;rsquo;re synchronous in the sense that it would mean in JavaScript. Other Ruby code can run while one of these operations is pending. It&amp;rsquo;s that Ruby has eliminated the distinction between synchronous and asynchronous code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As we saw in the initial examples of colorless method calls - it’s not purely IO that parks and resumes. It’s the majority of blocking operations. Here’s another example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Threads&lt;/span&gt;
threads &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; },
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { &lt;span style=&#34;color:#e6db74&#34;&gt;`ruby -v`&lt;/span&gt; },
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Thread&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new { 
    noop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fork {}
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait noop
  }
&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
	
results &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; threads&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:value&lt;/span&gt;)
	
&lt;span style=&#34;color:#75715e&#34;&gt;# Fibers&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set_scheduler(&lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Scheduler&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new)
fibers &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; { sleep &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; },
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; { &lt;span style=&#34;color:#e6db74&#34;&gt;`ruby -v`&lt;/span&gt; },
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; { 
    noop &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fork {}
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Process&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;wait noop
  }
&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
	
results &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; fibers&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;map(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:wait&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We &lt;a href=&#34;https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-fork&#34;&gt;&lt;code&gt;fork&lt;/code&gt;&lt;/a&gt; a ruby process, run a command line script to get the current ruby version using &lt;code&gt;ruby -v&lt;/code&gt; and &lt;a href=&#34;https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-sleep&#34;&gt;&lt;code&gt;sleep&lt;/code&gt;&lt;/a&gt; - all in parallel.&lt;/p&gt;
&lt;h3 id=&#34;async-aside&#34;&gt;A quick aside on Async&lt;/h3&gt;
&lt;p&gt;Manually setting the FiberScheduler and manually unsetting it is just to more clearly demonstrate the interface. But you would normally run the block format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  fibers &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; get_http_fiber &lt;span style=&#34;color:#75715e&#34;&gt;#...&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You’d also use the &lt;code&gt;Async&lt;/code&gt; helpers instead of &lt;code&gt;Fiber.schedule&lt;/code&gt;, which have a more robust available API:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_http_fiber&lt;/span&gt;(url)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
    log_then_get(&lt;span style=&#34;color:#66d9ef&#34;&gt;URI&lt;/span&gt;(url), &lt;span style=&#34;color:#66d9ef&#34;&gt;Fiber&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;current)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;digging-deep&#34;&gt;Digging deep&lt;/h3&gt;
&lt;p&gt;My toy examples aside, this is one of the things that makes threaded/fibered web servers like &lt;a href=&#34;https://github.com/puma/puma&#34;&gt;Puma&lt;/a&gt;/&lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;Falcon&lt;/a&gt; and threaded job servers like &lt;a href=&#34;https://github.com/sidekiq/sidekiq&#34;&gt;Sidekiq&lt;/a&gt;/&lt;a href=&#34;https://github.com/bensheldon/good_job&#34;&gt;GoodJob&lt;/a&gt; so powerful - parallelizing blocking operations. Every time one of your job or web server threads/fibers reads a file, queries a database, makes an HTTP call, or waits for a shared resource, Ruby parks the thread/fiber and another thread/fiber is able to take over. Depending on the type of workload you can have dozens/hundreds/thousands of threads/fibers running concurrently, and blocking in parallel. We’ll push some limits on that later in the series to see where that might break down, and how we can keep scaling past those limits!&lt;/p&gt;
&lt;p&gt;Now that we understand what colorless Ruby programming means and how it works - we’re left with some questions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why would we need or want both Threads and Fibers?&lt;/li&gt;
&lt;li&gt;Why do some languages choose to introduce color to their languages? Are there upsides to color?&lt;/li&gt;
&lt;li&gt;What is the deal with those “VM locks”?&lt;/li&gt;
&lt;li&gt;What does the “OS Kernel” have to do with parallel IO?&lt;/li&gt;
&lt;li&gt;What’s a Reactor?&lt;/li&gt;
&lt;li&gt;Is there a best concurrency option?&lt;/li&gt;
&lt;li&gt;Are there abstractions you should be using?&lt;/li&gt;
&lt;li&gt;What’s the point of concurrency without parallelism?&lt;/li&gt;
&lt;li&gt;We also keep talking about parallelizing blocking operations - why don’t threads and fibers already allow you to parallelize everything?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To answer these questions (and more), we’re going to dig pretty far into the ruby runtime to better understand Ruby threads, fibers and other forms of concurrency in Ruby.&lt;/p&gt;
&lt;p&gt;Let’s start with the OG, Threads, in “The Thread API: Concurrent, colorless Ruby”. More soon! 👋🏼&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/thread.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;
&lt;h3 id=&#34;ruby-history-corner&#34;&gt;PS: A historical sidenote - Ruby had its own callback phase&lt;/h3&gt;
&lt;p&gt;It was 2009. The Black Eyed Peas were on top of the charts with “Boom Boom Pow”. Microsoft had just launched Windows 7. Avatar was taking off in the box office. And of course most important of all, node.js was released (😉). During a brief ensuing insanity, everyone thought it would devour the world of web development.&lt;/p&gt;
&lt;p&gt;Like any new option, it ultimately took its place &lt;em&gt;alongside&lt;/em&gt; other tools, but it had a big influence in the popularity of the &lt;a href=&#34;https://en.wikipedia.org/wiki/Reactor_pattern&#34;&gt;reactor pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The reactor pattern is an event loop that registers events with the operating system and efficiently multiplexes them. It was a way of solving the &lt;a href=&#34;https://en.m.wikipedia.org/wiki/C10k_problem&#34;&gt;C10K problem&lt;/a&gt; - a solution for serving 10 thousand+ clients from a single server.&lt;/p&gt;
&lt;p&gt;To get a great insight into the Ruby concurrency landscape at that time, and why the node.js model seemed so appealing, see this post from Yehuda Katz in 2010 - &lt;a href=&#34;https://yehudakatz.com/2010/08/14/threads-in-ruby-enough-already/&#34;&gt;https://yehudakatz.com/2010/08/14/threads-in-ruby-enough-already/&lt;/a&gt;. In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Early Rails was not thread-safe so you could only scale with Processes&lt;/li&gt;
&lt;li&gt;Once Rails was “thread-safe”, it started with a giant mutex around the entire framework that meant only one thread could do anything at a time, including blocking operations&lt;/li&gt;
&lt;li&gt;Even threaded servers at the time would often wrap things poorly and too broadly in mutexes&lt;/li&gt;
&lt;li&gt;The most popular database for Ruby/Rails at the time, MySQL, had a Ruby driver which did not release the thread on blocking operations&lt;/li&gt;
&lt;li&gt;Ruby 1.9 was the first Ruby to map to real operating system threads and loads of people still worked in Ruby 1.8&lt;/li&gt;
&lt;li&gt;He doesn’t mention it specifically, but the gem landscape was also pretty scary in terms of thread safety because many people did not understand or worry about it prior to that point. Things are definitely better now, though I’d recommend reviewing my &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby Programs are always multi-threaded&lt;/a&gt; posts, and specifically &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#tips-for-gems&#34;&gt;tips for auditing gems&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was a concurrency mess.&lt;/p&gt;
&lt;p&gt;No wonder node.js seemed like a magical concurrency silver bullet. A single process handling hundreds to thousands of users vs dozens of heavyweight processes handling a paltry comparative amount.&lt;/p&gt;
&lt;p&gt;In the Ruby world the reactor pattern was served by a tool called EventMachine. We’ll talk a bit deeper about EventMachine in the “Concurrent, Colorless Ruby” fiber post later, but it was solely callback based.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EventMachine&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;run &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
  redis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;EM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Hiredis&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;connect
  http &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;EM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;HttpRequest&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;http://google.com/&amp;#34;&lt;/span&gt;
  )&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get &lt;span style=&#34;color:#e6db74&#34;&gt;query&lt;/span&gt;: { &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;keyname&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt; }
	
  http&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;errback { &lt;span style=&#34;color:#66d9ef&#34;&gt;EventMachine&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop }
  http&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;callback {
    parse(http&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;response)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;each &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;link&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      req &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;EM&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;HttpRequest&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(link)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get
      req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;callback {
        body &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; req&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;response
        redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set(link, body)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;callback {
          &lt;span style=&#34;color:#75715e&#34;&gt;# and on and on&lt;/span&gt;
        }
      }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  }
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;EventMachine&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;stop
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see how messy coding like this becomes, just like the original callback model we presented in JavaScript.&lt;/p&gt;
&lt;p&gt;EventMachine was a powerful scaling option, and people still maintain EventMachine-based code today, but it was at odds with the normal flow of Ruby code. Ruby could have moved to red and blue style calls like some other languages to clean that up, but instead superseded those Reactor efforts with the FiberScheduler.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The programming language, not the game 🙂&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Turns out prime did a reaction video on it as well &lt;a href=&#34;https://youtu.be/MoKe4zvtNzA&#34;&gt;https://youtu.be/MoKe4zvtNzA&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In case this seems like a confusing and bizarre title, it’s a play on “the road to hell is paved with good intention” &lt;a href=&#34;https://en.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions&#34;&gt;https://en.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions&lt;/a&gt; 🌈&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m sure this is not exhaustive, the list is just illustrative and represents some popular languages. You don’t need to defend your language of choice 😉&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;They stand for Ruby Actors so it is a Ruby form of the &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Actor_model&#34;&gt;actor&lt;/a&gt; model. It works similarly to &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers&#34;&gt;JavaScript WebWorkers&lt;/a&gt; in that they are isolated and require strict rules for sharing to enforce thread safety&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;For now, as we’ll discuss later&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;As with most things, there’s even more nuance to the layering and we’ll touch on it a bit later&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;But have evolved significantly since then. This also required community support in addition to language support&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s more nuanced than this, and this is more for illustrative puposes. Even though they don’t run Ruby code in parallel, threads and fibers may still be tied to separate CPUs.&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/aa82bee7-c210-489e-9b5c-b1fa798f2d8d.jpeg)

&gt; 👋🏼 This is part of series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt;   - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - Ruby methods are colorless
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “Ruby methods are colorless”. I’ll update the links as each part is released, and include these links in each post.

- [First, some context](#some-context)
	- [Function colors](#function-colors)
	- [The road to hell is paved with callbacks](#paved-with-callbacks)
	- [Invasive async/await](#invasive-async-await)
- [What makes Ruby colorless?](#what-makes-ruby-colorless)
	- [A nested concurrency model 🪆](#nested-concurrency)
	- [Colorless threads 🧵 and Fibers 🌾 ](#threads-and-fibers)
	- [What does it mean to be concurrent?](#to-be-concurrent)
	- [Colorless calls](#colorless-calls)
	- [A quick aside on Async](#async-aside)
	- [Digging deep](#digging-deep)
	- [PS: A historical sidenote - Ruby had its own callback phase](#ruby-history-corner)

&gt; 📝 **A quick note on Ruby runtimes**
&gt; 
&gt; I’m approaching this series from the perspective of CRuby. This is the original and most popular/common Ruby runtime. I’m also focusing primarily on Ruby 3.2/3.3+. This matters because it informs how things like Threads and Fibers behave.

&lt;h2 id=&#34;some-context&#34;&gt;First, some context&lt;/h2&gt;

Last year I [read a tweet](https://twitter.com/ThePrimeagen/status/1689289653680033792) from [@ThePrimagen](https://twitter.com/ThePrimeagen) about his experience with [Go](https://go.dev/)[^1]. In it he mentioned some strengths and weaknesses (cheekily), and then he mentioned a phrase I’d never heard before: “colorless functions”.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/03867f06ca.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;

&gt; real talk, colorless functions [are] amazing and I think that most people don’t realize how great it really is
&gt; 
&gt; -- @ThePrimeagen

&lt;h3 id=&#34;function-colors&#34;&gt;Function colors&lt;/h3&gt;

Curious to understand what a “colorless function” is, a quick search returned this article as the first result: [What color is your function?](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)[^2]

In it, [Bob Nystrom](https://twitter.com/munificentbob) goes through a fictional language (spoiler: it’s Javascript/node.js) that breaks functions down into one of two “colors”:

&gt; 1. Every function has a color
&gt; 2. The way you call a function depends on its color
&gt; 3. You can only call a red function from within a red function
&gt; 4. Red functions are more painful to call
&gt; 5. Some core library functions are red
&gt; 
&gt; It’s an allegory... red functions are asynchronous ones
&gt; 
&gt; -- Nystrom

Red and blue functions equate to asynchronous and synchronous functions. And having to indicate the color of your function infects the rest of your code and the API decisions you make.

&lt;h3 id=&#34;paved-with-callbacks&#34;&gt;The road to hell is paved with callbacks&lt;/h3&gt;

Nystrom’s article was written in 2015 while Javascript/node was still in its [callback hell](http://callbackhell.com/)[^3] phase. Any operation that could block, like reading a file, making an HTTP call, or querying a database had to be done using a callback. Callbacks proliferated throughout your code - so asynchronous, red functions were plentiful and painful to interact with.

In JavaScript, everything happens in a single-ish threaded context called the “event loop”. You can’t block the loop - so you would set a callback to know when a blocking call completed:

```js
readFile(path, (content, err) =&gt; {
  if (err) { throw &#39;&#39;; }
  saveFile(content, (file, err) =&gt; {
    // and on and on...
  });
});
```
After callbacks, he alludes to what would be the subsequent [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises) phase of Javascript/node.js with his section “_I promise the future is better_”. An ergonomic improvement to callbacks but still cumbersome, and does not solve most red/blue pain points:

```js
readFile(path)
  .then((content) =&gt; {
    saveFile(content)
      .then((file) =&gt; {})
      .catch(err) =&gt; {})
  })
  .catch((err) =&gt; console.error(err));
```
Finally he describes [the `async/await` phase](https://javascript.info/async-await) in “_I’m awaiting a solution_”, which leads us to modern Javascript:

```js
try {
  const content = await readFile(path);
} catch (err) {
  console.error(err);
}
	
try {
  const file = await saveFile(content);
} catch (err) {
  console.error(err);
}
```
&lt;h3 id=&#34;invasive-async-await&#34;&gt;Invasive async/await&lt;/h3&gt;

I’ll edit “_I’m awaiting a solution_” for modern JS, where he notes:

&gt; 1. Synchronous functions return values, async ones return `Promise&lt;T&gt;` _(Promise containing type T)_ wrappers around the value.
&gt; 2. Sync functions are just called, async ones need an `await`.
&gt; 3. If you call an async function you’ve got this wrapper object when you actually want the `T`. You can’t unwrap it unless you make _your_ function async and await it.”
&gt; 
&gt; -- Nystrom

\#3 is the particularly infectious bit. Async code bubbles all the way to the top. If you want to use `await`, then you have to mark your function as `async`. Then if someone else calling your function wants to use `await`, they _also_ have to mark themselves as `async`, on and on until the root of the call chain. If at any point you don’t then you have to use the `async` result (in JavaScript’s case a `Promise&lt;T&gt;`).

```js
async function readFile(): Promise&lt;string&gt; {
  return await read();
}
	
async function iCallReadFile(): Promise&lt;string&gt; {
  return await readFile();
}
	
async function iCallICallReadFile(): Promise&lt;string&gt; {
  return await iCallReadFile();
}
	
function iGiveUp(callback) {
  iCallICallReadFile()
    .then(callback)
    .catch((err) =&gt; {})
}
```
Once your language is broken down into async and synchronous functions, you can’t escape it. 

Even more onerous, if it isn’t built into your language core like JavaScript/node.js, adding it later means modifying your entire runtime, libraries and codebases to understand it. This was (still can be?) a painful transition for languages like Python and Rust.

```rust
// et tu, Rust?
async fn main() {
  let content = read_file().await;
}
```
&lt;h2 id=&#34;what-makes-ruby-colorless&#34;&gt;What makes Ruby colorless?&lt;/h2&gt;

So what does a “blue” (synchronous) method look like in Ruby?

```rb
file = File.read(path)
```
And what does that same “red” (asynchronous) call look like?

```rb
file = File.read(path)
```
Wait. Is that right? Surely there must be more nuance?

HTTP?

```rb
response = Net::HTTP.get(
  URI(&#34;https://example.com&#34;)
)
```
Shell calls!

```rb
output = `cat file.txt`
```
Process spawning??

```rb
pid = fork
  # good stuff
end
Process::Status.wait pid
```
Mutex locks?!?

```rb
mutex.synchronize {}
```
DNS resolution!??!

```rb
ip = Resolv.getaddress &#34;ruby-lang.org&#34;
```
Sleep!?!!!

```rb
sleep 5
```
😮‍💨😮‍💨😮‍💨

Well that’s nice! There is no difference between asynchronous and synchronous methods, so no color. We get asynchronous behavior for free. Blog post over!?

Well... not _exactly_. We still don’t know _how_ we get that behavior.

In the section “_What language isn’t colored?_” Nystrom specifically mentions Ruby being one of a few colorless languages[^4]:

&gt; ...languages that don’t have this problem: [Java,] Go, Lua, and Ruby.
&gt; 
&gt; Any guess what they have in common?
&gt; 
&gt; _Threads_. Or, more precisely: multiple independent callstacks that can be switched between. It isn’t strictly necessary for them to be operating system threads. Goroutines in Go, coroutines in Lua, and fibers in Ruby are perfectly adequate.
&gt; 
&gt; -- Nystrom

So Ruby is colorless, and to be colorless you need independent call stacks that you can switch between. And apparently Threads and Fibers can give you that.

What does that mean and what does that enable? And since Nystrom’s article was written in 2015 - is there anything new to Ruby since then? 

&lt;h3 id=&#34;nested-concurrency&#34;&gt;A nested concurrency model 🪆&lt;/h3&gt;

In [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html), we briefly discussed that Ruby has a layered concurrency model and that the best way to describe it is as a nesting doll - as you pull away the outer layers, you find new layers of concurrency inside. 

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/a44a601478.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;
 
&gt; Here’s my key for mapping icons to concurrency
&gt; 
&gt; - 💎 represents a Ruby process, which is essentially another instance of Ruby itself
&gt; - 🦖 represents a Ruby Ractor. Ractor stands for Ruby Actor, as in the Actor model. But I always think of the word “Raptor” instead, so I represent it with a Dinosaur 🤷🏻‍♂️ 
&gt; - 🧵 represents a Ruby Thread. It’s a thread icon, for obvious reasons 
&gt; - 🌾 represents a Ruby Fiber. I think of wheat when I think of Fibers, hence 🌾 

As a refresher, in your own Ruby code, you can easily inspect each layer directly:

```rb
puts &#34;Process #{Process.pid}&#34;
puts &#34;  Ractor #{Ractor.current}&#34;
puts &#34;    Thread #{Thread.current}&#34;
puts &#34;      Fiber #{Fiber.current}&#34;
	
# Process 123
#   Ractor #&lt;Ractor:#1 running&gt;
#     Thread #&lt;Thread:0x0... run&gt;
#       Fiber #&lt;Fiber:0x0... (resumed)&gt;
```
Your code is always operating within a specific instance of each layer. Mostly you can access those instances with a call to `current`.

Each layer has unique characteristics when it comes to running concurrent code. Some can operate in parallel, executing independent Ruby code at the exact same time. Others run your Ruby code concurrently, moving back and forth between them but never running simultaneously.

![](https://cdn.uploads.micro.blog/98548/2024/1f6316498b.jpeg)

&gt; *source*: jpcamara.com

The outermost layer is a **Process 💎**. Every program on every common operating system (Linux, Windows, macOS) runs inside a process - it’s the top-level unit of execution in the OS. You can have as many processes as your OS will allow - but the number of CPU cores determine how many processes can run in parallel. Multiple Ruby processes can run in parallel but they live in isolated memory spaces - they have no simple way of communicating with each other.

```rb
pid = fork do
  #...
end
Process::Status.wait pid
```
Internally, a Ruby process is made up of **Ractors 🦖**. Ractors don’t have a direct parallel in the OS - they’re a unique Ruby structure which wrap native OS threads[^5]. Multiple Ractors can run Ruby code in parallel, and they have strict sharing rules to keep them memory safe - so you can only communicate between them by passing messages. Those messages can copy data, or they can move objects completely between Ractors and change ownership. Ractors _sound_ pretty nice, but are still experimental. Regardless, every Ruby process starts out with a single Ractor that your Ruby code runs inside of.

```rb
ractor = Ractor.new do
 #...
end
ractor.take
```
Inside of Ractors, you have **Threads 🧵**. Threads are created 1:1 with threads on your operating system[^6]. These threads share the same memory space with each other, and every thread created within a process shares the same memory space as the process, and spends its lifetime there. Because threads share the same memory space they have to be carefully coordinated to safely manage state. Ruby threads cannot run CPU-bound Ruby code in parallel, but they can parallelize for blocking operations. Threads run _preemptively_, which means they can swap out of your code at any time, and move to other Ruby code. Every Ruby Ractor starts out with a single thread that your Ruby code runs inside of.

```rb
thread = Thread.new do
  #...
end
thread.join
```
Inside of threads, you have **Fibers 🌾**. Fibers are another structure with no direct OS equivalent (though they have parallels in other languages). Fibers are a tool intended for lightweight, cooperative concurrency. Like Ruby threads, Fibers share a memory space, but their coordination is more deterministic. Also like Ruby threads, Fibers cannot run CPU-bound Ruby code in parallel, but using a FiberScheduler can parallelize for blocking operations. Every Ruby thread starts out with a single fiber that your Ruby code runs inside of.

```rb
fiber = Fiber.new do
  #...
end
fiber.resume # transfer / Fiber.schedule
```
When a Ruby program starts, all four layers start with it. The **Process 💎** starts, it creates a **Ractor 🦖**, which creates a **Thread 🧵**, which creates a **Fiber 🌾**. Ultimately, your Ruby code is in the context of all 4, but most specifically the fiber. Each layer is always active in a Ruby program whether you use them directly or not[^7]. We can utilize each in unique ways as well.

That’s a lot! In subsequent posts you’ll learn why each layer exists and the values each provides. Every layer will get some discussion - but as it relates to asynchronous, colorless programming, threads and fibers are the most relevant. 

&lt;h3 id=&#34;threads-and-fibers&#34;&gt;Colorless threads 🧵 and fibers 🌾&lt;/h3&gt;

![](https://cdn.uploads.micro.blog/98548/2024/group-83-1.jpg)

&gt; *source*: jpcamara.com

[Threads](https://rubyapi.org/3.3/o/thread) 🧵 are one of the oldest and most common units of concurrency in Ruby. They’re the OG for splitting up concurrent chunks of work. They’ve been in Ruby since Ruby 1.1[^8] and have enabled colorless calls for many years.

[Fibers](https://rubyapi.org/3.3/o/fiber) 🌾 aren’t too young themselves, but are about 10 years younger than their older sibling Threads. They were released in Ruby 1.9, but remained a bit of an esoteric option for a long time. They offered a form of concurrency, but for most use-cases it didn’t really help anyone - unlike Threads, they didn’t enable colorless programming at first.

But ever since Ruby 3, Fibers have been given superpowers in the form of the FiberScheduler. By using a FiberScheduler, Fibers can now provide seamless, colorless programming.

So both threads and fibers offer colorless, concurrent programming. We’ve got a definition for colorless, but what about concurrency?

&lt;h3 id=&#34;to-be-concurrent&#34;&gt;What does it mean to be concurrent?&lt;/h3&gt;

The most popular source for a definition of concurrency is from the creator of Go, Rob Pike, in his talk [concurrency is not parallelism](https://youtu.be/oV9rvDllKEg?si=L3aitDpilMRk5Bfn). Another member of the Go team summarized it: 

&gt; When people hear the word concurrency they often think of parallelism, a related but quite distinct concept. In programming, **concurrency is the composition of independently executing processes**, while parallelism is the simultaneous execution of (possibly related) computations. **Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.**
&gt; 
&gt; **source**: [https://go.dev/blog/waza-talk](https://go.dev/blog/waza-talk)

They describe it as a way of composing tasks. It allows you to spread out and coordinate work. How that work is ultimately executed is not relevant to whether the code is concurrent or not.

Look at the activity monitor for your OS right now. If you were to count the number of processes and threads, it would vastly exceed the number of available cores. And yet everything still chugs along smoothly. That’s concurrency in action.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/activity-monitor-final.png&#34; width=&#34;60%&#34; height=&#34;60%&#34; alt=&#34;&#34;&gt;

&gt; There are over 200 threads associated with just these 5 apps. I don’t have 200 CPU cores, so only a handful of them could ever be active in parallel. But all of them _could_ operate _concurrently_.

Having said all that - we do _want_ our work to happen as parallel as possible in most cases. We want to maximize our resources and parallelize as much as we can.

In Rob Pike’s concurrency talk, he used the Go mascot, the gopher, to demonstrate how his task (incinerating obsolete language manuals 😅) was composed, and how it could be run in parallel, or with one gopher at a time, but it was still concurrent:

![](https://cdn.uploads.micro.blog/98548/2024/draggedimage.png)

&gt; **source**: [https://go.dev/talks/2012/waza.slide](https://go.dev/talks/2012/waza.slide)

Go’s gophers are kind of fun. Let’s use some concurrency mascots for Ruby to show our own example.

### Process!
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/process.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;


### Ractor!
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ractor.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;

### Thread!
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/thread.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;

### Fiber!
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/fiber.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;

### CPU!
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/cpu.png&#34; width=&#34;35%&#34; height=&#34;35%&#34; alt=&#34;&#34;&gt;

&gt; *source*: jpcamara.com

As a baseline, Processes and Ractors can run in parallel for as many cores as are available:

&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-2.png&#34; width=&#34;488&#34; height=&#34;283&#34; alt=&#34;&#34;&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio.png&#34; width=&#34;488&#34; height=&#34;273&#34; alt=&#34;&#34;&gt;

But Threads and Fibers, while concurrent, effectively only parallelize against one core at a time[^9]:

&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-1.png&#34; width=&#34;488&#34; height=&#34;365&#34; alt=&#34;&#34;&gt;

What happens when we start scaling Processes and Ractors past the number of available cores?

&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-3.png&#34; width=&#34;600&#34; height=&#34;252&#34; alt=&#34;&#34;&gt;&lt;img class=&#34;invert&#34; src=&#34;https://cdn.uploads.micro.blog/98548/2024/colorless-mascots.drawio-4.png&#34; width=&#34;600&#34; height=&#34;245&#34; alt=&#34;&#34;&gt;

Once we exceed the ability to parallelize, we are concurrent in the same way as Threads and Fibers! This model allows our units of concurrency adapt to the environment - when cores are available they can run in parallel, and when cores are not available they can swap between each other, transparently to the program itself. Every program eventually becomes concurrent as it scales, at least for CPU-bound processing.

But this leaves Threads and Fibers looking pretty limited. They allow you to breakup your work into independent, interleaving tasks, operating concurrently on Ruby code. But what good is that since they _never_ operate in parallel... or do they?

&lt;h3 id=&#34;colorless-calls&#34;&gt;Colorless calls&lt;/h3&gt;

How does this all relate to colorless methods? 

Let’s see with an example. In our example, we’re just traversing a few different websites we care about, and we want to retrieve them as efficiently as possible.

First we try it with threads. For demonstration purposes, we use the `httpbin.com/delay` endpoint to simulate delays in responses.

```rb
require &#34;net/http&#34;
	
def log_then_get(url)
  puts &#34;Requesting #{url}...&#34;
  get(url)
end
	
def get(uri)
  response = Net::HTTP.get(uri)
  puts caller(0).join(&#34;\n&#34;)
  response
end
	
def get_http_thread(url)
  Thread.new do
    log_then_get(URI(url))
  end
end
	
def get_http_via_threads
  threads = []
  threads &lt;&lt; get_http_thread(
    &#34;https://httpbin.org/delay/3?ex=1&#34;
  )
  threads &lt;&lt; get_http_thread(
    &#34;https://httpbin.org/delay/3?ex=2&#34;
  )
  threads &lt;&lt; get_http_thread(
    &#34;https://httpbin.org/delay/3?ex=3&#34;
  )
  threads &lt;&lt; get_http_thread(
    &#34;https://httpbin.org/delay/3?ex=4&#34;
  )
  threads.map(&amp;:value)
end
	
now = Time.now
get_http_via_threads
puts &#34;Thread runtime: #{Time.now - now}&#34;
```
This code is doing a few things:

1. It’s split into multiple methods to create a callstack (effectively, a backtrace). This callstack allows us to demonstrate that we maintain the position and state of each method call, even when we switch between threads.
2. It creates four threads and appends them to an array. After those threads are initialized they go into a ready state, available to be run by the thread scheduler.
3. It calls `value`on each thread. This blocks the program until each thread finishes and returns the last value returned from the thread. Once we block the main thread with `value`, the other threads have an immediate opportunity to start running. In this case we block until each thread finishes making an HTTP call.

```rb
# &gt; bundle exec ruby main.rb
# 
# Requesting https://httpbin.org/delay/3?ex=1...
# Requesting https://httpbin.org/delay/3?ex=2...
# Requesting https://httpbin.org/delay/3?ex=3...
# Requesting https://httpbin.org/delay/3?ex=4...
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:18:in `block in get_http_thread&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:18:in `block in get_http_thread&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:18:in `block in get_http_thread&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:18:in `block in get_http_thread&#39;
# Thread runtime: 3.340238554
```
Second we try it with fibers:

```rb
require &#34;net/http&#34;
require &#34;async&#34;
	
# Same #log_then_get
# Same #get
	
def get_http_fiber(url, responses)
  Fiber.schedule do
    responses &lt;&lt; log_then_get(URI(url))
  end
end
	
def get_http_via_fibers
  Fiber.set_scheduler(Async::Scheduler.new)
  responses = []
  responses &lt;&lt; get_http_fiber(
    &#34;https://httpbin.org/delay/3?ex=1&#34;, responses
  )
  responses &lt;&lt; get_http_fiber(
    &#34;https://httpbin.org/delay/3?ex=2&#34;, responses
  )
  responses &lt;&lt; get_http_fiber(
    &#34;https://httpbin.org/delay/3?ex=3&#34;, responses
  )
  responses &lt;&lt; get_http_fiber(
    &#34;https://httpbin.org/delay/3?ex=4&#34;, responses
  )
  responses
ensure
  Fiber.set_scheduler(nil)
end
	
now = Time.now
get_http_via_fibers
puts &#34;Fiber runtime: #{Time.now - now}&#34;
```
Same as before, this code is doing a few things:

1. We are split into multiple methods to create a callstack. Same as with threads, this callstack allows us to demonstrate that we maintain the position and state of each method call, even when we switch between fibers.
2. It schedules four Fibers. We append to an array inside the `Fiber.schedule` to get the result. Unlike threads, a scheduled fiber will start running its block immediately.
3. The fibers will coordinate between themselves and the current fiber. When we unset the FiberScheduler using `set_scheduler(nil)` in our `ensure`, the `Async::Scheduler` makes sure all fibers have finished running before returning.

This results in the following, similar backtrace:

```rb
# &gt; bundle exec ruby main.rb
# 
# Requesting https://httpbin.org/delay/3?ex=1...
# Requesting https://httpbin.org/delay/3?ex=2...
# Requesting https://httpbin.org/delay/3?ex=3...
# Requesting https://httpbin.org/delay/3?ex=4...
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:24:in `block in get_http_fiber&#39;
# .../async-2.6.3/lib/async/task.rb:160:in `block in run&#39;
# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:24:in `block in get_http_fiber&#39;
# .../async-2.6.3/lib/async/task.rb:160:in `block in run&#39;
# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:24:in `block in get_http_fiber&#39;
# .../async-2.6.3/lib/async/task.rb:160:in `block in run&#39;
# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&#39;
# main.rb:12:in `get&#39;
# main.rb:7:in `log_then_get&#39;
# main.rb:24:in `block in get_http_fiber&#39;
# .../async-2.6.3/lib/async/task.rb:160:in `block in run&#39;
# .../async-2.6.3/lib/async/task.rb:330:in `block in schedule&#39;
# Fiber runtime: 3.291355669
```
If we visualize what’s happening, we see how things are getting coordinated. It’s nearly identical between the two:

Threads:

![](https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio.png)

Fibers:

![](https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-1.png)

👆🏼This diagram does look pretty sequential - that’s odd huh?

And here is what happens once we are waiting for a response:

Threads:

![](https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-3.png)

Fibers:

![](https://cdn.uploads.micro.blog/98548/2024/colorless-threadsfibers-diagram.drawio-2.png)

👆🏼Oh, that seems better!

Running these HTTP methods is the same if I run it with no threads/fibers, 4 threads/fibers, or 400 threads/fibers. The code never changes, the execution context is all we need to start operating asynchronously. _Something_ is coordinating for us behind-the-scenes, and we’re able to handle our HTTP calls in parallel 🙌🏼.

This lines up with our timing. In each example we ran 4 HTTP calls, all guaranteed to block for around 3 seconds. Despite that, each example finished in roughly 3 seconds, rather than the 12 seconds it would take if they had run sequentially.

```rb
# Fiber runtime: 3.291355669
# Thread runtime: 3.340238554
```
To paraphrase Bob Nystrom’s article one last time, we can apply his original points about Go almost perfectly to Ruby threads/fibers:

&gt; As soon as you do any IO operation, it just parks that thread/fiber and resumes any other one that isn’t blocked on IO. 
&gt; 
&gt; If you look at the IO operations in the standard library, they seem synchronous. In other words, they just do work and then return a result when they are done. But it&#39;s not that they&#39;re synchronous in the sense that it would mean in JavaScript. Other Ruby code can run while one of these operations is pending. It&#39;s that Ruby has eliminated the distinction between synchronous and asynchronous code.

As we saw in the initial examples of colorless method calls - it’s not purely IO that parks and resumes. It’s the majority of blocking operations. Here’s another example:

```rb
# Threads
threads = [
  Thread.new { sleep 5 },
  Thread.new { `ruby -v` },
  Thread.new { 
    noop = fork {}
    Process.wait noop
  }
]
	
results = threads.map(&amp;:value)
	
# Fibers
Fiber.set_scheduler(Async::Scheduler.new)
fibers = [
  Async { sleep 5 },
  Async { `ruby -v` },
  Async { 
    noop = fork {}
    Process.wait noop
  }
]
	
results = fibers.map(&amp;:wait)
```
We [`fork`](https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-fork) a ruby process, run a command line script to get the current ruby version using `ruby -v` and [`sleep`](https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-sleep) - all in parallel.

&lt;h3 id=&#34;async-aside&#34;&gt;A quick aside on Async&lt;/h3&gt;

Manually setting the FiberScheduler and manually unsetting it is just to more clearly demonstrate the interface. But you would normally run the block format:

```rb
Async do
  fibers &lt;&lt; get_http_fiber #...
end
```
You’d also use the `Async` helpers instead of `Fiber.schedule`, which have a more robust available API:

```rb
def get_http_fiber(url)
  Async do
    log_then_get(URI(url), Fiber.current)
  end
end
```
&lt;h3 id=&#34;digging-deep&#34;&gt;Digging deep&lt;/h3&gt;

My toy examples aside, this is one of the things that makes threaded/fibered web servers like [Puma](https://github.com/puma/puma)/[Falcon](https://github.com/socketry/falcon) and threaded job servers like [Sidekiq](https://github.com/sidekiq/sidekiq)/[GoodJob](https://github.com/bensheldon/good_job) so powerful - parallelizing blocking operations. Every time one of your job or web server threads/fibers reads a file, queries a database, makes an HTTP call, or waits for a shared resource, Ruby parks the thread/fiber and another thread/fiber is able to take over. Depending on the type of workload you can have dozens/hundreds/thousands of threads/fibers running concurrently, and blocking in parallel. We’ll push some limits on that later in the series to see where that might break down, and how we can keep scaling past those limits!

Now that we understand what colorless Ruby programming means and how it works - we’re left with some questions.

- Why would we need or want both Threads and Fibers?
- Why do some languages choose to introduce color to their languages? Are there upsides to color?
- What is the deal with those “VM locks”?
- What does the “OS Kernel” have to do with parallel IO?
- What’s a Reactor?
- Is there a best concurrency option?
- Are there abstractions you should be using?
- What’s the point of concurrency without parallelism?
- We also keep talking about parallelizing blocking operations - why don’t threads and fibers already allow you to parallelize everything?

To answer these questions (and more), we’re going to dig pretty far into the ruby runtime to better understand Ruby threads, fibers and other forms of concurrency in Ruby. 

Let’s start with the OG, Threads, in “The Thread API: Concurrent, colorless Ruby”. More soon! 👋🏼 

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/thread.png&#34; width=&#34;25%&#34; height=&#34;25%&#34; alt=&#34;&#34;&gt;

&lt;h3 id=&#34;ruby-history-corner&#34;&gt;PS: A historical sidenote - Ruby had its own callback phase&lt;/h3&gt;

It was 2009. The Black Eyed Peas were on top of the charts with “Boom Boom Pow”. Microsoft had just launched Windows 7. Avatar was taking off in the box office. And of course most important of all, node.js was released (😉). During a brief ensuing insanity, everyone thought it would devour the world of web development.

Like any new option, it ultimately took its place _alongside_ other tools, but it had a big influence in the popularity of the [reactor pattern](https://en.wikipedia.org/wiki/Reactor_pattern).

The reactor pattern is an event loop that registers events with the operating system and efficiently multiplexes them. It was a way of solving the [C10K problem](https://en.m.wikipedia.org/wiki/C10k_problem) - a solution for serving 10 thousand+ clients from a single server.

To get a great insight into the Ruby concurrency landscape at that time, and why the node.js model seemed so appealing, see this post from Yehuda Katz in 2010 - [https://yehudakatz.com/2010/08/14/threads-in-ruby-enough-already/](https://yehudakatz.com/2010/08/14/threads-in-ruby-enough-already/). In summary:

- Early Rails was not thread-safe so you could only scale with Processes
- Once Rails was “thread-safe”, it started with a giant mutex around the entire framework that meant only one thread could do anything at a time, including blocking operations 
- Even threaded servers at the time would often wrap things poorly and too broadly in mutexes
- The most popular database for Ruby/Rails at the time, MySQL, had a Ruby driver which did not release the thread on blocking operations
- Ruby 1.9 was the first Ruby to map to real operating system threads and loads of people still worked in Ruby 1.8
- He doesn’t mention it specifically, but the gem landscape was also pretty scary in terms of thread safety because many people did not understand or worry about it prior to that point. Things are definitely better now, though I’d recommend reviewing my [Your Ruby Programs are always multi-threaded](https://jpcamara.com/2024/06/04/your-ruby-programs.html) posts, and specifically [tips for auditing gems](https://jpcamara.com/2024/06/23/your-ruby-programs.html#tips-for-gems)

It was a concurrency mess.

No wonder node.js seemed like a magical concurrency silver bullet. A single process handling hundreds to thousands of users vs dozens of heavyweight processes handling a paltry comparative amount.

In the Ruby world the reactor pattern was served by a tool called EventMachine. We’ll talk a bit deeper about EventMachine in the “Concurrent, Colorless Ruby” fiber post later, but it was solely callback based.

```rb
EventMachine.run do
  redis = EM::Hiredis.connect
  http = EM::HttpRequest.new(
    &#34;http://google.com/&#34;
  ).get query: { &#34;keyname&#34; =&gt; &#34;value&#34; }
	
  http.errback { EventMachine.stop }
  http.callback {
    parse(http.response).each |link| do
      req = EM::HttpRequest.new(link).get
      req.callback {
        body = req.response
        redis.set(link, body).callback {
          # and on and on
        }
      }
    end
  }
	
  EventMachine.stop
end
```
You can see how messy coding like this becomes, just like the original callback model we presented in JavaScript.

EventMachine was a powerful scaling option, and people still maintain EventMachine-based code today, but it was at odds with the normal flow of Ruby code. Ruby could have moved to red and blue style calls like some other languages to clean that up, but instead superseded those Reactor efforts with the FiberScheduler.

[^1]:	The programming language, not the game 🙂

[^2]:	Turns out prime did a reaction video on it as well https://youtu.be/MoKe4zvtNzA

[^3]:	In case this seems like a confusing and bizarre title, it’s a play on “the road to hell is paved with good intention” [https://en.wikipedia.org/wiki/The\_road\_to\_hell\_is\_paved\_with\_good\_intentions](https://en.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions) 🌈 

[^4]:	I’m sure this is not exhaustive, the list is just illustrative and represents some popular languages. You don’t need to defend your language of choice 😉

[^5]:	They stand for Ruby Actors so it is a Ruby form of the [actor](https://en.m.wikipedia.org/wiki/Actor_model) model. It works similarly to [JavaScript WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) in that they are isolated and require strict rules for sharing to enforce thread safety

[^6]:	For now, as we’ll discuss later

[^7]:	As with most things, there’s even more nuance to the layering and we’ll touch on it a bit later

[^8]:	But have evolved significantly since then. This also required community support in addition to language support

[^9]:	It’s more nuanced than this, and this is more for illustrative puposes. Even though they don’t run Ruby code in parallel, threads and fibers may still be tied to separate CPUs.
</source:markdown>
    </item>
    
    <item>
      <title>Consistent, request-local state</title>
      <link>https://jpcamara.com/2024/07/01/consistent-requestlocal-state.html</link>
      <pubDate>Mon, 01 Jul 2024 20:11:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/07/01/consistent-requestlocal-state.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/image-6-30-24-6-25pm.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Consistent, request-local state&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “Consistent, request-local state”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Rather than a full entry in the series, this is a brief-ish&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; concurrency mini - a follow up to &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;recap&#34;&gt;Recap&lt;/h2&gt;
&lt;p&gt;In &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#sharing-state-with-fibers&#34;&gt;Sharing state with fibers&lt;/a&gt;, we walked through a scenario where you had fiber-local state using &lt;code&gt;Thread.current[]&lt;/code&gt;, and it was not available inside nested Fibers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This is #&amp;lt;User id=123&amp;gt;
Thread.current[:app_user]
Async do # equivalent to Fiber.new
  # This is nil
  Thread.current[:app_user]
  params[:files].each do |file|
    Async { # Fiber.new
      # Still nil
      Thread.current[:app_user]
      # Upload to S3 fails...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We had &lt;a href=&#34;https://softwareengineering.stackexchange.com/questions/420986/what-is-the-meaning-of-fan-out&#34;&gt;fanned out&lt;/a&gt; user uploads to take advantage of parallelized IO. The problem was we tried to use fiber-local state and that gets reset in new Fibers.&lt;/p&gt;
&lt;p&gt;To solve it, we tried true thread-local state with &lt;code&gt;thread_variable_get&lt;/code&gt; and &lt;code&gt;thread_variable_set&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This is #&amp;lt;User id=123&amp;gt;
Thread.current.thread_variable_get(:app_user)
Async do
  # Still #&amp;lt;User id=123&amp;gt;
 Thread.current.thread_variable_get(:app_user)
  params[:files].each do |file|
    Async {
      # Still #&amp;lt;User id=123&amp;gt;!
      Thread.current.thread_variable_get(:app_user)
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a regular threaded server, that’s ok. It’s exactly how &lt;code&gt;CurrentAttributes&lt;/code&gt; would function by default:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext &amp;lt; ActiveSupport::CurrentAttributes
  attribute :user, :locale

  def user=(user)
    super
    self.locale = user.locale
  end
end

# This is #&amp;lt;User id=123&amp;gt;
AppContext.user
Async do
  # Still #&amp;lt;User id=123&amp;gt;
  AppContext.user
  params[:files].each do |file|
    Async {
      # Still #&amp;lt;User id=123&amp;gt;!
      AppContext.user
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s because &lt;code&gt;CurrentAttributes&lt;/code&gt; works off of an &lt;code&gt;isolation_level&lt;/code&gt;. It internally uses &lt;code&gt;ActiveSupport::IsolatedExecutionContext&lt;/code&gt;, which is an abstraction on top of Thread/Fiber-local state. It has two possible &lt;code&gt;isolation_level&lt;/code&gt;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:thread&lt;/code&gt; - This is the default, and works exactly like our code using &lt;code&gt;thread_variable_get&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:fiber&lt;/code&gt; - This is the other available option, and works exactly like our code using &lt;code&gt;Thread.current[]&lt;/code&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ActiveSupport&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;IsolatedExecutionContext&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;isolation_level &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:thread&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# or :fiber&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The reason this abstraction matters is because of the introduction of Fiber-based servers.&lt;/p&gt;
&lt;h2 id=&#34;careful-with-the-falcons&#34;&gt;Careful with the Falcons&lt;/h2&gt;
&lt;p&gt;If you look into the &lt;code&gt;async&lt;/code&gt; ecosystem, pretty quickly you’ll find a full-featured web server called &lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;&lt;code&gt;falcon&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://raw.githubusercontent.com/socketry/falcon/main/assets/logo.webp&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Falcon is a multi-process, multi-fiber rack-compatible HTTP server built on top of async, async-container and async-http. Each request is executed within a lightweight fiber and can block on up-stream requests without stalling the entire server process. Falcon supports HTTP/1 and HTTP/2 natively.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;falcon&lt;/code&gt; is Fiber-based, meaning each request is run in a new Fiber, rather than a new Thread. Effectively, as it accepts each socket connection it hands it off to a new Fiber:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Async do # Fiber.new
  socket.accept do
    # Instead of Thread.new or a Thread pool
    Async {} # Fiber.new
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, &lt;code&gt;falcon&lt;/code&gt; will set the &lt;code&gt;IsolatedExecutionContext.isolation_level&lt;/code&gt; for you to use Fibers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ActiveSupport::IsolatedExecutionContext.isolation_level = :fiber
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which means it localizes its state at the Fiber level. We’ve got our original problem again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This is #&amp;lt;User id=123&amp;gt;
AppContext.user
Async do
  # This is nil
  AppContext.user
  params[:files].each do |file|
    Async {
      # Still nil
      AppContext.user
      # Upload to S3 fails...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What would happen if you set the &lt;code&gt;isolation_level&lt;/code&gt; back to &lt;code&gt;:thread&lt;/code&gt;?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ActiveSupportIsolatedExecutionContext&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;isolation_level &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;:thread&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-rb&#34; data-lang=&#34;rb&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ImagesController&lt;/span&gt;
  before_action &lt;span style=&#34;color:#e6db74&#34;&gt;:set_user&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# May be a user from a different fiber!&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;# They all live in the same Thread, so they share&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;#   thread-local state&lt;/span&gt;
      &lt;span style=&#34;color:#66d9ef&#34;&gt;AppContext&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;app_user
      params&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:files&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;].&lt;/span&gt;each &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;file&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;
        &lt;span style=&#34;color:#66d9ef&#34;&gt;Async&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;S3Upload&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new(file)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;call }
      &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-consistent-state.drawio.png&#34; width=&#34;539&#34; height=&#34;299&#34; alt=&#34;&#34;&gt;
&lt;p&gt;Bad things. We start running into issues with shared global state again, because all of our fibers share the same Thread! We &lt;em&gt;need&lt;/em&gt; to be Fiber-local when running on a Fiber-based server.&lt;/p&gt;
&lt;p&gt;If Thread-locals are not safe on a Fiber-based server, how can we safely share this state?&lt;/p&gt;
&lt;h2 id=&#34;resetting-context&#34;&gt;Resetting context&lt;/h2&gt;
&lt;p&gt;One less than ideal option is to just reset the state in each nested Fiber:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This is #&amp;lt;User id=123&amp;gt;
user = AppContext.user
Async do
  AppContext.user = user
  # This is #&amp;lt;User id=123&amp;gt;
  AppContext.user
  params[:files].each do |file|
    Async {
      AppContext.user = user
      # This is #&amp;lt;User id=123&amp;gt;
      AppContext.user
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don’t &lt;em&gt;have&lt;/em&gt; to set it on every level - just the level that actually needs it. But you probably want to set it on every level to not surprise yourself later.&lt;/p&gt;
&lt;p&gt;This &lt;em&gt;works&lt;/em&gt;, but it&amp;rsquo;s manual and clunky.&lt;/p&gt;
&lt;h2 id=&#34;fiber-storage&#34;&gt;Fiber storage&lt;/h2&gt;
&lt;p&gt;There’s a new option available as of Ruby 3.2.&lt;/p&gt;
&lt;p&gt;It’s a new kind of “local”, called &lt;a href=&#34;https://docs.ruby-lang.org/en/master/Fiber.html#method-c-5B-5D&#34;&gt;Fiber storage&lt;/a&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, and it was designed to help with precisely this type of issue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fiber[:app_user] = user

# This is #&amp;lt;User id=123&amp;gt;
Fiber[:app_user]
Async do
  # This is #&amp;lt;User id=123&amp;gt;
  Fiber[:app_user]
  params[:files].each do |file|
    Async {
      # This is #&amp;lt;User id=123&amp;gt;
      Fiber[:app_user]
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fiber storage is a mechanism for inheriting state from a Fiber to any Ractors/Threads/Fibers started from that scope.&lt;/p&gt;
&lt;p&gt;The best term for this type of storage is “request-local”. The definition of “request” i’m using here is very loose - it just means the context of some particular slice of execution. That might mean a web page request, a background job run, a cron job, etc.&lt;/p&gt;
&lt;p&gt;So we &lt;em&gt;could&lt;/em&gt; create a new &lt;code&gt;AppContext&lt;/code&gt; using this approach:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  class &amp;lt;&amp;lt; self
    def user
      Fiber[:app_user]
    end

    def user=(user)
      Fiber[:app_user] = user
      self.locale = user.locale
    end

    def locale
      Fiber[:app_locale]
    end

    def locale=(locale)
      Fiber[:app_locale] = locale
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this new &lt;code&gt;AppContext&lt;/code&gt;, things are working again!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Async do
  # This is #&amp;lt;User id=123&amp;gt;
  AppContext.user
  params[:files].each do |file|
    Async {
      # This is #&amp;lt;User id=123&amp;gt;
      AppContext.user
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even more, this works for Threads too:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thread.new do
  # This is #&amp;lt;User id=123&amp;gt;
  AppContext.user
  params[:files].each do |file|
    Thread.new {
      # This is #&amp;lt;User id=123&amp;gt;
      AppContext.user
      # Upload to S3...
    }
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;And&lt;/em&gt; Ractors:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ractor.new do
  # This is #&amp;lt;User id=123&amp;gt;
  AppContext.user
  Thread.new do
    # This is #&amp;lt;User id=123&amp;gt;
    AppContext.user
  end.join
end
&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-inherited-fiber-storage.drawio.png&#34; width=&#34;599&#34; height=&#34;359&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 If it works for Ractors, Threads and Fibers, why call it “Fiber storage”?&lt;/p&gt;
&lt;p&gt;Fibers are the innermost level of concurrency in Ruby (Process -&amp;gt; Ractor -&amp;gt; Thread -&amp;gt; Fiber). Since all code operates within a Fiber scope, it makes sense to have it be the storage layer for local state.&lt;/p&gt;
&lt;p&gt;Every time you create a new Ractor/Thread, a new Fiber is created within it. Which means the thing actually inheriting the Fiber storage is the Fiber that exists within that new context, not the Ractor/Thread itself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An ideal future state of libraries is that everything handling “request” type state would move to using Fiber storage (CurrentAttributes, RequestStore, and friends).&lt;/p&gt;
&lt;h2 id=&#34;caveats&#34;&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Like with any new approach in an existing, robust ecosystem, there are some caveats to consider.&lt;/p&gt;
&lt;h3 id=&#34;it-requires-framework-buy-in&#34;&gt;It requires framework buy-in&lt;/h3&gt;
&lt;p&gt;Every request needs to be wrapped in a new Fiber with fresh storage, so it doesn’t accidentally inherit cross-request storage:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# In a library like Puma/Sidekiq/Pitchfork
def new_request
  Fiber.new(storage: nil) do
    # fresh storage for this request
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Falcon already handles this for you. But no other framework does (yet). If you aren’t using Falcon, this may not be a viable option for you yet.&lt;/p&gt;
&lt;h3 id=&#34;each-layer-inherits-a-copy&#34;&gt;Each layer inherits a copy&lt;/h3&gt;
&lt;p&gt;Each layer copies the keys and values from the current Fiber storage scope into a new hash. You can’t override entries from the parent scope, so overrides only impact lower levels.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Fiber[:value] = 1
Fiber.new do
  Fiber[:value] = 2
end
Fiber[:value] # =&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is intentional, but just something to keep in mind.&lt;/p&gt;
&lt;h3 id=&#34;some-things-arent-meant-to-be-shared&#34;&gt;Some things aren’t meant to be shared&lt;/h3&gt;
&lt;p&gt;As you fan out there are things you likely want to share, like the current user. But there are things you don’t want to, or can’t - like database connections or file handles.&lt;/p&gt;
&lt;p&gt;That means existing solutions that try to share concepts (like &lt;code&gt;CurrentAttributes&lt;/code&gt; and &lt;code&gt;IsolatedExecutionContext&lt;/code&gt;) will need to split their approach. For a concept like &lt;code&gt;CurrentAttributes&lt;/code&gt; you want inherited Fiber storage. For a database connection, you want things fiber-local so each Fiber has its own connection.&lt;/p&gt;
&lt;h2 id=&#34;takeaways&#34;&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use an abstraction, like &lt;code&gt;CurrentAttributes&lt;/code&gt;. Over time, these abstractions should catch up with available options like Fiber storage and you’ll reap the benefits&lt;/li&gt;
&lt;li&gt;If you’re using Falcon, Fiber storage is a great option for sharing state across child Fibers/Threads/Ractors&lt;/li&gt;
&lt;li&gt;If you’re not using Falcon, there’s still some work for the community to catch up with this new option, so use it carefully or avoid it for now&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;onward&#34;&gt;Onward!&lt;/h2&gt;
&lt;p&gt;Ruby 3+ has introduced interesting new options for concurrency like the FiberScheduler/Fiber storage and thanks to many community efforts, things are starting to catch up. But there’s still work to be done and new abstractions needed to handle things seamlessly.&lt;/p&gt;
&lt;p&gt;Thanks to Samuel Williams&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; (&lt;a href=&#34;https://x.com/ioquatix&#34;&gt;@ioquatix&lt;/a&gt;, author of Async, Falcon, and the FiberScheduler) for suggesting the clarification and reviewing this post!&lt;/p&gt;
&lt;p&gt;Ok, &lt;em&gt;now&lt;/em&gt; we’ll be heading over to “Ruby methods are colorless”. More soon 👋🏼.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Some readers may be thinking “oh thank god, finally a brief one” 😆&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It works the same but it’s not actually using that internally. It monkey-patches Thread and adds a new &lt;code&gt;attr_accessor&lt;/code&gt;. If someone cares, I could talk about it more!&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Also not actually using that internally, same as with &lt;code&gt;:thread&lt;/code&gt;. It monkey-patches Fiber and adds a new &lt;code&gt;attr_accessor&lt;/code&gt;. If someone cares, I could talk about it more as well!&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There’s a bit more context in the original proposal &lt;a href=&#34;https://bugs.ruby-lang.org/issues/19078&#34;&gt;https://bugs.ruby-lang.org/issues/19078&lt;/a&gt; and in the docs for creating new Fibers &lt;a href=&#34;https://docs.ruby-lang.org/en/master/Fiber.html#method-c-new&#34;&gt;https://docs.ruby-lang.org/en/master/Fiber.html#method-c-new&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Aka Mr Async, aka The Fiber Fella&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/image-6-30-24-6-25pm.png)

&gt; 👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt;   - Consistent, request-local state
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “Consistent, request-local state”. I’ll update the links as each part is released, and include these links in each post.

Rather than a full entry in the series, this is a brief-ish[^1] concurrency mini - a follow up to [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html).

## Recap
In [Sharing state with fibers](https://jpcamara.com/2024/06/23/your-ruby-programs.html#sharing-state-with-fibers), we walked through a scenario where you had fiber-local state using `Thread.current[]`, and it was not available inside nested Fibers.

	# This is #&lt;User id=123&gt;
	Thread.current[:app_user]
	Async do # equivalent to Fiber.new
	  # This is nil
	  Thread.current[:app_user]
	  params[:files].each do |file|
	    Async { # Fiber.new
	      # Still nil
	      Thread.current[:app_user]
	      # Upload to S3 fails...
	    }
	  end
	end

We had [fanned out](https://softwareengineering.stackexchange.com/questions/420986/what-is-the-meaning-of-fan-out) user uploads to take advantage of parallelized IO. The problem was we tried to use fiber-local state and that gets reset in new Fibers.

To solve it, we tried true thread-local state with `thread_variable_get` and `thread_variable_set`. 

	# This is #&lt;User id=123&gt;
	Thread.current.thread_variable_get(:app_user)
	Async do
	  # Still #&lt;User id=123&gt;
	 Thread.current.thread_variable_get(:app_user)
	  params[:files].each do |file|
	    Async {
	      # Still #&lt;User id=123&gt;!
	      Thread.current.thread_variable_get(:app_user)
	      # Upload to S3...
	    }
	  end
	end

On a regular threaded server, that’s ok. It’s exactly how `CurrentAttributes` would function by default:

	class AppContext &lt; ActiveSupport::CurrentAttributes
	  attribute :user, :locale
	
	  def user=(user)
	    super
	    self.locale = user.locale
	  end
	end

	# This is #&lt;User id=123&gt;
	AppContext.user
	Async do
	  # Still #&lt;User id=123&gt;
	  AppContext.user
	  params[:files].each do |file|
	    Async {
	      # Still #&lt;User id=123&gt;!
	      AppContext.user
	      # Upload to S3...
	    }
	  end
	end

That’s because `CurrentAttributes` works off of an `isolation_level`. It internally uses `ActiveSupport::IsolatedExecutionContext`, which is an abstraction on top of Thread/Fiber-local state. It has two possible `isolation_level`s:

- `:thread` - This is the default, and works exactly like our code using `thread_variable_get`[^2]
- `:fiber` - This is the other available option, and works exactly like our code using `Thread.current[]`[^3]

```rb
ActiveSupport::IsolatedExecutionContext.isolation_level = :thread # or :fiber
```
The reason this abstraction matters is because of the introduction of Fiber-based servers.

## Careful with the Falcons
If you look into the `async` ecosystem, pretty quickly you’ll find a full-featured web server called [`falcon`](https://github.com/socketry/falcon).

![](https://raw.githubusercontent.com/socketry/falcon/main/assets/logo.webp)

&gt; Falcon is a multi-process, multi-fiber rack-compatible HTTP server built on top of async, async-container and async-http. Each request is executed within a lightweight fiber and can block on up-stream requests without stalling the entire server process. Falcon supports HTTP/1 and HTTP/2 natively.

`falcon` is Fiber-based, meaning each request is run in a new Fiber, rather than a new Thread. Effectively, as it accepts each socket connection it hands it off to a new Fiber:

	Async do # Fiber.new
	  socket.accept do
	    # Instead of Thread.new or a Thread pool
	    Async {} # Fiber.new
	  end
	end

By default, `falcon` will set the `IsolatedExecutionContext.isolation_level` for you to use Fibers:

	ActiveSupport::IsolatedExecutionContext.isolation_level = :fiber

Which means it localizes its state at the Fiber level. We’ve got our original problem again:

	# This is #&lt;User id=123&gt;
	AppContext.user
	Async do
	  # This is nil
	  AppContext.user
	  params[:files].each do |file|
	    Async {
	      # Still nil
	      AppContext.user
	      # Upload to S3 fails...
	    }
	  end
	end

What would happen if you set the `isolation_level` back to `:thread`?

```rb
ActiveSupportIsolatedExecutionContext.isolation_level = :thread
```
```rb
class ImagesController
  before_action :set_user
	
  def create
    Async do
      # May be a user from a different fiber!
      # They all live in the same Thread, so they share
      #   thread-local state
      AppContext.app_user
      params[:files].each do |file|
        Async { S3Upload.new(file).call }
      end
    end
  end
end
```
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-consistent-state.drawio.png&#34; width=&#34;539&#34; height=&#34;299&#34; alt=&#34;&#34;&gt;

Bad things. We start running into issues with shared global state again, because all of our fibers share the same Thread! We _need_ to be Fiber-local when running on a Fiber-based server.

If Thread-locals are not safe on a Fiber-based server, how can we safely share this state?

## Resetting context
One less than ideal option is to just reset the state in each nested Fiber:

	# This is #&lt;User id=123&gt;
	user = AppContext.user
	Async do
	  AppContext.user = user
	  # This is #&lt;User id=123&gt;
	  AppContext.user
	  params[:files].each do |file|
	    Async {
	      AppContext.user = user
	      # This is #&lt;User id=123&gt;
	      AppContext.user
	      # Upload to S3...
	    }
	  end
	end

You don’t _have_ to set it on every level - just the level that actually needs it. But you probably want to set it on every level to not surprise yourself later.

This _works_, but it&#39;s manual and clunky.

## Fiber storage
There’s a new option available as of Ruby 3.2.

It’s a new kind of “local”, called [Fiber storage](https://docs.ruby-lang.org/en/master/Fiber.html#method-c-5B-5D)[^4], and it was designed to help with precisely this type of issue:

	Fiber[:app_user] = user
	
	# This is #&lt;User id=123&gt;
	Fiber[:app_user]
	Async do
	  # This is #&lt;User id=123&gt;
	  Fiber[:app_user]
	  params[:files].each do |file|
	    Async {
	      # This is #&lt;User id=123&gt;
	      Fiber[:app_user]
	      # Upload to S3...
	    }
	  end
	end

Fiber storage is a mechanism for inheriting state from a Fiber to any Ractors/Threads/Fibers started from that scope.

The best term for this type of storage is “request-local”. The definition of “request” i’m using here is very loose - it just means the context of some particular slice of execution. That might mean a web page request, a background job run, a cron job, etc.

So we _could_ create a new `AppContext` using this approach:

	class AppContext
	  class &lt;&lt; self
	    def user
	      Fiber[:app_user]
	    end
	
	    def user=(user)
	      Fiber[:app_user] = user
	      self.locale = user.locale
	    end
	
	    def locale
	      Fiber[:app_locale]
	    end
	
	    def locale=(locale)
	      Fiber[:app_locale] = locale
	    end
	  end
	end

With this new `AppContext`, things are working again!

	Async do
	  # This is #&lt;User id=123&gt;
	  AppContext.user
	  params[:files].each do |file|
	    Async {
	      # This is #&lt;User id=123&gt;
	      AppContext.user
	      # Upload to S3...
	    }
	  end
	end

Even more, this works for Threads too:

	Thread.new do
	  # This is #&lt;User id=123&gt;
	  AppContext.user
	  params[:files].each do |file|
	    Thread.new {
	      # This is #&lt;User id=123&gt;
	      AppContext.user
	      # Upload to S3...
	    }
	  end
	end

_And_ Ractors:

	Ractor.new do
	  # This is #&lt;User id=123&gt;
	  AppContext.user
	  Thread.new do
	    # This is #&lt;User id=123&gt;
	    AppContext.user
	  end.join
	end

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-inherited-fiber-storage.drawio.png&#34; width=&#34;599&#34; height=&#34;359&#34; alt=&#34;&#34;&gt;

&gt; 📝 If it works for Ractors, Threads and Fibers, why call it “Fiber storage”? 
&gt; 
&gt; Fibers are the innermost level of concurrency in Ruby (Process -\&gt; Ractor -\&gt; Thread -\&gt; Fiber). Since all code operates within a Fiber scope, it makes sense to have it be the storage layer for local state. 
&gt; 
&gt; Every time you create a new Ractor/Thread, a new Fiber is created within it. Which means the thing actually inheriting the Fiber storage is the Fiber that exists within that new context, not the Ractor/Thread itself.

An ideal future state of libraries is that everything handling “request” type state would move to using Fiber storage (CurrentAttributes, RequestStore, and friends).

## Caveats
Like with any new approach in an existing, robust ecosystem, there are some caveats to consider.

### It requires framework buy-in
Every request needs to be wrapped in a new Fiber with fresh storage, so it doesn’t accidentally inherit cross-request storage:

	# In a library like Puma/Sidekiq/Pitchfork
	def new_request
	  Fiber.new(storage: nil) do
	    # fresh storage for this request
	  end
	end

Falcon already handles this for you. But no other framework does (yet). If you aren’t using Falcon, this may not be a viable option for you yet.

### Each layer inherits a copy
Each layer copies the keys and values from the current Fiber storage scope into a new hash. You can’t override entries from the parent scope, so overrides only impact lower levels.

	Fiber[:value] = 1
	Fiber.new do
	  Fiber[:value] = 2
	end
	Fiber[:value] # =&gt; 1

This is intentional, but just something to keep in mind.

### Some things aren’t meant to be shared
As you fan out there are things you likely want to share, like the current user. But there are things you don’t want to, or can’t - like database connections or file handles.

That means existing solutions that try to share concepts (like `CurrentAttributes` and `IsolatedExecutionContext`) will need to split their approach. For a concept like `CurrentAttributes` you want inherited Fiber storage. For a database connection, you want things fiber-local so each Fiber has its own connection.

## Takeaways
- Use an abstraction, like `CurrentAttributes`. Over time, these abstractions should catch up with available options like Fiber storage and you’ll reap the benefits
- If you’re using Falcon, Fiber storage is a great option for sharing state across child Fibers/Threads/Ractors
- If you’re not using Falcon, there’s still some work for the community to catch up with this new option, so use it carefully or avoid it for now

## Onward!
Ruby 3+ has introduced interesting new options for concurrency like the FiberScheduler/Fiber storage and thanks to many community efforts, things are starting to catch up. But there’s still work to be done and new abstractions needed to handle things seamlessly.

Thanks to Samuel Williams[^5] ([@ioquatix](https://x.com/ioquatix), author of Async, Falcon, and the FiberScheduler) for suggesting the clarification and reviewing this post!

Ok, _now_ we’ll be heading over to “Ruby methods are colorless”. More soon 👋🏼.

[^1]:	Some readers may be thinking “oh thank god, finally a brief one” 😆 

[^2]:	It works the same but it’s not actually using that internally. It monkey-patches Thread and adds a new `attr_accessor`. If someone cares, I could talk about it more!

[^3]:	Also not actually using that internally, same as with `:thread`. It monkey-patches Fiber and adds a new `attr_accessor`. If someone cares, I could talk about it more as well!

[^4]:	There’s a bit more context in the original proposal [https://bugs.ruby-lang.org/issues/19078](https://bugs.ruby-lang.org/issues/19078) and in the docs for creating new Fibers [https://docs.ruby-lang.org/en/master/Fiber.html#method-c-new](https://docs.ruby-lang.org/en/master/Fiber.html#method-c-new)

[^5]:	Aka Mr Async, aka The Fiber Fella
</source:markdown>
    </item>
    
    <item>
      <title>Your Ruby programs are always multi-threaded: Part 2</title>
      <link>https://jpcamara.com/2024/06/26/your-ruby-programs-are-always.html</link>
      <pubDate>Wed, 26 Jun 2024 03:57:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/06/26/your-ruby-programs-are-always.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/2ffdf3bdb5.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Your Ruby programs are always multi-threaded: Part 2
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “Your Ruby programs are always multi-threaded: Part 2”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#its-all-threaded&#34;&gt;It’s all threaded&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#how-threaded-is-it&#34;&gt;Ok but how threaded is it &lt;em&gt;really&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#threading-mistakes&#34;&gt;Threading mistakes&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#sharing-ivars&#34;&gt;Sharing module/class instance variables&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs&#34;&gt;Heisenbugs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#ruby-internals&#34;&gt;Ruby internals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#back-in-reality&#34;&gt;Back in reality…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#copying-state&#34;&gt;Copying state to instances&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#back-in-reality-two&#34;&gt;Back in reality… part two&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3a. &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#cleaning-up&#34;&gt;Cleaning up thread state&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#using-sidekiq&#34;&gt;If you’re using Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html#using-activejob&#34;&gt;If you’re using ActiveJob&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Part 2&lt;/strong&gt;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;3b. &lt;a href=&#34;#sharing-state-with-fibers&#34;&gt;Sharing state with fibers&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#wild-fiber&#34;&gt;A wild Fiber appeared!&lt;/a&gt; 🌾&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ruby-concurrency-layers&#34;&gt;Ruby concurrency has layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#actual-thread-locals&#34;&gt;Actual thread-locals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#current-attributes&#34;&gt;CurrentAttributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#request-store&#34;&gt;RequestStore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#back-in-reality-three&#34;&gt;Back in reality… part three&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;&lt;a href=&#34;#reusing-objects&#34;&gt;Reusing objects&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;&lt;a href=&#34;#race-conditions&#34;&gt;Race conditions&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#threading-concepts&#34;&gt;Threading concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#check-then-act&#34;&gt;Memoization / check-then-act&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#read-modify-write&#34;&gt;read-modify-write&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#coordinating-jobs&#34;&gt;Coordinating jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#read-modify-write-for-days&#34;&gt;read-modify-write shows up in many ways&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#parting-thoughts&#34;&gt;Parting thoughts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#tips-for-gems&#34;&gt;Tips for auditing gems&lt;/a&gt; 💎&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#using-falcon&#34;&gt;I use Falcon - Fibers are safe from this right?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#using-jruby-truffle&#34;&gt;I use JRuby/TruffleRuby…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#reforking&#34;&gt;Preforking / Reforking servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#mutexes-for-globals&#34;&gt;What about using mutexes for globals?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#detect-and-correct&#34;&gt;It’s not doom and gloom, it’s detect and correct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#takeaways&#34;&gt;Takeaways&lt;/a&gt; 🥡&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you haven’t read “&lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/a&gt;”, you’re better off starting there. You’ll still get value of this post, but you’ll get the full context by reading both. All of the Table of Contents links under &lt;strong&gt;Part 1&lt;/strong&gt; link back to that previous article &lt;a href=&#34;https://jpcamara.com/2024/06/04/your-ruby-programs.html&#34;&gt;↩️&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;sharing-state-with-fibers&#34;&gt;3b. Sharing state with fibers&lt;/h3&gt;
&lt;p&gt;Now that you’re properly clearing out thread-local state between requests/jobs, you have a new feature to write. Up until this point you could only add an image from a url - users want to upload images from their computer.&lt;/p&gt;
&lt;p&gt;It seems complex to take a file from the front end and somehow get it to a background job. So you’re going to do it in your controller - but you want to minimize the chance of timing out while uploading a bunch of files.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 Since this example app is built with rails, it should instead be using ActiveStorage. But let’s just play along at home 😌&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You’ve heard of an interesting library called &lt;code&gt;async&lt;/code&gt; that makes parallelizing IO&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; pretty easy and is more predictable than threads. It does it using this special Ruby 3+ FiberScheduler thing 🤔.&lt;/p&gt;
&lt;p&gt;As a test you try uploading some files to S3 in the controller. You create an &lt;code&gt;Async&lt;/code&gt; block, and inside of it you create as many &lt;code&gt;Async&lt;/code&gt; blocks as there are blocking operations to parallelize:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ImagesController
  before_action :set_user

  def create
    Async do
      params[:files].each do |file|
        Async { S3Upload.new(file).call }
      end
    end
  end
end

class S3Upload
  def initialize(file)
    [@file](https://micro.blog/file) = file
  end

  def call
    object.put(body: [@file](https://micro.blog/file))
  end

  private

  def object
    client = Aws::S3::Resource.new
    bucket = client.bucket(ENV[&amp;quot;bucket&amp;quot;])
    bucket.object(object_path(@file))
  end

  def object_path
    [@file](https://micro.blog/file)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-page-5.drawio-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You test this and they all upload in parallel! Nice! Your request takes as long as the longest upload, and no longer than that. But uploading random file names at the top level of your S3 bucket is not ideal so you prefix the files with the current user id:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ...
Async do
  S3Upload.new(file, AppContext.user).call
end
# ...

class S3Upload
  def initialize(file, user)
    [@file](https://micro.blog/file) = file
    [@user](https://micro.blog/user) = user
  end

  # ...

  def object_path
    name = [@file](https://micro.blog/file)
    &amp;quot;#{@user.id}/#{name}&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You test this and… it raises a &lt;code&gt;NoMethodError&lt;/code&gt; on &lt;code&gt;NilClass&lt;/code&gt; when trying to get &lt;code&gt;user.id&lt;/code&gt;. Huh? You’re sure the user is defined so you print some information:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ImagesController
  before_action :set_user

  def create
    puts &amp;quot;user[#{AppContext.user}]&amp;quot;
    # ...
  end
end

# ...
def object_path
  name = [@file](https://micro.blog/file)
  puts &amp;quot;upload user[#{AppContext.user}]&amp;quot;
  puts &amp;quot;upload [#{@user}]&amp;quot;
  &amp;quot;#{@user.id}/#{name}&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which prints:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;user[#&amp;lt;User id=123&amp;gt;]
upload user[]
upload [@user](https://micro.blog/user)[]
NoMethodError...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;wild-fiber&#34;&gt;A wild Fiber appeared!&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;AppContext.user&lt;/code&gt; is nil when we’re inside that async block… I think it’s time to look at those &lt;code&gt;Thread.current[]&lt;/code&gt; &lt;a href=&#34;https://docs.ruby-lang.org/en/3.0/Thread.html#method-i-5B-5D&#34;&gt;docs again&lt;/a&gt;…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thread#[]&lt;/p&gt;
&lt;p&gt;Returns the value of a fiber-local variable&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well that doesn’t seem right. Doesn’t everyone call them thread-locals? They’re not thread-local… they’re &lt;em&gt;fiber&lt;/em&gt;-local?&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/9c0c11900d.jpeg&#34; width=&#34;80%&#34; height=&#34;80%&#34; alt=&#34;&#34;&gt;
&lt;p&gt;What exactly does it mean for it to be fiber-local? And how can you fix it so can use your &lt;code&gt;AppContext&lt;/code&gt; within &lt;code&gt;Async&lt;/code&gt;?&lt;/p&gt;
&lt;h4 id=&#34;ruby-concurrency-layers&#34;&gt;Ruby concurrency has layers&lt;/h4&gt;
&lt;p&gt;The concurrency model in Ruby is multi-layered. The best way to describe it is as a nesting doll - as you pull away the outer layers, you find new layers of concurrency inside.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/7fee80c6bc.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In your own Ruby code, you can easily inspect each layer directly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;puts &amp;quot;Process #{Process.pid}&amp;quot;
puts &amp;quot;  Ractor #{Ractor.current}&amp;quot;
puts &amp;quot;    Thread #{Thread.current}&amp;quot;
puts &amp;quot;      Fiber #{Fiber.current}&amp;quot;

# Process 123
#   Ractor #&amp;lt;Ractor:#1 running&amp;gt;
#     Thread #&amp;lt;Thread:0x0... run&amp;gt;
#       Fiber #&amp;lt;Fiber:0x0... (resumed)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your code is always operating in the context of a specific instance of each layer. Mostly you can access those instances with a call to &lt;code&gt;current&lt;/code&gt;. Your code runs within a Fiber, which is inside a Thread, which is inside a Ractor, which is inside a Process. The next posts in this series will dig into every layer, but for now you’ve got a fiber problem&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Internally, every &lt;code&gt;Async&lt;/code&gt; block is run in a new &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Fiber.html&#34;&gt;Fiber&lt;/a&gt;. That’s the innermost layer of Ruby concurrency. So that means if you have something that is stored as a &lt;em&gt;fiber&lt;/em&gt;-local, every time you create a new fiber, you get an empty, fresh set of locals.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Fiber.current #&amp;lt;Fiber&amp;gt;
puts &amp;quot;Main Fiber #{Fiber.current}&amp;quot;
puts Thread.current[:app_user]
Fiber.new do
  puts &amp;quot;Fiber.new #{Fiber.current}&amp;quot;
  puts Thread.current[:app_user]
end.resume
Async do
  puts &amp;quot;Async #{Fiber.current}&amp;quot;
  puts Thread.current[:app_user]
  Async do
    puts &amp;quot;  Async #{Fiber.current}&amp;quot;
    puts Thread.current[:app_user]
    Async do
      puts &amp;quot;    Async #{Fiber.current}&amp;quot;
      puts Thread.current[:app_user]
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the above code you get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Main Fiber #&amp;lt;Fiber:0x00007a77a0b5bbb0 (resumed)&amp;gt;
#&amp;lt;User id=123&amp;gt;
  Fiber.new #&amp;lt;Fiber:0x00007a77a01766b8 main.rb:940 (resumed)&amp;gt;
	
    Async #&amp;lt;Fiber:0x00007a77a0175858 task.rb:326 (resumed)&amp;gt;
	
      Async #&amp;lt;Fiber:0x00007a77a0174fc0 task.rb:326 (resumed)&amp;gt;
	
        Async #&amp;lt;Fiber:0x00007a77a0174cf0 task.rb:326 (resumed)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s well and good to know, you think, but how can you fix it? You don’t care about being fiber-local, you’re trying to be thread-local so these fibers can use your &lt;code&gt;AppContext&lt;/code&gt;🧵.&lt;/p&gt;
&lt;h4 id=&#34;actual-thread-locals&#34;&gt;Actual thread-locals&lt;/h4&gt;
&lt;p&gt;Those same &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Thread.html#method-i-5B-5D&#34;&gt;Thread.current docs&lt;/a&gt; point us in the right direction:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For thread-local variables, see &lt;code&gt;thread_variable_get&lt;/code&gt; and &lt;code&gt;thread_variable_set&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let’s update &lt;code&gt;AppContext&lt;/code&gt; to use that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  class &amp;lt;&amp;lt; self
    def user
      Thread.current.thread_variable_get(:app_user)
    end

    def user=(user)
      Thread.current.thread_variable_set(:app_user, user)
      self.locale = user.locale
    end

    def locale
      Thread.current.thread_variable_get(:app_locale)
    end

    def locale=(locale)
      Thread.current.thread_variable_set(:app_locale, locale)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a bit more verbose, but otherwise it feels the same. Run our upload code again and…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;user[#&amp;lt;User id=123&amp;gt;]
upload user[#&amp;lt;User id=123&amp;gt;]
upload [@user](https://micro.blog/user)[#&amp;lt;User id=123&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Success 🙌🏼. The data is now actually thread-local. It is still present inside of fibers because those fibers all belong to the current thread&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;There are actually &lt;em&gt;two more&lt;/em&gt; core Ruby ways&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; you could handle this situation 😵‍💫. But we’ve covered the two most common options, and it solves the problem at hand. We’ll dig into those other options later in the series.&lt;/p&gt;
&lt;p&gt;This seems like it should be a solved problem, you think. Does everyone write this from scratch and encounter these same issues? Is there an easier way?&lt;/p&gt;
&lt;p&gt;Yes, thankfully.&lt;/p&gt;
&lt;h4 id=&#34;current-attributes&#34;&gt;CurrentAttributes&lt;/h4&gt;
&lt;p&gt;If you’re in Rails, or you use ActiveSupport, you should be using &lt;a href=&#34;https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html&#34;&gt;&lt;code&gt;CurrentAttributes&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CurrentAttributes&lt;/code&gt; provides the same basic behavior we built (plus more), and integrates seamlessly into both Rails controllers and jobs. It also has first class support from Sidekiq, even without Rails. Here’s the &lt;code&gt;AppContext&lt;/code&gt; code, now using &lt;code&gt;CurrentAttributes&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext &amp;lt; ActiveSupport::CurrentAttributes
  attribute :user, :locale

  def user=(user)
    super
    self.locale = user.locale
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, it’s already a much simpler and clearer setup. But the real benefit is now we don’t have to worry about anything else.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It hooks into the Rails executor lifecycle, so whenever a web request/job begins or completes, &lt;code&gt;AppContext.reset&lt;/code&gt; is called&lt;/li&gt;
&lt;li&gt;It internally uses &lt;code&gt;IsolatedExecutionState&lt;/code&gt;, which is an abstraction around thread and fiber-local state. It is configurable to use thread or fiber-local state, and by default it uses thread-local state so it works out of the box for your example Rails app&lt;/li&gt;
&lt;li&gt;Sidekiq offers &lt;a href=&#34;https://www.mikeperham.com/2022/07/29/sidekiq-and-request-specific-context/&#34;&gt;first class support for it&lt;/a&gt;, so even if you don’t use Rails you can opt-in to resetting behavior, and Sidekiq will automatically load it at the start of your job based on the execution state present when the job was enqueued&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interestingly, &lt;code&gt;CurrentAttributes&lt;/code&gt; doesn’t use the &lt;code&gt;Thread.current[]&lt;/code&gt; or &lt;code&gt;Thread.thread_variable_get&lt;/code&gt; approaches. We’ll talk about that more in a later post.&lt;/p&gt;
&lt;h4 id=&#34;request-store&#34;&gt;RequestStore&lt;/h4&gt;
&lt;p&gt;I would not recommend using this library. It only supports &lt;code&gt;Thread.current&lt;/code&gt;, and it only automatically clears itself for Rails controllers - you’re still on the hook for managing job middleware/callbacks! It also will fail the same way as our example in an &lt;code&gt;Async&lt;/code&gt; block. You can learn more about it from its &lt;a href=&#34;https://github.com/steveklabnik/request_store&#34;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;back-in-reality-three&#34;&gt;Back in reality, part three…&lt;/h4&gt;
&lt;p&gt;If this example seemed oddly specific again, that’s because this issue is present in some change tracking gems.&lt;/p&gt;
&lt;p&gt;In terms of &lt;a href=&#34;https://www.ruby-toolbox.com/projects/paper_trail&#34;&gt;monthly downloads&lt;/a&gt;, &lt;a href=&#34;https://github.com/paper-trail-gem/paper_trail&#34;&gt;&lt;code&gt;paper_trail&lt;/code&gt;&lt;/a&gt; is by far the most popular gem for change tracking. And internally it uses &lt;code&gt;request_store&lt;/code&gt; which means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It uses &lt;code&gt;Thread.current[]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;It &lt;em&gt;does&lt;/em&gt; take care of Rails controller cleanup for you&lt;/li&gt;
&lt;li&gt;it &lt;em&gt;does not&lt;/em&gt; take care of job cleanup for you. If you set tracking information anywhere in your jobs, you are susceptible to leaking data between job runs 🙅🏻‍♂️.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re going to use &lt;code&gt;paper_trail&lt;/code&gt;, &lt;em&gt;make sure you clear out your state in jobs&lt;/em&gt;. Since it uses &lt;code&gt;request_store&lt;/code&gt; internally, you can clear out state using that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# If using Sidekiq
class PaperTrailClearMiddleware
  include Sidekiq::ServerMiddleware

  def call(_worker, _job, _queue)
    yield
  ensure
    RequestStore.clear!
  end
end

Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add PaperTrailClearMiddleware
  end
end

# If using only ActiveJob
class ApplicationJob &amp;lt; ActiveJob::Base
  after_perform :clear_paper_trail_context

  def clear_paper_trail_context
    RequestStore.clear!
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is a gem that does this for Sidekiq only for you, called &lt;a href=&#34;https://github.com/madebylotus/request_store-sidekiq&#34;&gt;request_store-sidekiq&lt;/a&gt;. It hasn’t been updated in several years, but it does what my middleware example does, automatically for you.&lt;/p&gt;
&lt;p&gt;All change tracking gems use some type of thread/fiber state, so here’s the rundown on the remaining popular gems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/collectiveidea/audited&#34;&gt;audited&lt;/a&gt; is the next most popular but it uses &lt;code&gt;CurrentAttributes&lt;/code&gt; internally, so you’re good!&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/palkan/logidze&#34;&gt;Logidze&lt;/a&gt; uses a block, so it’s basically impossible to leak data between threads. But, it uses &lt;code&gt;Thread.current[]&lt;/code&gt; internally so it will break if you nest fibers (like &lt;code&gt;Async&lt;/code&gt; blocks) inside of it&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/public-activity/public_activity&#34;&gt;PublicActivity&lt;/a&gt; uses &lt;code&gt;Thread.current[]&lt;/code&gt; but it’s unclear if it is cleaning it up appropriately. You’ll have to do you own due diligence there&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;reusing-objects&#34;&gt;4. Reusing objects&lt;/h3&gt;
&lt;p&gt;You do know that single-threaded servers can still have issues leaking state between requests - like our fiber-local issue. But can they hit real threading issues? Let’s ask GitHub:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Us&lt;/strong&gt;: Hey GitHub!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: Ummm, hi?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Us&lt;/strong&gt;: You run Unicorn as your web server don’t you?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Github&lt;/strong&gt;: Who are you? How did you get in here??&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Us&lt;/strong&gt;: Can a single-threaded server like Unicorn encounter threading issues?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: …&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: …yes 😔 &lt;a href=&#34;https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/&#34;&gt;https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Someone wrote threaded code to save on performance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;after_action :run_expensive_op_in_thread
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It held a reference to a request environment object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def run_expensive_op_in_thread
  Thread.new do
    env = request.env
    # some background logic
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That request object got reused between requests - it just cleared its data out instead of creating a new instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def after_request
  request.env.clear
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In certain edge cases, sessions would get reused between users:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def run_expensive_op_in_thread
  Thread.new do
    if not_logged_in
      login(request.env)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So yes… it still involved &lt;em&gt;explicit&lt;/em&gt; threading, but in the context of a single-threaded server. This is the crux of that “Ruby is always multi-threaded” thing. Threads pop up all over the place even when you &lt;em&gt;think&lt;/em&gt; you’re not using them. If you defensively code assuming threads could eventually be involved, you reduce the risk of these types of issues popping up later.&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/race-condition-diagram.webp&#34; width=&#34;600&#34; height=&#34;284&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source:&lt;/strong&gt; &lt;a href=&#34;https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/&#34;&gt;https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The fix for the problem was to stop reusing environment objects between requests. Each new request created a new environment object, which means a thread could safely hold onto that environment information without anything leaking in from subsequent requests.&lt;/p&gt;
&lt;p&gt;Create new instances. Unless you know they are &lt;em&gt;incredibly&lt;/em&gt; expensive, it is not worth the risk of sharing them if it can be avoided.&lt;/p&gt;
&lt;h3 id=&#34;race-conditions&#34;&gt;5. Race conditions&lt;/h3&gt;
&lt;p&gt;You’ve been having a tough time with these bugs, yeesh. It’s as if some kind of external force is controlling you to intentionally introduce each of these issues for other people’s benefit. But that obviously can’t be true 🤫.&lt;/p&gt;
&lt;p&gt;You’ve got a new requirement. And this one seems pretty… concurrent. You need to coordinate some of your jobs - you split your work into several pieces, run them in individual jobs, then perform one final piece once every job has finished.&lt;/p&gt;
&lt;p&gt;How can you coordinate a set of jobs? Maybe there’s a concurrency concept you can try out!&lt;/p&gt;
&lt;p&gt;You read about something called a “Countdown Latch”. You tell it what to countdown from, multiple threads can safely count down using it, and you can have another thread wait until it finishes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;latch = CountdownLatch.new(10)
10.times.map do |x|
  Thread.new do
    puts &amp;quot;hello #{x}&amp;quot;
    latch.count_down
  end
end

latch.wait
puts &amp;quot;world!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prints out&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;hello 0
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The results look ordered but it’s just a coincidence of how it ran. The &lt;code&gt;CountdownLatch&lt;/code&gt; only helps coordinate completing jobs, nothing internal to their ordering&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;threading-concepts&#34;&gt;Threading concepts&lt;/h4&gt;
&lt;p&gt;When you call &lt;code&gt;latch.wait&lt;/code&gt;, the thread waits until the &lt;code&gt;count_down&lt;/code&gt; is complete. What would implementing a &lt;code&gt;CountdownLatch&lt;/code&gt; look like?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CountdownLatch
  def initialize(count)
    @count = count
    @mutex = Mutex.new
    @cond = ConditionVariable.new
    puts &amp;quot;CountdownLatch initialized with a count of #{@count}&amp;quot;
  end

  def wait
    @mutex.synchronize do
      @cond.wait(@mutex) while @count &amp;gt; 0
    end
  end

  def count
    @mutex.synchronize { @count }
  end

  def count_down
    @mutex.synchronize do
      @count -= 1
      if @count == 0
        @cond.broadcast
      end
      @count
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 The &lt;a href=&#34;https://github.com/ruby-concurrency/concurrent-ruby&#34;&gt;&lt;code&gt;concurrent-ruby&lt;/code&gt;&lt;/a&gt; gem comes with its own &lt;code&gt;CountdownLatch&lt;/code&gt;. I’ve written my own here for demonstration - but if you have a need for one, use theirs, not mine&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A &lt;code&gt;CountdownLatch&lt;/code&gt; is a special form of a computer science concept called a &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Barrier_(computer_science)&#34;&gt;barrier&lt;/a&gt;. Barriers are used to help coordinate execution of multiple threads&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; - a &lt;code&gt;CountdownLatch&lt;/code&gt; is a one-time use form of a barrier where a thread can wait until the barrier counts down to zero.&lt;/p&gt;
&lt;p&gt;To achieve that, you need a &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Thread/Mutex.html&#34;&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/a&gt; and a &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Thread/ConditionVariable.html&#34;&gt;&lt;code&gt;ConditionVariable&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;Mutex&lt;/code&gt;s and &lt;code&gt;ConditionVariable&lt;/code&gt;s are low-level tools available for coordinating access to resources in Ruby code. We’ll dig more into those in “Concurrent, colorless Ruby”.&lt;/p&gt;
&lt;p&gt;For now, know that a &lt;code&gt;Mutex&lt;/code&gt; can be used to safely lock a section of Ruby code. While you are in a &lt;code&gt;synchronize&lt;/code&gt; block, any other thread using the same &lt;code&gt;Mutex&lt;/code&gt; will wait until that block finishes. As far as the coordinating threads are concerned, anything that happens in that block happens atomically - individual pieces which would not normally be thread-safe are treated like a single, safe operation.&lt;/p&gt;
&lt;p&gt;Ok. We’ve got our coordinator ✅. Now let’s try applying it to our jobs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TickerJob
  include Sidekiq::Job

  def perform
    # do some work
    latch = CountdownLatch.new(10)
    remaining = latch.count_down
    puts &amp;quot;Tick! [#{remaining}]&amp;quot;
  end
end

class CoordinatorJob
  include Sidekiq::Job

  def perform
    latch = CountdownLatch.new(10)
    10.times do
      TickerJob.perform_async
    end
    latch.wait
    puts &amp;quot;All jobs finished!&amp;quot;
  end
end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which gives us…!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;CountdownLatch initialized with a count of 10
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Uhhhh… hmmm… well that’s not working properly. It never finishes, and every &lt;code&gt;count_down&lt;/code&gt; gives us &lt;code&gt;9&lt;/code&gt;. Oh right. You can’t create new countdowns everywhere you need them… you need to &lt;em&gt;gulp&lt;/em&gt;… &lt;em&gt;share&lt;/em&gt; them 😱.&lt;/p&gt;
&lt;p&gt;You know the &lt;code&gt;CountdownLatch&lt;/code&gt; class is thread safe, so that means multiple threads can use an instance of it together safely. You decide to use a global for now to try it out in your jobs… a class-level ivar 😅. Those are problematic, &lt;em&gt;but&lt;/em&gt; the countdown code is thread safe so it should be ok, right? This is your time to shine!&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/goldeneye.webp&#34; width=&#34;470&#34; height=&#34;200&#34; alt=&#34;&#34;&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  def self.latch
    @latch ||= CountdownLatch.new(10)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just to be safe you try it on some simple threads first…&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.times.map do
  Thread.new do
    latch = AppContext.latch
    # do some work
    puts &amp;quot;Count down! [#{latch.count_down}]&amp;quot;
  end
end

AppContext.latch.wait
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;CountdownLatch initialized with a count of 10
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well… So much for thread safety? What’s the deal?&lt;/p&gt;
&lt;p&gt;You’re seeing &lt;code&gt;CountdownLatch initialized with a count&lt;/code&gt; repeatedly, so it seems that the instance gets created over and over. Is the &lt;code&gt;latch&lt;/code&gt; instance different every time?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.times.map do |_x|
  Thread.new do
    latch = AppContext.latch
    # do some work
    puts &amp;quot;Count down! [#{latch.count_down}]&amp;quot;
    puts &amp;quot;latch id #{latch.object_id}&amp;quot;
  end
end

AppContext.latch.wait
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🫠&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;...
latch id 2700
...
latch id 2720
...
latch id 2740
...
latch id 2760
...
latch id 2780
...
latch id 2800
...
latch id 2820
...
latch id 2840
...
latch id 2860
...
latch id 2880
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You get a different latch every time.&lt;/p&gt;
&lt;h4 id=&#34;check-then-act&#34;&gt;Memoization / check-then-act&lt;/h4&gt;
&lt;p&gt;You’re hitting a race condition - multiple threads are racing to finish an operation. And this particular situation is so common it’s got a name: “check-then-act”.&lt;/p&gt;
&lt;p&gt;In Ruby, the most common case of this is with memoization.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 If you don’t know - memoization is one of those funny looking terms, and sounds a bit like if &lt;a href=&#34;https://youtu.be/nEe1cTDbXHU?si=OjyohItd-GTD8gdq&#34;&gt;the priest from princess bride&lt;/a&gt; tried to say “memorization”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/mawwiage.webp&#34; width=&#34;480&#34; height=&#34;260&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;Mem-mwah-zatioooon&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Memoization is just a means for lazy-loading code, or sharing an expensive resource. The first time you need it, you initialize it and store it to a variable you can reuse, like an instance variable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Memoization is ok within a single thread, but if you’re sharing an object then memoization can lead to “check-then-act” conditions. The &lt;code&gt;AppContext&lt;/code&gt; code is using memoization to share the &lt;code&gt;CountdownLatch&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@latch ||= CountdownLatch.new(10)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks like a one-line call, but is actually multiple statements.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if !@latch
  @latch = CountdownLatch.new(10)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You “check” if the &lt;code&gt;@latch&lt;/code&gt; is present, “then” you “act” on that information. If it’s present, you don’t do anything. If it isn’t, you create a new &lt;code&gt;CountdownLatch&lt;/code&gt; and set it to &lt;code&gt;@latch&lt;/code&gt;. Multiple threads cannot run this code in parallel, but they can swap between each other at inopportune times.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread 1 checks, and finds that &lt;code&gt;@latch&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. It instantiates &lt;code&gt;CountdownLatch&lt;/code&gt; to set it to &lt;code&gt;@latch&lt;/code&gt;. The &lt;code&gt;initialize&lt;/code&gt; method in &lt;code&gt;CountdownLatch&lt;/code&gt; has a &lt;code&gt;puts&lt;/code&gt; statement. That’s blocking IO, so Thread 1 yields and…&lt;/li&gt;
&lt;li&gt;Thread 2 checks, and finds that &lt;code&gt;@latch&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;. As it instantiates &lt;code&gt;CountdownLatch&lt;/code&gt;, it yields due to the blocking IO from &lt;code&gt;puts&lt;/code&gt;…&lt;/li&gt;
&lt;li&gt;Thread 3 checks, and finds that &lt;code&gt;@latch&lt;/code&gt; is &lt;code&gt;nil&lt;/code&gt;… seeing a pattern?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;puts&lt;/code&gt; statement exists very intentionally to force this condition. Without it, the race condition would happen much less frequently. But it could be a logger call, or it could open a connection to an external resource, or create a Tempfile&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;. Many things can easily causes the &lt;code&gt;initialize&lt;/code&gt; to yield to other available threads.&lt;/p&gt;
&lt;p&gt;If you want to use memoization safely between threads, you need a &lt;code&gt;Mutex&lt;/code&gt;. Just like &lt;code&gt;CountdownLatch&lt;/code&gt; uses a &lt;code&gt;Mutex&lt;/code&gt; internally to &lt;code&gt;count_down&lt;/code&gt; safely between threads, we need a &lt;code&gt;Mutex&lt;/code&gt; to safely share our lazy-loaded instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  @latch_mutex = Mutex.new

  def self.latch
    return @latch if @latch
    @latch_mutex.synchronize do
      @latch ||= CountdownLatch.new(10)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;💰 My personal metric is that the right amount of mutexes in my code is zero. If I am using a mutex, I think hard to figure out a way to avoid it because it means I’m opening up myself and future devs to a lot of cognitive overhead: you need to think critically anytime you make a change relating to mutex code.&lt;/p&gt;
&lt;p&gt;If you’re a library or framework author they may be unavoidable at some point to do interesting or useful things. In my own application code, I can pretty much &lt;em&gt;always&lt;/em&gt; avoid them.&lt;/p&gt;
&lt;p&gt;To be perfectly honest, I’m not confident it’s ok to return the &lt;code&gt;@latch&lt;/code&gt; early if it’s present. I’m pretty sure it is… but is there a memory visibility implication? Am I creating a new type of edge case or race condition? These are the questions you have to ask yourself when you introduce mutexes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok, &lt;code&gt;AppContex.latch&lt;/code&gt; memoization take three:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.times.map do
  Thread.new do
    latch = AppContext.latch
    # do some work
    puts &amp;quot;Count down! [#{latch.count_down}]&amp;quot;
    puts &amp;quot;latch id #{latch.object_id}&amp;quot;
  end
end

AppContext.latch.wait
puts &amp;quot;All threads finished!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Finally&lt;/em&gt;, you get a correct countdown!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;CountdownLatch initialized with a count of 10
Count down! [9]
latch id 2700
Count down! [6]
latch id 2700
Count down! [3]
latch id 2700
Count down! [0]
latch id 2700
Count down! [7]
latch id 2700
Count down! [1]
latch id 2700
Count down! [4]
latch id 2700
Count down! [8]
latch id 2700
Count down! [5]
latch id 2700
Count down! [2]
latch id 2700
All threads finished!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Much better! We should be ready for our jobs now. But is this really the right direction? You’ve resolved the “check-then-act” issue, but you realize there are some pretty obvious limits to this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Right now we can have one instance of the &lt;code&gt;CountdownLatch&lt;/code&gt; to share, and it’s hard-coded to 10… What would be the best way to share an object, but also make it configurable?&lt;/li&gt;
&lt;li&gt;What if you have more than one job server? Multiple job servers can pull jobs from the same queue 🤔. You can only synchronize across threads if they live in the same server process. Outside of that, your mutex’s ability to lock anything is just a wish in the breeze.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need something to share, that’s independent of what’s in memory and can represent multiple coordinating sets of jobs.&lt;/p&gt;
&lt;h4 id=&#34;read-modify-write&#34;&gt;read-modify-write&lt;/h4&gt;
&lt;p&gt;You’re going to branch a bit outside of threads for concurrency, and get distributed. You rewrite the &lt;code&gt;CountdownLatch&lt;/code&gt; to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow specifying an id to key off of, so we can support more than one &lt;code&gt;CountdownLatch&lt;/code&gt; at a time&lt;/li&gt;
&lt;li&gt;Store the countdown value in &lt;a href=&#34;https://redis.io/&#34;&gt;Redis&lt;/a&gt;, so it can be accessed and count down from anywhere&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Independently create &lt;code&gt;CountdownLatch&lt;/code&gt; instances so you don’t need to share the instance itself. You are sharing the Redis database instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ruby&#34; data-lang=&#34;ruby&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DistributedCountdownLatch&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;initialize&lt;/span&gt;(id, count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;)
    &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;@id&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;https&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;//mi&lt;/span&gt;cro&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;blog&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;id) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; id
    @redis &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;Redis&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;new
    @redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set(key, count) &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; count
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;wait&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
      puts &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Remaining in countdown: [&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;current&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;]&amp;#34;&lt;/span&gt;
      sleep(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)
    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;current&lt;/span&gt;
    @redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;get(key)&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;to_i
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;count_down&lt;/span&gt;
    new_current &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
    @redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;set(key, new_current)
    new_current
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;latch_&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;#{&lt;/span&gt;@id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running it across some threads looks promising.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;latch = DistributedCountdownLatch.new(&amp;quot;hello&amp;quot;, 10)
10.times.map do |x|
  Thread.new do
    puts &amp;quot;hello #{latch.count_down}&amp;quot;
  end
end

latch.wait
puts &amp;quot;world&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Remaining in countdown: [10]
hello 9
hello 8
hello 7
hello 6
hello 5
hello 4
hello 3
hello 2
hello 1
Remaining in countdown: [1]
hello 0
world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You run it several times and feel ready to use it in your Sidekiq jobs!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TickerJob
  include Sidekiq::Job

  def perform
    # do some work
    latch = DistributedCountdownLatch.new(&amp;quot;tick&amp;quot;)
    remaining = latch.count_down
    puts &amp;quot;Tick! [#{remaining}]&amp;quot;
  end
end

class CoordinatorJob
  include Sidekiq::Job

  def perform
    latch = DistributedCountdownLatch.new(&amp;quot;tick&amp;quot;, 10)
    10.times do
      TickerJob.perform_async
    end
    latch.wait
    puts &amp;quot;All jobs finished!&amp;quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s see how it goes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Tick! [9]
Tick! [9]
Tick! [8]
Tick! [7]
Tick! [7]
Tick! [6]
Tick! [6]
Remaining in countdown: [6]
Tick! [5]
Tick! [5]
Tick! [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Poorly. It went poorly 🥺.&lt;/p&gt;
&lt;p&gt;If we walk through the code again:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Thread 1 reads the value from Redis. That’s blocking IO, so Thread 1 yields and…&lt;/li&gt;
&lt;li&gt;Thread 2 reads the value from Redis. That’s blocking IO, so Thread 2 yields and…&lt;/li&gt;
&lt;li&gt;Thread 3 reads the value from Redis. That’s blocking IO, so Thread 3 yields and…&lt;/li&gt;
&lt;li&gt;Thread 1 gets the value back which is 10. Its subtracts 1 from it and writes 9 to Redis. That’s blocking IO, so Thread 1 yields and…&lt;/li&gt;
&lt;li&gt;Thread 2 gets the value back which is 10. Its subtracts 1 from it and writes 9 to Redis. That’s blocking IO, so Thread 1 yields and…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;🙅🏻‍♂️👈🏼👉🏼👇🏼👆🏼🙅🏻‍♂️&lt;/p&gt;
&lt;p&gt;You’re experiencing another race condition, and once again it’s common enough to have a name: “read-modify-write”. You’re “read”ing in a value in its current state from Redis, “modify”ing it, then “write”ing it back to Redis. The problem is that for shared resources, each reader “read”s the value in the same state. We have to coordinate the way each client is pulling data from Redis.&lt;/p&gt;
&lt;p&gt;Race conditions happen on shared resources - those shared resources don’t need to live on the same machine.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 Redis is a single-threaded database, meaning it can only ever execute one thing at a time, no matter how many clients are connected. But being single-threaded does not do anything to fix race conditions. It may only execute one operation at a time, but nothing about that guarantees you won’t execute those operations out of order.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What can you do?&lt;/p&gt;
&lt;p&gt;We need something like a &lt;code&gt;Mutex&lt;/code&gt;, but a &lt;code&gt;Mutex&lt;/code&gt; that can work across independent servers. A &lt;code&gt;Mutex&lt;/code&gt; is just a lock you acquire on a resource - can you get that with Redis?&lt;/p&gt;
&lt;p&gt;You can simulate a lock using the Redis operation &lt;code&gt;SET&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class DistributedCountdownLatch
  class WatchError &amp;lt; StandardError; end

  # ... same code as before

  def count_down
    lock_key = &amp;quot;lock_#{key}&amp;quot;
    loop do
      # `nx` stands for `not exists`
      # `ex` means the lock key will expire in 10 seconds
      if @redis.set(lock_key, 1, nx: true, ex: 10)
        new_count = current - 1
        # `multi` allows every operation to run atomically
        @redis.multi do |transaction|
          transaction.set(key, new_count)
          transaction.del(lock_key)
        end
        return new_count
      end
      sleep 0.1
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;First you set a key using the &lt;code&gt;nx&lt;/code&gt;, or “not exists” flag. If a key already exists, the operation fails. In that case you &lt;code&gt;sleep&lt;/code&gt; for &lt;code&gt;0.1&lt;/code&gt; seconds, then try again.&lt;/li&gt;
&lt;li&gt;Second you get the current value.&lt;/li&gt;
&lt;li&gt;Third, you perform a Redis &lt;code&gt;MULTI&lt;/code&gt; operation, which is a kind of Redis database transaction. We will &lt;code&gt;SET&lt;/code&gt; the new count, and &lt;code&gt;DEL&lt;/code&gt;ete our lock key, in one atomic call to Redis. It all works, or it all fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 This type of lock is called a “Pessimistic lock”. Pessimistic locking means you must obtain exclusive access to the lock before running an operation, and retry or raise an error if that is not possible. The alternative is “optimistic” locking, which means you attempt to run the operation, and before committing verify that no other operations have taken place. If they have you either have to retry, or raise an error.&lt;/p&gt;
&lt;p&gt;📝 If you’re going to use Redis as a distributed lock, you should use the &lt;a href=&#34;https://github.com/leandromoreira/redlock-rb&#34;&gt;redlock-rb gem&lt;/a&gt;, which implements the Redis teams recommended algorithm for locking, the &lt;a href=&#34;https://redis.io/docs/latest/develop/use/patterns/distributed-locks/&#34;&gt;“Redlock” algorithm&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;By creating a simple form of a distributed &lt;code&gt;Mutex&lt;/code&gt;, this fixes our race condition! Now we will only attempt the update once we acquire the lock, and we’ll retry until we can acquire it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Tick! [9]
Tick! [8]
Remaining in countdown [8]
Tick! [7]
Tick! [6]
Tick! [5]
Tick! [4]
Tick! [3]
Tick! [2]
Tick! [1]
Remaining in countdown [1]
Tick! [0]
All jobs finished!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;coordinating-jobs&#34;&gt;Coordinating jobs&lt;/h4&gt;
&lt;p&gt;Even having fixed the “check-then-set” and “read-modify-write” issues, trying to coordinate your jobs using this approach is a lot of work to get right. I wouldn’t recommend it, even though the code is slightly more accurate now.&lt;/p&gt;
&lt;p&gt;If you’re interested in more appropriate ways to coordinate jobs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some job servers offer a feature for running and coordinating jobs in batches, with optional success/finish/error callbacks.
&lt;ul&gt;
&lt;li&gt;Sidekiq offers this in their &lt;a href=&#34;https://sidekiq.org/products/pro.html&#34;&gt;paid pro tier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/bensheldon/good_job?tab=readme-ov-file#batches&#34;&gt;GoodJob&lt;/a&gt; offers batch support&lt;/li&gt;
&lt;li&gt;I am &lt;a href=&#34;https://github.com/rails/solid_queue/pull/142&#34;&gt;working on batch support&lt;/a&gt; for &lt;a href=&#34;https://github.com/rails/solid_queue&#34;&gt;SolidQueue&lt;/a&gt; - feel free to leave feedback!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If you still are interested in a &lt;code&gt;CountdownLatch&lt;/code&gt; approach:
&lt;ul&gt;
&lt;li&gt;Redis has a built-in &lt;code&gt;INCR&lt;/code&gt; and &lt;code&gt;DECR&lt;/code&gt; method, which handle these updates for you atomically&lt;/li&gt;
&lt;li&gt;ActiveRecord has an &lt;code&gt;increment&lt;/code&gt; and &lt;code&gt;decrement&lt;/code&gt; method you could use to &lt;code&gt;decrement&lt;/code&gt; a value on a record atomically.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;read-modify-write-for-days&#34;&gt;read-modify-write shows up in many ways&lt;/h4&gt;
&lt;p&gt;There’s a very good chance you have code susceptible to read-modify-write issues right now. Aside from reading a value and then modifying it, the most common situation I see is around enforcing uniqueness constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You expect a record in a database to be unique, but you don’t have uniqueness constraints at the database-level.&lt;/li&gt;
&lt;li&gt;You “read” the database and find the record doesn’t exist.&lt;/li&gt;
&lt;li&gt;Another request comes in at the same time and also “read”s the database and finds the record doesn’t exist.&lt;/li&gt;
&lt;li&gt;In this case the “modify” is creating the record. Each request now attempts to create the record.&lt;/li&gt;
&lt;li&gt;With no database-level uniqueness constraint, the result is that each request thinks the record doesn’t exist, and you “write” two (or more) records, violating your uniqueness requirement.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;parting-thoughts&#34;&gt;Parting thoughts&lt;/h3&gt;
&lt;p&gt;Phew! That was a lot. We’ve gone deep into 5 different threading mistakes, ways to identify them, and ways to fix them. Let’s finish up with a few topics addressing how to look out for threading issues in your future endeavors, as well as answering some possible remaining questions!&lt;/p&gt;
&lt;h3 id=&#34;tips-for-gems&#34;&gt;Tips for auditing gems 💎 &lt;/h3&gt;
&lt;p&gt;Here are some notes on what to look for when auditing a gem for Thread safety.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the gem interface is a static method, check the source. It might be an indicator of class-level ivars or thread/fiber-local data&lt;/li&gt;
&lt;li&gt;If the amount of code is small, take some time to understand it. Keep the “threading mistakes” we’ve discussed in mind and see if anything sticks out to you&lt;/li&gt;
&lt;li&gt;If there is a lot of code, look through and make sure it has active maintenance and maintainers. You’re not likely to learn a larger code base so at least know it’s being actively maintained. There have been studies to show that the longer-lived a codebase is, the likelier it is developers and users will find and remove threading issues.&lt;/li&gt;
&lt;li&gt;If there’s low activity, a lot of code, and you can’t take the time to understand it, you probably want to avoid it&lt;/li&gt;
&lt;li&gt;Make sure &lt;code&gt;main&lt;/code&gt; has been released into a gem version. The repository can sometimes be misleading - it may have a fix explaining an issue you’re hitting that hasn’t been released&lt;/li&gt;
&lt;li&gt;If a gem is unmaintained or inactive, it doesn’t mean you can’t use it. &lt;em&gt;But&lt;/em&gt;, effectively you own it. If you hit issues, you’ll be the one to fix them&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;using-falcon&#34;&gt;I use &lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;Falcon&lt;/a&gt; - Fibers are safe from this right?&lt;/h3&gt;
&lt;p&gt;The short answer is “assume not”.&lt;/p&gt;
&lt;p&gt;We’ll dig more into the nuances of fibers in “Concurrent, colorless Ruby: Fibers”, but you are always safer to run as if threads will eventually be involved. Falcon can run using Threads in addition to Fibers, for instance.&lt;/p&gt;
&lt;p&gt;While Fibers &lt;em&gt;are&lt;/em&gt; more deterministic, even with the FiberScheduler, they are still susceptible to some of the same issues as Threads.&lt;/p&gt;
&lt;p&gt;Quick question: Can race conditions happen with Fibers? We’ll walk through that in “Concurrent, colorless Ruby: Fibers”. Or see the handy answer key below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;.seY: rewsnA&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;using-jruby-truffle&#34;&gt;I use JRuby/TruffleRuby…&lt;/h3&gt;
&lt;p&gt;Good news! These examples will break really fast for you! Congrats!&lt;/p&gt;
&lt;p&gt;We’ll talk more about this later in the series. But know for now that JRuby and TruffleRuby use Threads without any implicit locks (ie, the GVL). You will hit threading issues much faster in those environments than in CRuby.&lt;/p&gt;
&lt;h3 id=&#34;reforking&#34;&gt;Preforking / Reforking servers&lt;/h3&gt;
&lt;p&gt;Preforking and Reforking servers &lt;em&gt;do&lt;/em&gt; share resources. They use &lt;code&gt;fork&lt;/code&gt;, which initially shares the entire memory space between two processes using something called Copy-On-Write memory. That means things which are not safe to share, like file handles, &lt;em&gt;are&lt;/em&gt; shared by default.&lt;/p&gt;
&lt;p&gt;Thankfully many members of the Ruby community have worked to make that as safe as possible for most popular libraries. We’ll talk more on this topic later in the series, discussing areas where it is handled automatically and areas where it isn’t.&lt;/p&gt;
&lt;h3 id=&#34;mutexes-for-globals&#34;&gt;What about using mutexes for globals?&lt;/h3&gt;
&lt;p&gt;You may have seen some of the global examples and thought: “couldn’t you keep them global the way they are and keep them safe using a mutex?”&lt;/p&gt;
&lt;p&gt;Good question!&lt;/p&gt;
&lt;p&gt;That would mean using a lock to provide coordinated access to your global state. Like taking our Fibonacci example and using a lock here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;monitor&amp;quot;

class Result
  attr_accessor :value
end

class Fibonacci
  @fib_monitor = Monitor.new

  class &amp;lt;&amp;lt; self
    def result=(value)
      @fib_monitor.synchronize { @result = value }
    end

    def result
      @fib_monitor.synchronize { @result }
    end

    def calculate(n)
      @fib_monitor.synchronize do
        self.result = Result.new
        result.value = fib(n)
      end
    end

    def fib(n)
      return n if n &amp;lt;= 1

      fib(n - 1) + fib(n - 2)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 We actually use a &lt;code&gt;Monitor&lt;/code&gt; here instead of a &lt;code&gt;Mutex&lt;/code&gt; because it supports reentrancy. That means we can re-enter the synchronize block from the same thread, which is necessary in our example. Otherwise, it is &lt;em&gt;largely&lt;/em&gt; the same as a &lt;code&gt;Mutex&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;📝 You may have noticed I removed the &lt;code&gt;attr_accessor&lt;/code&gt; and I both set and get using the &lt;code&gt;Monitor&lt;/code&gt;. I did this out of caution for a concept of “visibility” of shared resources between Threads. We’ll discuss that more later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now we’ll run it again using &lt;code&gt;run_forever&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

run_forever do |iteration|
  n = iteration % answers.size
  Fibonacci.calculate(n)
  answer = answers[n]
  result = Fibonacci.result.value

  if result != answer
    raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
  end
rescue =&amp;gt; e
  puts &amp;quot;Iteration[#{iteration}] #{e.message}&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works. We don’t get any errors. The mutex allows the code to treat multiple operations as a single operation. Are there downsides?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you’re going to use a &lt;code&gt;Monitor&lt;/code&gt;/&lt;code&gt;Mutex&lt;/code&gt; on a piece of a global state - keep your mutex scope small to reduce locking overhead&lt;/li&gt;
&lt;li&gt;Don’t hog the GVL. Make sure you don’t wrap your GVL-releasing operations like blocking IO in a &lt;code&gt;Mutex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Don’t get your mutex scope wrong. If you get your scoping wrong, you may still run into issues like memory inconsistency between Threads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll dig into GVL hogs and surprising behaviors in “Colorless, concurrent Ruby”. For now the short answer is: you can, but avoid it, if possible. It may hurt your ability to parallelize your code.&lt;/p&gt;
&lt;h3 id=&#34;detect-and-correct&#34;&gt;It’s not doom and gloom, it’s detect and correct&lt;/h3&gt;
&lt;p&gt;When I highlight a bunch of pitfalls you might hit, it feels like I’m being a virtual storm cloud, just trying to ruin your sunny coding day. In my post &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html&#34;&gt;PgBouncer is useful, important, and fraught with peril&lt;/a&gt;, I highlight the tricky parts to inform but I still use the tool happily every day!&lt;/p&gt;
&lt;p&gt;It’s the same for Ruby and threading concerns. Almost every language has these same problems, or worse&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;. Threads are easy to get wrong, so you want to take some golden paths and stay simple when using them.&lt;/p&gt;
&lt;p&gt;Your code is safer if you assume you’re always using threads. You are. And if by some oddity you &lt;em&gt;really&lt;/em&gt; &lt;em&gt;aren’t&lt;/em&gt;, there’s a strong chance you will be eventually.&lt;/p&gt;
&lt;h3 id=&#34;takeaways&#34;&gt;Takeaways 🥡&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Your Ruby code is threaded. Just assume it is and forget semantics.&lt;/li&gt;
&lt;li&gt;The GVL can make threading bugs harder to produce - if you see suspicious code that &lt;em&gt;seems&lt;/em&gt; to work, try again using &lt;code&gt;run_forever&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Think thread safety&lt;/li&gt;
&lt;li&gt;If you write any explicitly threaded code, coordinate threads or share data - use existing vetted tools like job batches and &lt;code&gt;concurrent-ruby&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Other runtimes like Ruby/TruffleRuby will produce bugs much faster&lt;/li&gt;
&lt;li&gt;As much as possible, don’t share resources between threads. Forget your lessons from childhood: sharing is bad.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;next-up&#34;&gt;Next up&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/7fee80c6bc.jpeg&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the second step into this Ruby concurrency series. Next up we’ll discuss the concept of “colorless” programming, and how it enhances the Ruby experience. We’ll also take our first dive into how the layers of Ruby work, starting with Threads.&lt;/p&gt;
&lt;p&gt;More soon 👋🏼!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And any blocking operation&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;💩&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There is a short section in the thread docs that describes this behavior as well &lt;a href=&#34;https://docs.ruby-lang.org/en/3.3/Thread.html#class-Thread-label-Fiber-local+vs.+Thread-local&#34;&gt;https://docs.ruby-lang.org/en/3.3/Thread.html#class-Thread-label-Fiber-local+vs.+Thread-local&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;code&gt;Fiber#store&lt;/code&gt; and adding attr accessors to Thread/Fiber&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The concept can also be used for other units of concurrency, like fibers&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Or use a file mutex &lt;a href=&#34;https://github.com/yegor256/futex&#34;&gt;https://github.com/yegor256/futex&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Other databases would also work, but the Redis data model is the simplest to get going for a simple value. Sidekiq works off of Redis as well, so you already would have it in your stack&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;So don’t let your Python or Java friends try to bully you about it 😉. JavaScript can suffer similar issues as well.&lt;/p&gt;
&lt;p&gt;I’ve never supported a production Rust or Elixir application so I can’t speak from experience, but those are the only two languages I can think of that might fare a lot better in this regard (and similar languages like F#, Gleam, Clojure, etc).&lt;/p&gt;
&lt;p&gt;Elixir because of its pure functional approach. Rust because it’s one of the first non-functional languages I’ve ever encountered that has strong consistency baked into its core. They eliminate a class of threading bugs by their nature, but can still experience issues unique to how they work.&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/2ffdf3bdb5.png)

&gt; 👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:
&gt; 
&gt; - [Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)
&gt; - Your Ruby programs are always multi-threaded: Part 2
&gt;   - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my! (Concurrent, colorless Ruby)](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “Your Ruby programs are always multi-threaded: Part 2”. I’ll update the links as each part is released, and include these links in each post.

- _**Part 1**_
- [It’s all threaded](https://jpcamara.com/2024/06/04/your-ruby-programs.html#its-all-threaded)
- [Ok but how threaded is it _really_](https://jpcamara.com/2024/06/04/your-ruby-programs.html#how-threaded-is-it)
- [Threading mistakes](https://jpcamara.com/2024/06/04/your-ruby-programs.html#threading-mistakes)
	- 1. [Sharing module/class instance variables](https://jpcamara.com/2024/06/04/your-ruby-programs.html#sharing-ivars)
		- [Heisenbugs](https://jpcamara.com/2024/06/04/your-ruby-programs.html#heisenbugs)
		- [Ruby internals](https://jpcamara.com/2024/06/04/your-ruby-programs.html#ruby-internals)
		- [Back in reality…](https://jpcamara.com/2024/06/04/your-ruby-programs.html#back-in-reality)
	- 2. [Copying state to instances](https://jpcamara.com/2024/06/04/your-ruby-programs.html#copying-state)
		- [Back in reality… part two](https://jpcamara.com/2024/06/04/your-ruby-programs.html#back-in-reality-two)
	- 3a. [Cleaning up thread state](https://jpcamara.com/2024/06/04/your-ruby-programs.html#cleaning-up)
		- [If you’re using Sidekiq](https://jpcamara.com/2024/06/04/your-ruby-programs.html#using-sidekiq)
		- [If you’re using ActiveJob](https://jpcamara.com/2024/06/04/your-ruby-programs.html#using-activejob)
- _**Part 2**_
	- 3b. [Sharing state with fibers](#sharing-state-with-fibers)
		- [A wild Fiber appeared!](#wild-fiber) 🌾 
		- [Ruby concurrency has layers](#ruby-concurrency-layers)
		- [Actual thread-locals](#actual-thread-locals)
		- [CurrentAttributes](#current-attributes)
		- [RequestStore](#request-store)
		- [Back in reality… part three](#back-in-reality-three)
	- 4. [Reusing objects](#reusing-objects)
	- 5. [Race conditions](#race-conditions)
		- [Threading concepts](#threading-concepts)
		- [Memoization / check-then-act](#check-then-act)
		- [read-modify-write](#read-modify-write)
		- [Coordinating jobs](#coordinating-jobs)
		- [read-modify-write shows up in many ways](#read-modify-write-for-days)
- [Parting thoughts](#parting-thoughts)
- [Tips for auditing gems](#tips-for-gems) 💎 
- [I use Falcon - Fibers are safe from this right?](#using-falcon)
- [I use JRuby/TruffleRuby…](#using-jruby-truffle)
- [Preforking / Reforking servers](#reforking)
- [What about using mutexes for globals?](#mutexes-for-globals)
- [It’s not doom and gloom, it’s detect and correct](#detect-and-correct)
- [Takeaways](#takeaways) 🥡

If you haven’t read “[Your Ruby programs are always multi-threaded: Part 1](https://jpcamara.com/2024/06/04/your-ruby-programs.html)”, you’re better off starting there. You’ll still get value of this post, but you’ll get the full context by reading both. All of the Table of Contents links under **Part 1** link back to that previous article [↩️](https://jpcamara.com/2024/06/04/your-ruby-programs.html).

&lt;h3 id=&#34;sharing-state-with-fibers&#34;&gt;3b. Sharing state with fibers&lt;/h3&gt;

Now that you’re properly clearing out thread-local state between requests/jobs, you have a new feature to write. Up until this point you could only add an image from a url - users want to upload images from their computer.

It seems complex to take a file from the front end and somehow get it to a background job. So you’re going to do it in your controller - but you want to minimize the chance of timing out while uploading a bunch of files.

&gt; 📝 Since this example app is built with rails, it should instead be using ActiveStorage. But let’s just play along at home 😌

You’ve heard of an interesting library called `async` that makes parallelizing IO[^1] pretty easy and is more predictable than threads. It does it using this special Ruby 3+ FiberScheduler thing 🤔.

As a test you try uploading some files to S3 in the controller. You create an `Async` block, and inside of it you create as many `Async` blocks as there are blocking operations to parallelize:

	class ImagesController
	  before_action :set_user
	
	  def create
	    Async do
	      params[:files].each do |file|
	        Async { S3Upload.new(file).call }
	      end
	    end
	  end
	end
	
	class S3Upload
	  def initialize(file)
	    [@file](https://micro.blog/file) = file
	  end
	
	  def call
	    object.put(body: [@file](https://micro.blog/file))
	  end
	
	  private
	
	  def object
	    client = Aws::S3::Resource.new
	    bucket = client.bucket(ENV[&#34;bucket&#34;])
	    bucket.object(object_path(@file))
	  end
	
	  def object_path
	    [@file](https://micro.blog/file)
	  end
	end

![](https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-page-5.drawio-1.png)

&gt; **source**: jpcamara.com

You test this and they all upload in parallel! Nice! Your request takes as long as the longest upload, and no longer than that. But uploading random file names at the top level of your S3 bucket is not ideal so you prefix the files with the current user id:

	# ...
	Async do
	  S3Upload.new(file, AppContext.user).call
	end
	# ...
	
	class S3Upload
	  def initialize(file, user)
	    [@file](https://micro.blog/file) = file
	    [@user](https://micro.blog/user) = user
	  end
	
	  # ...
	
      def object_path
        name = [@file](https://micro.blog/file)
        &#34;#{@user.id}/#{name}&#34;
      end
	end

You test this and… it raises a `NoMethodError` on `NilClass` when trying to get `user.id`. Huh? You’re sure the user is defined so you print some information:

	class ImagesController
	  before_action :set_user
	
	  def create
	    puts &#34;user[#{AppContext.user}]&#34;
	    # ...
	  end
	end
	
	# ...
	def object_path
	  name = [@file](https://micro.blog/file)
	  puts &#34;upload user[#{AppContext.user}]&#34;
	  puts &#34;upload [#{@user}]&#34;
	  &#34;#{@user.id}/#{name}&#34;
	end

Which prints:

```text
user[#&lt;User id=123&gt;]
upload user[]
upload [@user](https://micro.blog/user)[]
NoMethodError...
```
&lt;h4 id=&#34;wild-fiber&#34;&gt;A wild Fiber appeared!&lt;/h4&gt;

`AppContext.user` is nil when we’re inside that async block… I think it’s time to look at those `Thread.current[]` [docs again](https://docs.ruby-lang.org/en/3.0/Thread.html#method-i-5B-5D)…

&gt; Thread\#[]
&gt; 
&gt; Returns the value of a fiber-local variable 

Well that doesn’t seem right. Doesn’t everyone call them thread-locals? They’re not thread-local… they’re _fiber_-local?

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/9c0c11900d.jpeg&#34; width=&#34;80%&#34; height=&#34;80%&#34; alt=&#34;&#34;&gt;

What exactly does it mean for it to be fiber-local? And how can you fix it so can use your `AppContext` within `Async`?

&lt;h4 id=&#34;ruby-concurrency-layers&#34;&gt;Ruby concurrency has layers&lt;/h4&gt;

The concurrency model in Ruby is multi-layered. The best way to describe it is as a nesting doll - as you pull away the outer layers, you find new layers of concurrency inside.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/7fee80c6bc.jpeg&#34; width=&#34;70%&#34; height=&#34;70%&#34; alt=&#34;&#34;&gt;

&gt; **source**: jpcamara.com

In your own Ruby code, you can easily inspect each layer directly:

	puts &#34;Process #{Process.pid}&#34;
	puts &#34;  Ractor #{Ractor.current}&#34;
	puts &#34;    Thread #{Thread.current}&#34;
	puts &#34;      Fiber #{Fiber.current}&#34;
	
	# Process 123
	#   Ractor #&lt;Ractor:#1 running&gt;
	#     Thread #&lt;Thread:0x0... run&gt;
	#       Fiber #&lt;Fiber:0x0... (resumed)&gt;

Your code is always operating in the context of a specific instance of each layer. Mostly you can access those instances with a call to `current`. Your code runs within a Fiber, which is inside a Thread, which is inside a Ractor, which is inside a Process. The next posts in this series will dig into every layer, but for now you’ve got a fiber problem[^2].

Internally, every `Async` block is run in a new [Fiber](https://docs.ruby-lang.org/en/3.3/Fiber.html). That’s the innermost layer of Ruby concurrency. So that means if you have something that is stored as a _fiber_-local, every time you create a new fiber, you get an empty, fresh set of locals.

	# Fiber.current #&lt;Fiber&gt;
	puts &#34;Main Fiber #{Fiber.current}&#34;
	puts Thread.current[:app_user]
	Fiber.new do
	  puts &#34;Fiber.new #{Fiber.current}&#34;
	  puts Thread.current[:app_user]
	end.resume
	Async do
	  puts &#34;Async #{Fiber.current}&#34;
	  puts Thread.current[:app_user]
	  Async do
	    puts &#34;  Async #{Fiber.current}&#34;
	    puts Thread.current[:app_user]
	    Async do
	      puts &#34;    Async #{Fiber.current}&#34;
	      puts Thread.current[:app_user]
	    end
	  end
	end

Running the above code you get:

```text
Main Fiber #&lt;Fiber:0x00007a77a0b5bbb0 (resumed)&gt;
#&lt;User id=123&gt;
  Fiber.new #&lt;Fiber:0x00007a77a01766b8 main.rb:940 (resumed)&gt;
	
    Async #&lt;Fiber:0x00007a77a0175858 task.rb:326 (resumed)&gt;
	
      Async #&lt;Fiber:0x00007a77a0174fc0 task.rb:326 (resumed)&gt;
	
        Async #&lt;Fiber:0x00007a77a0174cf0 task.rb:326 (resumed)&gt;
```
That’s well and good to know, you think, but how can you fix it? You don’t care about being fiber-local, you’re trying to be thread-local so these fibers can use your `AppContext`🧵.

&lt;h4 id=&#34;actual-thread-locals&#34;&gt;Actual thread-locals&lt;/h4&gt;

Those same [Thread.current docs](https://docs.ruby-lang.org/en/3.3/Thread.html#method-i-5B-5D) point us in the right direction:

&gt; For thread-local variables, see `thread_variable_get` and `thread_variable_set`

Let’s update `AppContext` to use that:

	class AppContext
	  class &lt;&lt; self
	    def user
	      Thread.current.thread_variable_get(:app_user)
	    end
	
	    def user=(user)
          Thread.current.thread_variable_set(:app_user, user)
	      self.locale = user.locale
	    end
	
	    def locale
	      Thread.current.thread_variable_get(:app_locale)
	    end
	
	    def locale=(locale)
	      Thread.current.thread_variable_set(:app_locale, locale)
	    end
	  end
	end

It’s a bit more verbose, but otherwise it feels the same. Run our upload code again and…

```text
user[#&lt;User id=123&gt;]
upload user[#&lt;User id=123&gt;]
upload [@user](https://micro.blog/user)[#&lt;User id=123&gt;]
```
Success 🙌🏼. The data is now actually thread-local. It is still present inside of fibers because those fibers all belong to the current thread[^3].

There are actually _two more_ core Ruby ways[^4] you could handle this situation 😵‍💫. But we’ve covered the two most common options, and it solves the problem at hand. We’ll dig into those other options later in the series.

This seems like it should be a solved problem, you think. Does everyone write this from scratch and encounter these same issues? Is there an easier way? 

Yes, thankfully.

&lt;h4 id=&#34;current-attributes&#34;&gt;CurrentAttributes&lt;/h4&gt;

If you’re in Rails, or you use ActiveSupport, you should be using [`CurrentAttributes`](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html).

`CurrentAttributes` provides the same basic behavior we built (plus more), and integrates seamlessly into both Rails controllers and jobs. It also has first class support from Sidekiq, even without Rails. Here’s the `AppContext` code, now using `CurrentAttributes`:

	class AppContext &lt; ActiveSupport::CurrentAttributes
	  attribute :user, :locale
	
	  def user=(user)
	    super
	    self.locale = user.locale
	  end
	end

At first glance, it’s already a much simpler and clearer setup. But the real benefit is now we don’t have to worry about anything else.

- It hooks into the Rails executor lifecycle, so whenever a web request/job begins or completes, `AppContext.reset` is called
- It internally uses `IsolatedExecutionState`, which is an abstraction around thread and fiber-local state. It is configurable to use thread or fiber-local state, and by default it uses thread-local state so it works out of the box for your example Rails app
- Sidekiq offers [first class support for it](https://www.mikeperham.com/2022/07/29/sidekiq-and-request-specific-context/), so even if you don’t use Rails you can opt-in to resetting behavior, and Sidekiq will automatically load it at the start of your job based on the execution state present when the job was enqueued

Interestingly, `CurrentAttributes` doesn’t use the `Thread.current[]` or `Thread.thread_variable_get` approaches. We’ll talk about that more in a later post.

&lt;h4 id=&#34;request-store&#34;&gt;RequestStore&lt;/h4&gt;

I would not recommend using this library. It only supports `Thread.current`, and it only automatically clears itself for Rails controllers - you’re still on the hook for managing job middleware/callbacks! It also will fail the same way as our example in an `Async` block. You can learn more about it from its [GitHub repository](https://github.com/steveklabnik/request_store).

&lt;h4 id=&#34;back-in-reality-three&#34;&gt;Back in reality, part three…&lt;/h4&gt;

If this example seemed oddly specific again, that’s because this issue is present in some change tracking gems.

In terms of [monthly downloads](https://www.ruby-toolbox.com/projects/paper_trail), [`paper_trail`](https://github.com/paper-trail-gem/paper_trail) is by far the most popular gem for change tracking. And internally it uses `request_store` which means:

- It uses `Thread.current[]`
- It _does_ take care of Rails controller cleanup for you
- it _does not_ take care of job cleanup for you. If you set tracking information anywhere in your jobs, you are susceptible to leaking data between job runs 🙅🏻‍♂️.

If you’re going to use `paper_trail`, _make sure you clear out your state in jobs_. Since it uses `request_store` internally, you can clear out state using that.

	# If using Sidekiq
	class PaperTrailClearMiddleware
	  include Sidekiq::ServerMiddleware
	
	  def call(_worker, _job, _queue)
	    yield
	  ensure
	    RequestStore.clear!
	  end
	end
	
	Sidekiq.configure_server do |config|
	  config.server_middleware do |chain|
	    chain.add PaperTrailClearMiddleware
	  end
	end
	
	# If using only ActiveJob
	class ApplicationJob &lt; ActiveJob::Base
	  after_perform :clear_paper_trail_context
	
	  def clear_paper_trail_context
	    RequestStore.clear!
	  end
	end

There is a gem that does this for Sidekiq only for you, called [request\_store-sidekiq](https://github.com/madebylotus/request_store-sidekiq). It hasn’t been updated in several years, but it does what my middleware example does, automatically for you.

All change tracking gems use some type of thread/fiber state, so here’s the rundown on the remaining popular gems:

- [audited](https://github.com/collectiveidea/audited) is the next most popular but it uses `CurrentAttributes` internally, so you’re good!
- [Logidze](https://github.com/palkan/logidze) uses a block, so it’s basically impossible to leak data between threads. But, it uses `Thread.current[]` internally so it will break if you nest fibers (like `Async` blocks) inside of it
- [PublicActivity](https://github.com/public-activity/public_activity) uses `Thread.current[]` but it’s unclear if it is cleaning it up appropriately. You’ll have to do you own due diligence there

&lt;h3 id=&#34;reusing-objects&#34;&gt;4. Reusing objects&lt;/h3&gt;

You do know that single-threaded servers can still have issues leaking state between requests - like our fiber-local issue. But can they hit real threading issues? Let’s ask GitHub:

&gt; **Us**: Hey GitHub! 
&gt; 
&gt; **GitHub**: Ummm, hi?
&gt; 
&gt; **Us**: You run Unicorn as your web server don’t you? 
&gt; 
&gt; **Github**: Who are you? How did you get in here??
&gt; 
&gt;  **Us**: Can a single-threaded server like Unicorn encounter threading issues?
&gt; 
&gt; **GitHub**: …
&gt; 
&gt; **GitHub**: …yes 😔 [https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/](https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/) 

**TL;DR**

Someone wrote threaded code to save on performance:

	after_action :run_expensive_op_in_thread

It held a reference to a request environment object:

	def run_expensive_op_in_thread
	  Thread.new do
	    env = request.env
	    # some background logic
	  end
	end

That request object got reused between requests - it just cleared its data out instead of creating a new instance:

	def after_request
	  request.env.clear
	end

In certain edge cases, sessions would get reused between users:

	def run_expensive_op_in_thread
	  Thread.new do
	    if not_logged_in
	      login(request.env)
	    end
	  end
	end

So yes… it still involved _explicit_ threading, but in the context of a single-threaded server. This is the crux of that “Ruby is always multi-threaded” thing. Threads pop up all over the place even when you _think_ you’re not using them. If you defensively code assuming threads could eventually be involved, you reduce the risk of these types of issues popping up later.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/race-condition-diagram.webp&#34; width=&#34;600&#34; height=&#34;284&#34; alt=&#34;&#34;&gt;

&gt; **source:** [https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/](https://github.blog/2021-03-18-how-we-found-and-fixed-a-rare-race-condition-in-our-session-handling/)

The fix for the problem was to stop reusing environment objects between requests. Each new request created a new environment object, which means a thread could safely hold onto that environment information without anything leaking in from subsequent requests.

Create new instances. Unless you know they are _incredibly_ expensive, it is not worth the risk of sharing them if it can be avoided.

&lt;h3 id=&#34;race-conditions&#34;&gt;5. Race conditions&lt;/h3&gt;

You’ve been having a tough time with these bugs, yeesh. It’s as if some kind of external force is controlling you to intentionally introduce each of these issues for other people’s benefit. But that obviously can’t be true 🤫.

You’ve got a new requirement. And this one seems pretty… concurrent. You need to coordinate some of your jobs - you split your work into several pieces, run them in individual jobs, then perform one final piece once every job has finished.

How can you coordinate a set of jobs? Maybe there’s a concurrency concept you can try out! 

You read about something called a “Countdown Latch”. You tell it what to countdown from, multiple threads can safely count down using it, and you can have another thread wait until it finishes:

	latch = CountdownLatch.new(10)
	10.times.map do |x|
	  Thread.new do
	    puts &#34;hello #{x}&#34;
	    latch.count_down
	  end
	end
	
	latch.wait
	puts &#34;world!&#34;

This prints out

```text
hello 0
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
world!
```
&gt; The results look ordered but it’s just a coincidence of how it ran. The `CountdownLatch` only helps coordinate completing jobs, nothing internal to their ordering

&lt;h4 id=&#34;threading-concepts&#34;&gt;Threading concepts&lt;/h4&gt;

When you call `latch.wait`, the thread waits until the `count_down` is complete. What would implementing a `CountdownLatch` look like?

	class CountdownLatch
	  def initialize(count)
	    @count = count
	    @mutex = Mutex.new
	    @cond = ConditionVariable.new
	    puts &#34;CountdownLatch initialized with a count of #{@count}&#34;
	  end
	
	  def wait
	    @mutex.synchronize do
	      @cond.wait(@mutex) while @count &gt; 0
	    end
	  end
	
	  def count
	    @mutex.synchronize { @count }
	  end
	
	  def count_down
	    @mutex.synchronize do
	      @count -= 1
	      if @count == 0
	        @cond.broadcast
	      end
	      @count
	    end
	  end
	end

&gt; 📝 The [`concurrent-ruby`](https://github.com/ruby-concurrency/concurrent-ruby) gem comes with its own `CountdownLatch`. I’ve written my own here for demonstration - but if you have a need for one, use theirs, not mine

A `CountdownLatch` is a special form of a computer science concept called a [barrier](https://en.m.wikipedia.org/wiki/Barrier_(computer_science)). Barriers are used to help coordinate execution of multiple threads[^5] - a `CountdownLatch` is a one-time use form of a barrier where a thread can wait until the barrier counts down to zero.

To achieve that, you need a [`Mutex`](https://docs.ruby-lang.org/en/3.3/Thread/Mutex.html) and a [`ConditionVariable`](https://docs.ruby-lang.org/en/3.3/Thread/ConditionVariable.html). `Mutex`s and `ConditionVariable`s are low-level tools available for coordinating access to resources in Ruby code. We’ll dig more into those in “Concurrent, colorless Ruby”. 

For now, know that a `Mutex` can be used to safely lock a section of Ruby code. While you are in a `synchronize` block, any other thread using the same `Mutex` will wait until that block finishes. As far as the coordinating threads are concerned, anything that happens in that block happens atomically - individual pieces which would not normally be thread-safe are treated like a single, safe operation.

Ok. We’ve got our coordinator ✅. Now let’s try applying it to our jobs.

	class TickerJob
	  include Sidekiq::Job
	
	  def perform
	    # do some work
	    latch = CountdownLatch.new(10)
	    remaining = latch.count_down
	    puts &#34;Tick! [#{remaining}]&#34;
	  end
	end
	
	class CoordinatorJob
	  include Sidekiq::Job
	
	  def perform
	    latch = CountdownLatch.new(10)
	    10.times do
	      TickerJob.perform_async
	    end
	    latch.wait
	    puts &#34;All jobs finished!&#34;
	  end
	end
	  end
	end

Which gives us…!

```text
CountdownLatch initialized with a count of 10
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
CountdownLatch initialized with a count of 10
Tick! [9]
```
Uhhhh… hmmm… well that’s not working properly. It never finishes, and every `count_down` gives us `9`. Oh right. You can’t create new countdowns everywhere you need them… you need to _gulp_… _share_ them 😱.

You know the `CountdownLatch` class is thread safe, so that means multiple threads can use an instance of it together safely. You decide to use a global for now to try it out in your jobs… a class-level ivar 😅. Those are problematic, _but_ the countdown code is thread safe so it should be ok, right? This is your time to shine!

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/goldeneye.webp&#34; width=&#34;470&#34; height=&#34;200&#34; alt=&#34;&#34;&gt;

	class AppContext
	  def self.latch
	    @latch ||= CountdownLatch.new(10)
	  end
	end

Just to be safe you try it on some simple threads first…

	10.times.map do
	  Thread.new do
	    latch = AppContext.latch
	    # do some work
	    puts &#34;Count down! [#{latch.count_down}]&#34;
	  end
	end
	
	AppContext.latch.wait

```text
CountdownLatch initialized with a count of 10
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
CountdownLatch initialized with a count of 10
Count down! [9]
```
Well… So much for thread safety? What’s the deal?

You’re seeing `CountdownLatch initialized with a count` repeatedly, so it seems that the instance gets created over and over. Is the `latch` instance different every time?

	10.times.map do |_x|
	  Thread.new do
	    latch = AppContext.latch
	    # do some work
	    puts &#34;Count down! [#{latch.count_down}]&#34;
	    puts &#34;latch id #{latch.object_id}&#34;
	  end
	end
	
	AppContext.latch.wait

🫠 

```text
...
latch id 2700
...
latch id 2720
...
latch id 2740
...
latch id 2760
...
latch id 2780
...
latch id 2800
...
latch id 2820
...
latch id 2840
...
latch id 2860
...
latch id 2880
```
You get a different latch every time. 

&lt;h4 id=&#34;check-then-act&#34;&gt;Memoization / check-then-act&lt;/h4&gt;

You’re hitting a race condition - multiple threads are racing to finish an operation. And this particular situation is so common it’s got a name: “check-then-act”.

In Ruby, the most common case of this is with memoization. 

&gt; 📝 If you don’t know - memoization is one of those funny looking terms, and sounds a bit like if [the priest from princess bride](https://youtu.be/nEe1cTDbXHU?si=OjyohItd-GTD8gdq) tried to say “memorization”.

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/mawwiage.webp&#34; width=&#34;480&#34; height=&#34;260&#34; alt=&#34;&#34;&gt;

&gt; Mem-mwah-zatioooon

&gt; Memoization is just a means for lazy-loading code, or sharing an expensive resource. The first time you need it, you initialize it and store it to a variable you can reuse, like an instance variable.

Memoization is ok within a single thread, but if you’re sharing an object then memoization can lead to “check-then-act” conditions. The `AppContext` code is using memoization to share the `CountdownLatch`:

	@latch ||= CountdownLatch.new(10)

It looks like a one-line call, but is actually multiple statements.

	if !@latch
	  @latch = CountdownLatch.new(10)
	end

You “check” if the `@latch` is present, “then” you “act” on that information. If it’s present, you don’t do anything. If it isn’t, you create a new `CountdownLatch` and set it to `@latch`. Multiple threads cannot run this code in parallel, but they can swap between each other at inopportune times.

- Thread 1 checks, and finds that `@latch` is `nil`. It instantiates `CountdownLatch` to set it to `@latch`. The `initialize` method in `CountdownLatch` has a `puts` statement. That’s blocking IO, so Thread 1 yields and…
- Thread 2 checks, and finds that `@latch` is `nil`. As it instantiates `CountdownLatch`, it yields due to the blocking IO from `puts`…
- Thread 3 checks, and finds that `@latch` is `nil`… seeing a pattern?

The `puts` statement exists very intentionally to force this condition. Without it, the race condition would happen much less frequently. But it could be a logger call, or it could open a connection to an external resource, or create a Tempfile[^6]. Many things can easily causes the `initialize` to yield to other available threads.

If you want to use memoization safely between threads, you need a `Mutex`. Just like `CountdownLatch` uses a `Mutex` internally to `count_down` safely between threads, we need a `Mutex` to safely share our lazy-loaded instance:

	class AppContext
	  @latch_mutex = Mutex.new
	
	  def self.latch
	    return @latch if @latch
	    @latch_mutex.synchronize do
	      @latch ||= CountdownLatch.new(10)
	    end
	  end
	end

&gt; 💰 My personal metric is that the right amount of mutexes in my code is zero. If I am using a mutex, I think hard to figure out a way to avoid it because it means I’m opening up myself and future devs to a lot of cognitive overhead: you need to think critically anytime you make a change relating to mutex code.
&gt; 
&gt; If you’re a library or framework author they may be unavoidable at some point to do interesting or useful things. In my own application code, I can pretty much _always_ avoid them.
&gt; 
&gt; To be perfectly honest, I’m not confident it’s ok to return the `@latch` early if it’s present. I’m pretty sure it is… but is there a memory visibility implication? Am I creating a new type of edge case or race condition? These are the questions you have to ask yourself when you introduce mutexes.

Ok, `AppContex.latch` memoization take three:

	10.times.map do
	  Thread.new do
	    latch = AppContext.latch
	    # do some work
	    puts &#34;Count down! [#{latch.count_down}]&#34;
	    puts &#34;latch id #{latch.object_id}&#34;
	  end
	end
	
	AppContext.latch.wait
	puts &#34;All threads finished!&#34;

_Finally_, you get a correct countdown!

```text
CountdownLatch initialized with a count of 10
Count down! [9]
latch id 2700
Count down! [6]
latch id 2700
Count down! [3]
latch id 2700
Count down! [0]
latch id 2700
Count down! [7]
latch id 2700
Count down! [1]
latch id 2700
Count down! [4]
latch id 2700
Count down! [8]
latch id 2700
Count down! [5]
latch id 2700
Count down! [2]
latch id 2700
All threads finished!
```
Much better! We should be ready for our jobs now. But is this really the right direction? You’ve resolved the “check-then-act” issue, but you realize there are some pretty obvious limits to this approach:

- Right now we can have one instance of the `CountdownLatch` to share, and it’s hard-coded to 10… What would be the best way to share an object, but also make it configurable?
- What if you have more than one job server? Multiple job servers can pull jobs from the same queue 🤔. You can only synchronize across threads if they live in the same server process. Outside of that, your mutex’s ability to lock anything is just a wish in the breeze.

You need something to share, that’s independent of what’s in memory and can represent multiple coordinating sets of jobs.

&lt;h4 id=&#34;read-modify-write&#34;&gt;read-modify-write&lt;/h4&gt;

You’re going to branch a bit outside of threads for concurrency, and get distributed. You rewrite the `CountdownLatch` to:

- Allow specifying an id to key off of, so we can support more than one `CountdownLatch` at a time
- Store the countdown value in [Redis](https://redis.io/), so it can be accessed and count down from anywhere[^7]
- Independently create `CountdownLatch` instances so you don’t need to share the instance itself. You are sharing the Redis database instead.

```ruby
class DistributedCountdownLatch
  def initialize(id, count = nil)
    [@id](https://micro.blog/id) = id
    @redis = Redis.new
    @redis.set(key, count) if count
  end
	
  def wait
    while current &gt; 0
      puts &#34;Remaining in countdown: [#{current}]&#34;
      sleep(1)
    end
  end
	
  def current
    @redis.get(key).to_i
  end
	
  def count_down
    new_current = current - 1
    @redis.set(key, new_current)
    new_current
  end
	
  def key
    &#34;latch_#{@id}&#34;
  end
end
```
Running it across some threads looks promising.

	latch = DistributedCountdownLatch.new(&#34;hello&#34;, 10)
	10.times.map do |x|
	  Thread.new do
	    puts &#34;hello #{latch.count_down}&#34;
	  end
	end
	
	latch.wait
	puts &#34;world&#34;

So far so good!

```text
Remaining in countdown: [10]
hello 9
hello 8
hello 7
hello 6
hello 5
hello 4
hello 3
hello 2
hello 1
Remaining in countdown: [1]
hello 0
world
```
You run it several times and feel ready to use it in your Sidekiq jobs!

	class TickerJob
	  include Sidekiq::Job
	
	  def perform
	    # do some work
	    latch = DistributedCountdownLatch.new(&#34;tick&#34;)
	    remaining = latch.count_down
	    puts &#34;Tick! [#{remaining}]&#34;
	  end
	end
	
	class CoordinatorJob
	  include Sidekiq::Job
	
	  def perform
	    latch = DistributedCountdownLatch.new(&#34;tick&#34;, 10)
	    10.times do
	      TickerJob.perform_async
	    end
	    latch.wait
	    puts &#34;All jobs finished!&#34;
	  end
	end

Let’s see how it goes:

```text
Tick! [9]
Tick! [9]
Tick! [8]
Tick! [7]
Tick! [7]
Tick! [6]
Tick! [6]
Remaining in countdown: [6]
Tick! [5]
Tick! [5]
Tick! [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining in countdown: [4]
Remaining...
```
Poorly. It went poorly 🥺.

If we walk through the code again:

- Thread 1 reads the value from Redis. That’s blocking IO, so Thread 1 yields and…
- Thread 2 reads the value from Redis. That’s blocking IO, so Thread 2 yields and…
- Thread 3 reads the value from Redis. That’s blocking IO, so Thread 3 yields and…
- Thread 1 gets the value back which is 10. Its subtracts 1 from it and writes 9 to Redis. That’s blocking IO, so Thread 1 yields and…
- Thread 2 gets the value back which is 10. Its subtracts 1 from it and writes 9 to Redis. That’s blocking IO, so Thread 1 yields and…

🙅🏻‍♂️👈🏼👉🏼👇🏼👆🏼🙅🏻‍♂️

You’re experiencing another race condition, and once again it’s common enough to have a name: “read-modify-write”. You’re “read”ing in a value in its current state from Redis, “modify”ing it, then “write”ing it back to Redis. The problem is that for shared resources, each reader “read”s the value in the same state. We have to coordinate the way each client is pulling data from Redis. 

Race conditions happen on shared resources - those shared resources don’t need to live on the same machine.

&gt; 📝 Redis is a single-threaded database, meaning it can only ever execute one thing at a time, no matter how many clients are connected. But being single-threaded does not do anything to fix race conditions. It may only execute one operation at a time, but nothing about that guarantees you won’t execute those operations out of order.

What can you do?

We need something like a `Mutex`, but a `Mutex` that can work across independent servers. A `Mutex` is just a lock you acquire on a resource - can you get that with Redis?

You can simulate a lock using the Redis operation `SET`:

	class DistributedCountdownLatch
	  class WatchError &lt; StandardError; end
	
	  # ... same code as before
	
	  def count_down
	    lock_key = &#34;lock_#{key}&#34;
	    loop do
	      # `nx` stands for `not exists`
	      # `ex` means the lock key will expire in 10 seconds
	      if @redis.set(lock_key, 1, nx: true, ex: 10)
	        new_count = current - 1
	        # `multi` allows every operation to run atomically
	        @redis.multi do |transaction|
	          transaction.set(key, new_count)
	          transaction.del(lock_key)
	        end
	        return new_count
	      end
	      sleep 0.1
	    end
	  end
	end

- First you set a key using the `nx`, or “not exists” flag. If a key already exists, the operation fails. In that case you `sleep` for `0.1` seconds, then try again.
- Second you get the current value.
- Third, you perform a Redis `MULTI` operation, which is a kind of Redis database transaction. We will `SET` the new count, and `DEL`ete our lock key, in one atomic call to Redis. It all works, or it all fails.

&gt; 📝 This type of lock is called a “Pessimistic lock”. Pessimistic locking means you must obtain exclusive access to the lock before running an operation, and retry or raise an error if that is not possible. The alternative is “optimistic” locking, which means you attempt to run the operation, and before committing verify that no other operations have taken place. If they have you either have to retry, or raise an error.
&gt; 
&gt; 📝 If you’re going to use Redis as a distributed lock, you should use the [redlock-rb gem](https://github.com/leandromoreira/redlock-rb), which implements the Redis teams recommended algorithm for locking, the [“Redlock” algorithm](https://redis.io/docs/latest/develop/use/patterns/distributed-locks/).

By creating a simple form of a distributed `Mutex`, this fixes our race condition! Now we will only attempt the update once we acquire the lock, and we’ll retry until we can acquire it:

```text
Tick! [9]
Tick! [8]
Remaining in countdown [8]
Tick! [7]
Tick! [6]
Tick! [5]
Tick! [4]
Tick! [3]
Tick! [2]
Tick! [1]
Remaining in countdown [1]
Tick! [0]
All jobs finished!
```
&lt;h4 id=&#34;coordinating-jobs&#34;&gt;Coordinating jobs&lt;/h4&gt;

Even having fixed the “check-then-set” and “read-modify-write” issues, trying to coordinate your jobs using this approach is a lot of work to get right. I wouldn’t recommend it, even though the code is slightly more accurate now.

If you’re interested in more appropriate ways to coordinate jobs:

- Some job servers offer a feature for running and coordinating jobs in batches, with optional success/finish/error callbacks. 
	- Sidekiq offers this in their [paid pro tier](https://sidekiq.org/products/pro.html)
	- [GoodJob](https://github.com/bensheldon/good_job?tab=readme-ov-file#batches) offers batch support
	- I am [working on batch support](https://github.com/rails/solid_queue/pull/142) for [SolidQueue](https://github.com/rails/solid_queue) - feel free to leave feedback!
- If you still are interested in a `CountdownLatch` approach:
	- Redis has a built-in `INCR` and `DECR` method, which handle these updates for you atomically
	- ActiveRecord has an `increment` and `decrement` method you could use to `decrement` a value on a record atomically.

&lt;h4 id=&#34;read-modify-write-for-days&#34;&gt;read-modify-write shows up in many ways&lt;/h4&gt;

There’s a very good chance you have code susceptible to read-modify-write issues right now. Aside from reading a value and then modifying it, the most common situation I see is around enforcing uniqueness constraints:

- You expect a record in a database to be unique, but you don’t have uniqueness constraints at the database-level.
- You “read” the database and find the record doesn’t exist.
- Another request comes in at the same time and also “read”s the database and finds the record doesn’t exist.
- In this case the “modify” is creating the record. Each request now attempts to create the record.
- With no database-level uniqueness constraint, the result is that each request thinks the record doesn’t exist, and you “write” two (or more) records, violating your uniqueness requirement.

&lt;h3 id=&#34;parting-thoughts&#34;&gt;Parting thoughts&lt;/h3&gt;

Phew! That was a lot. We’ve gone deep into 5 different threading mistakes, ways to identify them, and ways to fix them. Let’s finish up with a few topics addressing how to look out for threading issues in your future endeavors, as well as answering some possible remaining questions!

&lt;h3 id=&#34;tips-for-gems&#34;&gt;Tips for auditing gems 💎 &lt;/h3&gt;

Here are some notes on what to look for when auditing a gem for Thread safety.

- If the gem interface is a static method, check the source. It might be an indicator of class-level ivars or thread/fiber-local data
- If the amount of code is small, take some time to understand it. Keep the “threading mistakes” we’ve discussed in mind and see if anything sticks out to you
- If there is a lot of code, look through and make sure it has active maintenance and maintainers. You’re not likely to learn a larger code base so at least know it’s being actively maintained. There have been studies to show that the longer-lived a codebase is, the likelier it is developers and users will find and remove threading issues.
- If there’s low activity, a lot of code, and you can’t take the time to understand it, you probably want to avoid it
- Make sure `main` has been released into a gem version. The repository can sometimes be misleading - it may have a fix explaining an issue you’re hitting that hasn’t been released
- If a gem is unmaintained or inactive, it doesn’t mean you can’t use it. _But_, effectively you own it. If you hit issues, you’ll be the one to fix them

&lt;h3 id=&#34;using-falcon&#34;&gt;I use &lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;Falcon&lt;/a&gt; - Fibers are safe from this right?&lt;/h3&gt;

The short answer is “assume not”.

We’ll dig more into the nuances of fibers in “Concurrent, colorless Ruby: Fibers”, but you are always safer to run as if threads will eventually be involved. Falcon can run using Threads in addition to Fibers, for instance.

While Fibers _are_ more deterministic, even with the FiberScheduler, they are still susceptible to some of the same issues as Threads. 

Quick question: Can race conditions happen with Fibers? We’ll walk through that in “Concurrent, colorless Ruby: Fibers”. Or see the handy answer key below.

**.seY: rewsnA**

&lt;h3 id=&#34;using-jruby-truffle&#34;&gt;I use JRuby/TruffleRuby…&lt;/h3&gt;

Good news! These examples will break really fast for you! Congrats!

We’ll talk more about this later in the series. But know for now that JRuby and TruffleRuby use Threads without any implicit locks (ie, the GVL). You will hit threading issues much faster in those environments than in CRuby.

&lt;h3 id=&#34;reforking&#34;&gt;Preforking / Reforking servers&lt;/h3&gt;

Preforking and Reforking servers _do_ share resources. They use `fork`, which initially shares the entire memory space between two processes using something called Copy-On-Write memory. That means things which are not safe to share, like file handles, _are_ shared by default.

Thankfully many members of the Ruby community have worked to make that as safe as possible for most popular libraries. We’ll talk more on this topic later in the series, discussing areas where it is handled automatically and areas where it isn’t.

&lt;h3 id=&#34;mutexes-for-globals&#34;&gt;What about using mutexes for globals?&lt;/h3&gt;

You may have seen some of the global examples and thought: “couldn’t you keep them global the way they are and keep them safe using a mutex?”

Good question!

That would mean using a lock to provide coordinated access to your global state. Like taking our Fibonacci example and using a lock here:

	require &#34;monitor&#34;
	
	class Result
	  attr_accessor :value
	end
	
	class Fibonacci
	  @fib_monitor = Monitor.new
	
	  class &lt;&lt; self
	    def result=(value)
	      @fib_monitor.synchronize { @result = value }
	    end
	
	    def result
	      @fib_monitor.synchronize { @result }
	    end
	
	    def calculate(n)
	      @fib_monitor.synchronize do
	        self.result = Result.new
	        result.value = fib(n)
	      end
	    end
	
	    def fib(n)
	      return n if n &lt;= 1
	
	      fib(n - 1) + fib(n - 2)
	    end
	  end
	end

&gt; 📝 We actually use a `Monitor` here instead of a `Mutex` because it supports reentrancy. That means we can re-enter the synchronize block from the same thread, which is necessary in our example. Otherwise, it is _largely_ the same as a `Mutex`.
&gt; 
&gt; 📝 You may have noticed I removed the `attr_accessor` and I both set and get using the `Monitor`. I did this out of caution for a concept of “visibility” of shared resources between Threads. We’ll discuss that more later.

Now we’ll run it again using `run_forever`:

	answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
	
	run_forever do |iteration|
	  n = iteration % answers.size
	  Fibonacci.calculate(n)
	  answer = answers[n]
	  result = Fibonacci.result.value
	
	  if result != answer
	    raise &#34;[#{result}] != [#{answer}]&#34;
	  end
	rescue =&gt; e
	  puts &#34;Iteration[#{iteration}] #{e.message}&#34;
	end

It works. We don’t get any errors. The mutex allows the code to treat multiple operations as a single operation. Are there downsides?

- If you’re going to use a `Monitor`/`Mutex` on a piece of a global state - keep your mutex scope small to reduce locking overhead
- Don’t hog the GVL. Make sure you don’t wrap your GVL-releasing operations like blocking IO in a `Mutex`.
- Don’t get your mutex scope wrong. If you get your scoping wrong, you may still run into issues like memory inconsistency between Threads.

We’ll dig into GVL hogs and surprising behaviors in “Colorless, concurrent Ruby”. For now the short answer is: you can, but avoid it, if possible. It may hurt your ability to parallelize your code.

&lt;h3 id=&#34;detect-and-correct&#34;&gt;It’s not doom and gloom, it’s detect and correct&lt;/h3&gt;

When I highlight a bunch of pitfalls you might hit, it feels like I’m being a virtual storm cloud, just trying to ruin your sunny coding day. In my post [PgBouncer is useful, important, and fraught with peril](https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html), I highlight the tricky parts to inform but I still use the tool happily every day!

It’s the same for Ruby and threading concerns. Almost every language has these same problems, or worse[^8]. Threads are easy to get wrong, so you want to take some golden paths and stay simple when using them. 

Your code is safer if you assume you’re always using threads. You are. And if by some oddity you _really_ _aren’t_, there’s a strong chance you will be eventually.

&lt;h3 id=&#34;takeaways&#34;&gt;Takeaways 🥡&lt;/h3&gt;

- Your Ruby code is threaded. Just assume it is and forget semantics.
- The GVL can make threading bugs harder to produce - if you see suspicious code that _seems_ to work, try again using `run_forever`
- Think thread safety
- If you write any explicitly threaded code, coordinate threads or share data - use existing vetted tools like job batches and `concurrent-ruby`
- Other runtimes like Ruby/TruffleRuby will produce bugs much faster
- As much as possible, don’t share resources between threads. Forget your lessons from childhood: sharing is bad.

### Next up
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/7fee80c6bc.jpeg&#34; width=&#34;50%&#34; height=&#34;50%&#34; alt=&#34;&#34;&gt;

&gt; **source**: jpcamara.com

This is the second step into this Ruby concurrency series. Next up we’ll discuss the concept of “colorless” programming, and how it enhances the Ruby experience. We’ll also take our first dive into how the layers of Ruby work, starting with Threads.

More soon 👋🏼!

[^1]:	And any blocking operation

[^2]:	💩 

[^3]:	There is a short section in the thread docs that describes this behavior as well [https://docs.ruby-lang.org/en/3.3/Thread.html#class-Thread-label-Fiber-local+vs.+Thread-local](https://docs.ruby-lang.org/en/3.3/Thread.html#class-Thread-label-Fiber-local+vs.+Thread-local)

[^4]:	`Fiber#store` and adding attr accessors to Thread/Fiber

[^5]:	The concept can also be used for other units of concurrency, like fibers

[^6]:	Or use a file mutex [https://github.com/yegor256/futex](https://github.com/yegor256/futex)

[^7]:	Other databases would also work, but the Redis data model is the simplest to get going for a simple value. Sidekiq works off of Redis as well, so you already would have it in your stack

[^8]:	So don’t let your Python or Java friends try to bully you about it 😉. JavaScript can suffer similar issues as well.

    I’ve never supported a production Rust or Elixir application so I can’t speak from experience, but those are the only two languages I can think of that might fare a lot better in this regard (and similar languages like F#, Gleam, Clojure, etc). 

    Elixir because of its pure functional approach. Rust because it’s one of the first non-functional languages I’ve ever encountered that has strong consistency baked into its core. They eliminate a class of threading bugs by their nature, but can still experience issues unique to how they work.
</source:markdown>
    </item>
    
    <item>
      <title>Your Ruby programs are always multi-threaded: Part 1</title>
      <link>https://jpcamara.com/2024/06/04/your-ruby-programs-are-always.html</link>
      <pubDate>Tue, 04 Jun 2024 05:00:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2024/06/04/your-ruby-programs-are-always.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/image-5-13-24-7-52pm.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your Ruby programs are always multi-threaded: Part 1&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html&#34;&gt;Your Ruby programs are always multi-threaded: Part 2&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html&#34;&gt;Consistent, request-local state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/07/15/ruby-methods-are.html&#34;&gt;Ruby methods are colorless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/08/26/the-thread-api.html&#34;&gt;The Thread API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html&#34;&gt;Bitmasks, Ruby Threads and Interrupts, oh my!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html&#34;&gt;When good threads go bad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thread and its MaNy friends&lt;/li&gt;
&lt;li&gt;Fibers&lt;/li&gt;
&lt;li&gt;Processes, Ractors and alternative runtimes&lt;/li&gt;
&lt;li&gt;Scaling concurrency with streaming&lt;/li&gt;
&lt;li&gt;Abstracted, concurrent Ruby&lt;/li&gt;
&lt;li&gt;Closing thoughts, kicking the tires and tangents&lt;/li&gt;
&lt;li&gt;How I dive into CRuby concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re reading “Your Ruby programs are always multi-threaded: Part 1”. I’ll update the links as each part is released, and include these links in each post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#its-all-threaded&#34;&gt;It’s all threaded&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#how-threaded-is-it&#34;&gt;Ok but how threaded is it &lt;em&gt;really&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#threading-mistakes&#34;&gt;Threading mistakes&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;#sharing-ivars&#34;&gt;Sharing module/class instance variables&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#heisenbugs&#34;&gt;Heisenbugs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#ruby-internals&#34;&gt;Ruby internals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#back-in-reality&#34;&gt;Back in reality…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&lt;a href=&#34;#copying-state&#34;&gt;Copying state to instances&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#back-in-reality-two&#34;&gt;Back in reality… part two&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3a. &lt;a href=&#34;#cleaning-up&#34;&gt;Cleaning up thread state&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#using-sidekiq&#34;&gt;If you’re using Sidekiq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#using-activejob&#34;&gt;If you’re using ActiveJob&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Part 2&lt;/strong&gt;&lt;/em&gt;
&lt;ul&gt;
&lt;li&gt;3b. &lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#sharing-state-with-fibers&#34;&gt;Sharing state with fibers&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#wild-fiber&#34;&gt;A wild Fiber appeared!&lt;/a&gt; 🌾&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#ruby-concurrency-layers&#34;&gt;Ruby concurrency has layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#actual-thread-locals&#34;&gt;Actual thread-locals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#current-attributes&#34;&gt;CurrentAttributes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#request-store&#34;&gt;RequestStore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#back-in-reality-three&#34;&gt;Back in reality… part three&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#reusing-objects&#34;&gt;Reusing objects&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol start=&#34;5&#34;&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#race-conditions&#34;&gt;Race conditions&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#threading-concepts&#34;&gt;Threading concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#check-then-act&#34;&gt;Memoization / check-then-act&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write&#34;&gt;read-modify-write&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#coordinating-jobs&#34;&gt;Coordinating jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write-for-days&#34;&gt;read-modify-write shows up in many ways&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#parting-thoughts&#34;&gt;Parting thoughts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#tips-for-gems&#34;&gt;Tips for auditing gems&lt;/a&gt; 💎&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#using-falcon&#34;&gt;I use Falcon - Fibers are safe from this right?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#using-jruby-truffle&#34;&gt;I use JRuby/TruffleRuby…&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#reforking&#34;&gt;Preforking / Reforking servers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#mutexes-for-globals&#34;&gt;What about using mutexes for globals?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#detect-and-correct&#34;&gt;It’s not doom and gloom, it’s detect and correct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jpcamara.com/2024/06/23/your-ruby-programs.html#takeaways&#34;&gt;Takeaways&lt;/a&gt; 🥡&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;its-all-threaded&#34;&gt;It&#39;s all threaded&lt;/h2&gt;
&lt;p&gt;If you run a web server using &lt;a href=&#34;https://github.com/puma/puma&#34;&gt;Puma&lt;/a&gt;, &lt;a href=&#34;https://github.com/socketry/falcon&#34;&gt;Falcon&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, &lt;a href=&#34;https://github.com/ruby/webrick&#34;&gt;Webrick&lt;/a&gt;, or &lt;a href=&#34;https://github.com/ohler55/agoo&#34;&gt;Agoo&lt;/a&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, you use threads.&lt;/p&gt;
&lt;p&gt;If you run background jobs using &lt;a href=&#34;https://github.com/sidekiq/sidekiq&#34;&gt;Sidekiq&lt;/a&gt;, &lt;a href=&#34;https://github.com/bensheldon/good_job&#34;&gt;GoodJob&lt;/a&gt;, &lt;a href=&#34;https://github.com/rails/solid_queue&#34;&gt;SolidQueue&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, or &lt;a href=&#34;https://github.com/que-rb/que&#34;&gt;Que&lt;/a&gt;, you use threads.&lt;/p&gt;
&lt;p&gt;If you use &lt;a href=&#34;https://github.com/rails/rails&#34;&gt;ActiveRecord&lt;/a&gt;, or &lt;a href=&#34;https://github.com/rails/rails&#34;&gt;Rails&lt;/a&gt;, you use threads.&lt;/p&gt;
&lt;p&gt;If you use the &lt;a href=&#34;https://github.com/newrelic/newrelic-ruby-agent&#34;&gt;NewRelic&lt;/a&gt;, &lt;a href=&#34;https://github.com/DataDog/dd-trace-rb&#34;&gt;DataDog&lt;/a&gt;, &lt;a href=&#34;https://github.com/getsentry/sentry-ruby&#34;&gt;Sentry&lt;/a&gt;, &lt;a href=&#34;https://github.com/launchdarkly/ruby-server-sdk&#34;&gt;LaunchDarkly&lt;/a&gt;, or &lt;a href=&#34;https://github.com/honeybadger-io/honeybadger-ruby&#34;&gt;Honeybadger&lt;/a&gt; gems, you use threads.&lt;/p&gt;
&lt;p&gt;If you use &lt;a href=&#34;https://github.com/ruby/net-http&#34;&gt;net/http&lt;/a&gt; (or any gem internally using it like &lt;a href=&#34;https://github.com/jnunemaker/httparty&#34;&gt;HTTParty&lt;/a&gt; or &lt;a href=&#34;https://github.com/rest-client/rest-client&#34;&gt;RestClient&lt;/a&gt;), &lt;a href=&#34;https://gitlab.com/os85/httpx&#34;&gt;httpx&lt;/a&gt;, or the &lt;a href=&#34;https://github.com/ruby/timeout&#34;&gt;Timeout&lt;/a&gt;&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; gem, you use threads 🧵.&lt;/p&gt;
&lt;p&gt;And because of the above, even if you use a forked, single-threaded server like &lt;a href=&#34;https://github.com/Shopify/pitchfork&#34;&gt;Pitchfork&lt;/a&gt; or &lt;a href=&#34;https://github.com/defunkt/unicorn&#34;&gt;Unicorn&lt;/a&gt;, or run &lt;a href=&#34;https://github.com/puma/puma&#34;&gt;Puma&lt;/a&gt; in worker mode with no additional threads, or run jobs with &lt;a href=&#34;https://github.com/resque/resque&#34;&gt;Resque&lt;/a&gt;, or just use a basic Ruby rake task, you are using threads!&lt;/p&gt;
&lt;p&gt;The point isn’t to be exhaustive. There are plenty more gems which use threads internally - you’re probably using some I haven’t listed. I’m also intentionally avoiding nuance - there are varying levels of threaded-ness.&lt;/p&gt;
&lt;p&gt;The point &lt;em&gt;is&lt;/em&gt; to demonstrate that threads are &lt;strong&gt;everywhere&lt;/strong&gt; in Ruby, even if you are not working with them explicitly. Always write your code to be as thread safe as possible. Act as if you’re in highly threaded code and you’ll live a much happier life.&lt;/p&gt;
&lt;h2 id=&#34;how-threaded-is-it&#34;&gt;Ok but how threaded is it &lt;em&gt;really&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;The degree of thread safety concern you need to have &lt;em&gt;does&lt;/em&gt; vary depending on how threaded your Ruby environment is.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sidekiq, Puma, SolidQueue, or GoodJob: very threaded. If there are threading bugs in code you use they will come up, eventually.&lt;/li&gt;
&lt;li&gt;Falcon: Can be configured to be threaded. If it is, threading bugs will come up eventually. By default it only uses Fibers, which can still be susceptible to some concurrency issues.&lt;/li&gt;
&lt;li&gt;Pitchfork, Unicorn or Resque: not threaded, but you’re probably using threaded gems or writing threaded code. I’ll discuss later how these gems can still hit threading issues on process based servers, and other concurrency adjacent issues you might encounter.&lt;/li&gt;
&lt;li&gt;A one-off Ruby script with no gems - you exist in a thread but if it’s just something like processing files sequentially, practically not threaded. You’re probably fine, but YMMV 😬&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s not just about writing explicitly threaded code that is safe to run. If you’re using &lt;code&gt;Thread.new&lt;/code&gt;, you know you’re working with threaded code, and you know you need to proceed carefully. If you don’t know that, be careful! Threading has many sharp edges - it’s easy to get wrong.&lt;/p&gt;
&lt;p&gt;Threads introduce non-determinism into your code - what works on one execution may not work on the next, even with identical inputs. It can lull you into a false sense of security, because that failure state may not appear for a long time. Writing safe threaded code takes careful analysis and planning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/24c9850ebd.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: Eli and JP Camara, &lt;a href=&#34;https://x.com/logicalcomic&#34;&gt;https://x.com/logicalcomic&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Writing safe threaded code can be difficult, but writing safe non-threaded Ruby code in a threaded environment doesn’t have to be. There are some top mistakes I’ve seen (or personally made) to keep an eye out for in your own code, and code in gems you bring into your projects. Let’s take a look at them, and we’ll learn some core Ruby/CRuby principles along the way as well.&lt;/p&gt;
&lt;h2 id=&#34;threading-mistakes&#34;&gt;Threading mistakes&lt;/h2&gt;
&lt;h3 id=&#34;sharing-ivars&#34;&gt;Sharing module/class instance variables&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 For brevity, I’ll be referring to instance variables as “ivars”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If there’s a classic example of what &lt;em&gt;not&lt;/em&gt; to do for thread safety, it&amp;rsquo;s maintaining and modifying class-level ivars&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Not knowing that, you embark on implementing some file processing code. Finding yourself in need of code that splits a file into chunks by newline, you write the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FileSplitter
  def self.split(file_path, max_lines)
    lines = []
    files = []
    File.open(file_path) do |file|
      file.each_line do |line|
        lines &amp;lt;&amp;lt; line
        if lines.size &amp;gt; max_lines
          output = Tempfile.new
          lines.each do |line|
            output.write(line)
          end
          lines = []
          files &amp;lt;&amp;lt; output
        end
      end

      # handle any remaining lines
      if lines.size &amp;gt; 0
        output = Tempfile.new
        lines.each do |line|
          output.write(line)
        end
        files &amp;lt;&amp;lt; output
      end

      files
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;split&lt;/code&gt; method takes a file path, a number of max lines per file, and returns an array of Tempfiles. Each Tempfile contains a chunk of the original file. In some cases you upload each chunk to a file service like S3, in other cases you hand them off to methods for further processing&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The method is kind of hefty, and there’s some clear duplication so you decide to refactor.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/6d3eb93e-bff9-485f-b91f-1c929ca1ba77.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: &lt;a href=&#34;https://theycantalk.com/post/710363232083361793/plans&#34;&gt;https://theycantalk.com/post/710363232083361793/plans&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You decide the logical split in the code is around writing to the temp files, a new method you’ll call &lt;code&gt;write_lines&lt;/code&gt;. There are so many variables that it just seems easier to use ivars instead of passing everything around.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FileSplitter
  def self.split(file_path, max_lines)
    @lines = []
    @files = []
    File.open(file_path) do |file|
      file.each_line do |line|
        @lines &amp;lt;&amp;lt; line
        write_lines(max_lines)
      end
      # force remaining to write
      write_lines
      @files
    end
  end

  def self.write_lines(max = nil)
    return if @lines.empty?

    if max.nil? || @lines.size &amp;gt; max
      @output = Tempfile.new
      @lines.each do |line|
        @output.write(line)
      end
      @lines = []
      @files &amp;lt;&amp;lt; @output
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the method is at the class level, the ivars are class level as well. For any code calling these methods, they only exist once. Every thread shares the same class object.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;del&gt;📝 If this doesn’t &lt;em&gt;look&lt;/em&gt; like class-level ivars, you may be used to the more explicit syntax using double at-signs. For example, &lt;code&gt;@@lines&lt;/code&gt;. These are equivalent, but when you’re inside a class method, class-level ivars only need one at-sign. When inside a class method, the instance is the class itself.&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Another format of this would be a class level &lt;code&gt;attr_accessor&lt;/code&gt;. So if you see a class method with &lt;code&gt;self.lines = []&lt;/code&gt; or see code like &lt;code&gt;FileSplitter.lines = []&lt;/code&gt;, these can &lt;em&gt;also&lt;/em&gt; be class level ivars!&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Some commenters have pointed at that the above is not accurate - until I update it more accurately, see the &lt;a href=&#34;https://www.ruby-lang.org/en/documentation/faq/8/&#34;&gt;ruby lang docs&lt;/a&gt; for more information about class level instance variable differences&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You are running this code in a background job using Sidekiq, running with multiple threads on CRuby 3.3. Most of the time the job runs fine, but you’ve received a few reports from people missing data, and some people even seeing data that wasn’t in their files! Uh oh, what is happening?!&lt;/p&gt;
&lt;p&gt;The problem is that every thread is sharing the same data, and modifying it concurrently. If multiple jobs are running and calling &lt;code&gt;FileSplitter.split&lt;/code&gt;, they are all trying to write to the same piece of memory. We can follow along with the code line by line to see where things break down:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;### Thread 1
# FileSplitter.split(path, max) -&amp;gt;
@lines = []
@files = []
# ...
@output = Tempfile.new
@output.write(line) # `write` causes the thread to sleep so Thread 2 starts

### Thread 2
# Calling `split` resets the instance variables
# Now each thread is using the same variables!
# FileSplitter.split(path, max) -&amp;gt;
@lines = []
@files = []
# ...
# Next time it wakes up, Thread 1 will be appending to the same array as Thread 2 💀 
@lines &amp;lt;&amp;lt; line
# ...
# Thread 1&#39;s Tempfile is overwritten and lost, along with anything written to it 👋🏼
@output = Tempfile.new
@output.write(line) # Now `write` causes Thread 2 to sleep

### Thread 1 wakes up and continues processing
# The next line from Thread 1 gets written to the output file from Thread 2
# We are now 100% off the rails...
# Pour one out for our mix and matched 
# user data ☠️☠️☠️
@lines.each do |line|
  @output.write(line)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the same flow, visualized:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-page-1.drawio-3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The simplest advice about the above code?&lt;/p&gt;
&lt;p&gt;Don’t use shared instance variables in classes, ever 🙅🏻‍♂️. Assume that a class level ivar is a &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Code_smell&#34;&gt;code smell&lt;/a&gt;, until proven otherwise.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 There is a caveat to this, and its configuration and initialization class level ivars. It is common to set global configuration values and macros&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt; using class level ivars. The difference is that these are written once on load of your application, and then only ever &lt;em&gt;read&lt;/em&gt; past that point. If they are not properly frozen you &lt;em&gt;could&lt;/em&gt; still corrupt them by modifying them during program execution. Which is why things like Rails middleware &lt;code&gt;freeze&lt;/code&gt; the middleware array to try and avoid this issue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;How can we make this code thread safe? Use regular Ruby instances. We don’t have to change much.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FileSplitter
  def initialize(file_path, max_lines)
    @file_path = file_path
    @max_lines = max_lines
    @lines = []
  end

  def split
    @files = []

    File.open(@file_path) do |file|
      file.each_line do |line|
        @lines &amp;lt;&amp;lt; line
        write_lines(max_lines)
      end
      # force remaining to write
      write_lines
      @files
    end
  end

  private

  def write_lines(max = nil)
    return if @lines.empty?

    if max.nil? || @lines.size &amp;gt; max
      # Also remove @output as an ivar,
      # we only use it right here so it
      # being an ivar wasn&#39;t necessary 
      output = Tempfile.new
      @lines.each do |line|
        output.write(line)
      end
      @lines = []
      @files &amp;lt;&amp;lt; output
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you use this code, each thread creates its own &lt;code&gt;FileSplitter&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;### Thread 1
splitter = FileSplitter.new(path, max)
splitter.split

### Thread 2
splitter = FileSplitter.new(path, max)
splitter.split
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each thread now safely uses its own independent, isolated instance 😌.&lt;/p&gt;
&lt;h4 id=&#34;heisenbugs&#34;&gt;Heisenbugs&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://pbmo.files.wordpress.com/2012/11/walter-white1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: &lt;a href=&#34;https://thedailyomnivore.net/2012/11/30/heisenbug/&#34;&gt;https://thedailyomnivore.net/2012/11/30/heisenbug/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A heisenbug is a software bug that seems to disappear or alter its behavior when one attempts to study it.&lt;/p&gt;
&lt;p&gt;The term is a pun on the name of Werner Heisenberg, the physicist who first asserted the observer effect of quantum mechanics, which states that the act of observing a system inevitably alters its state.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The effects of sharing class-level ivars can be deceptive. For instance, try running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Result
  attr_accessor :value
end

class Fibonacci
  class &amp;lt;&amp;lt; self
    attr_accessor :result

    def calculate(n)
      self.result = Result.new
      self.result.value = fib(n)
    end

    def fib(n)
      return n if n &amp;lt;= 1
      fib(n - 1) + fib(n - 2)
    end
  end
end

answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

answers.size.times.map do |n|
  Thread.new do
    Fibonacci.calculate(n)
    answer = answers[n]
    result = Fibonacci.result.value

    if result != answer
      raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
    end
  end
end.map(&amp;amp;:join)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have a naive&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;, basic Fibonacci generator, and we verify each result against an array of precomputed results. We spawn as many threads as there are precomputed results to calculate against. We’re storing the result in a class-level ivar that is being shared across threads - that shouldn’t end well! If any results don’t match, we raise an error.&lt;/p&gt;
&lt;p&gt;Run this example and… it doesn’t fail in CRuby 🤔. This might lull you into thinking it isn’t an issue.&lt;/p&gt;
&lt;p&gt;Is CRuby somehow thread safe? &lt;strong&gt;No&lt;/strong&gt;, we’ve already established with the &lt;code&gt;FileSplitter&lt;/code&gt; example that it is not inherently thread safe.&lt;/p&gt;
&lt;p&gt;Maybe that Global VM Lock (GVL) you’ve heard about is protecting you from this issue? &lt;strong&gt;Unintentionally&lt;/strong&gt;, yes.&lt;/p&gt;
&lt;p&gt;In CRuby, only one thread running Ruby code can run at a time. That means for our example you can’t get our class data to overwrite without what’s called a “context switch”: something that would cause the Ruby runtime to swap between our threads, even mid operation like in a method call or variable assignment.&lt;/p&gt;
&lt;p&gt;There are two common reasons context gets switched between threads in CRuby, which can result in operations only partially completing (ie, setting the proper result, then checking that result):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;~100ms of Ruby processing have elapsed&lt;/li&gt;
&lt;li&gt;A blocking operation has been invoked&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither of these cases are met by our example. You’re benefiting from low overhead and a lack of blocking operations - there isn’t enough going on to cause a context switch. Without (1) or (2), each thread runs and completes in entirety before the next thread runs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 If you don’t know what the GVL is - or only know it from a high level - you’ll learn all about it in “Concurrent, colorless Ruby” later on&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I titled this section “Heisenbugs” because you can get the bug to happen by altering things in what would appear to be innocuous ways. We’ll try two things:&lt;/p&gt;
&lt;p&gt;For (1), we’ll try with more calculations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817]

answers.size.times.map do |n|
  Thread.new do
    Fibonacci.calculate(n)
    answer = answers[n]
    result = Fibonacci.result.value

    if result != answer
      raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
    end
  end
end.map(&amp;amp;:join)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;💥! We get an error raised! So what happened?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;[] != [832040] (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the recursive Fibonacci solution, the code gets exponentially slower the more numbers we try to generate. And we now know when pure Ruby code is being run, the CRuby thread scheduler only allows each thread to run in 100ms time slices. This is so each thread can share processing time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 CPU intensive work doesn’t run more efficiently with threads in CRuby, but you will still encounter it on threaded servers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can see this time slicing in action with the &lt;code&gt;gvl-tracing&lt;/code&gt; gem, which creates a timeline of thread context switching using the &lt;a href=&#34;https://bugs.ruby-lang.org/issues/18339&#34;&gt;CRuby GVL Instrumentation API&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;gvl-tracing&amp;quot;

GvlTracing.start(&amp;quot;timeline.json&amp;quot;) do
  answers.size.times.map do |n|
    Thread.new do
      Fibonacci.calculate(n)
      answer = answers[n]
      result = Fibonacci.result.value

      if result != answer
        raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
      end
    end
  end.map(&amp;amp;:join)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/1d4007c2a5.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Our threads’ &lt;code&gt;running&lt;/code&gt; slices form a sort of waterfall because of the GVL. They spend most of their time in the &lt;code&gt;wants_gvl&lt;/code&gt; state, which means they’re ready for work but are waiting to acquire the GVL. When they run, they can only hold the GVL for around 100ms of Ruby processing before the CRuby scheduler passes control to another thread. You see each thread in a &lt;code&gt;running&lt;/code&gt; state for small slices of time - the highlighted thread ran 97ms before having control pass back. As the Fibonacci numbers get larger, the threads need more 100ms slices to finish, which is why each thread has so many &lt;code&gt;running&lt;/code&gt; blocks.&lt;/p&gt;
&lt;p&gt;It’s also why we start to hit issues with our class level ivars. The scheduler can stop a thread mid operation, or in between variable assignments. It takes more iterations than shown, but we can visualize the issue like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-fibonacci.drawio-1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Can we try a bit harder and get our original example to break as well? Even though it has low throughput?&lt;/p&gt;
&lt;p&gt;Let’s create a helper to simulate some load.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;concurrent-ruby&amp;quot;

def run_forever(pool_size: 10)
  pool = Concurrent::FixedThreadPool.new(
    pool_size
  )
  i = Concurrent::AtomicFixnum.new

  loop do
    pool.post do
      yield i.increment
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the &lt;code&gt;run_forever&lt;/code&gt; method we utilize a gem called &lt;code&gt;concurrent-ruby&lt;/code&gt; to endlessly run our code across multiple threads. It uses a pool of 10 threads, and each time a thread runs it maintains a thread-safe counter which it yields as the current iteration. We can now use it to run our original Fibonacci example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

run_forever do |iteration|
  n = iteration % answers.size
  Fibonacci.calculate(n)
  answer = answers[n]
  result = Fibonacci.result.value

  if result != answer
    raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
  end
rescue =&amp;gt; e
  puts &amp;quot;Iteration[#{iteration}] #{e.message}&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;💥. This time, we start seeing errors!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Iteration[36981] [13] != [34]
Iteration[154569] [89] != [144]
Iteration[173571] [89] != [21]
Iteration[197573] [] != [144]
Iteration[199483] [] != [89]
Iteration[201395] [] != [144]
Iteration[203330] [] != [55]
Iteration[207180] [5] != [144]
Iteration[209117] [5] != [144]
Iteration[211039] [5] != [55]
Iteration[214849] [5] != [89]
Iteration[234986] [8] != [89]
Iteration[221961] [1] != [144]
Iteration[223872] [1] != [144]
Iteration[225767] [1] != [34]
Iteration[244243] [0] != [144]
Iteration[218269] [1] != [144]
Iteration[236807] [8] != [144]
Iteration[240446] [55] != [89]
Iteration[231318] [1] != [34]
Iteration[246175] [0] != [13]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For (2), let’s try it again but call &lt;code&gt;puts&lt;/code&gt;, a blocking operation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;answers.size.times.map do |n|
  Thread.new do
    Fibonacci.calculate(n)
    puts &amp;quot;Get ready to check for #{n}!&amp;quot;
    answer = answers[n]
    result = Fibonacci.result.value

    if result != answer
      raise &amp;quot;[#{result}] != [#{answer}]&amp;quot;
    end
  end
end.map(&amp;amp;:join)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;puts&lt;/code&gt; gives us a pretty reliable failure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;[144] != [0] (RuntimeError)
Get ready to check for 1!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same basic idea happens - we perform the calculation, but before checking the result we print the message &lt;code&gt;Get ready to check for #{n}!&lt;/code&gt;. This is a blocking operation in CRuby&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt; - it causes our thread to yield and another thread to get scheduled. When the original thread regains control there’s a good chance the value has been swapped out and the comparison will fail. This is the main reason our original &lt;code&gt;FileSplitter&lt;/code&gt; code failed so reliably - the &lt;code&gt;Tempfile&lt;/code&gt; &lt;code&gt;write&lt;/code&gt; method is a blocking operation and caused a context switch.&lt;/p&gt;
&lt;h4 id=&#34;ruby-internals&#34;&gt;Ruby internals&lt;/h4&gt;
&lt;p&gt;Interesting side note, this was my original code for this section, which I initially tried on CRuby 3.2.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Repeater
  class &amp;lt;&amp;lt; self
    attr_accessor :result

    def repeat_by(content, n)
      self.result = [content] * n
    end
  end
end

100.times do
  100.times.map do |n|
    Thread.new do
      Repeater.repeat_by(&amp;quot;hello&amp;quot;, n)
      array_size = Repeater.result.size
      if array_size != n
        raise &amp;quot;[#{array_size}] should be [#{n}]?&amp;quot;
      end
    end
  end.map(&amp;amp;:join)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;[88] should be [98]? (RuntimeError)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That code failed reliably on Mac and Linux on Ruby 3.2. But on Ruby 3.3, I couldn’t get it to fail no matter how many times I ran it. Even between minor Ruby versions the thread internals can change enough to invalidate an evaluation of unsafe code. In this case the code benefited from the change, but there are no guarantees the next version won’t fail even more frequently than 3.2&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Using our &lt;code&gt;run_forever&lt;/code&gt; helper, however, we can eventually get this to fail on CRuby 3.3:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;run_forever do |iteration|
  n = rand(100)
  Repeater.repeat_by(&amp;quot;hello&amp;quot;, n)
  array_size = Repeater.result.size
  if array_size != n
    raise &amp;quot;[#{array_size}] should be [#{n}]?&amp;quot;
  end
rescue StandardError =&amp;gt; e
  puts &amp;quot;Iteration[#{iteration}] #{e.message}&amp;quot;
end
&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Iteration[323650] [52] should be [35]?
Iteration[408269] [59] should be [18]?
Iteration[563087] [51] should be [16]?
Iteration[623992] [8] should be [91]?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It takes hundreds of thousands of attempts but it does ultimately fail. But why did running it so many times cause it to fail? There’s no blocking operation. It shouldn’t take 100ms to run. In “Concurrent, colorless Ruby” we’ll dig even further into other reasons your thread context switches.&lt;/p&gt;
&lt;h4 id=&#34;back-in-reality&#34;&gt;Back in reality...&lt;/h4&gt;
&lt;p&gt;The community has a &lt;em&gt;much&lt;/em&gt; better understanding of this type of issue now vs 10+ years ago. But it still happens and I’ve seen it recently in gems, even ones that are well maintained.&lt;/p&gt;
&lt;p&gt;Watch out for this in your own code. Watch out for this in code reviews. Watch out for this in gems. If you see it and it &lt;em&gt;seems&lt;/em&gt; to run ok, use &lt;code&gt;run_forever&lt;/code&gt; and you’ll likely see it fail.&lt;/p&gt;
&lt;p&gt;Assume shared class instance variables are a bad idea. Knowing that you try something new…&lt;/p&gt;
&lt;h3 id=&#34;copying-state&#34;&gt;Copying state to instances&lt;/h3&gt;
&lt;p&gt;You’re maintaining the &lt;code&gt;please_encrypt&lt;/code&gt; gem (a very polite encryption layer), and you have a class level macro that defines which attributes to encrypt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Member
  include PleaseEncrypt
  please_encrypt :home_address, :phone
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Learning from your &lt;code&gt;FileSplitter&lt;/code&gt; issue, you’ve removed all of your shared class level ivars in the gem. But you’ve just added a new class level ivar that you’re &lt;em&gt;sure&lt;/em&gt; is safe.&lt;/p&gt;
&lt;p&gt;You know not to &lt;em&gt;share&lt;/em&gt; class-level instance variables, but you are now using some for default values. The macro defines class level ivars containing metadata about the encryption. It’s only defined once on class load, so you won’t be contending with threads overwriting each other&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;When an instance uses the &lt;code&gt;encryptables&lt;/code&gt; metadata, you &lt;code&gt;dup&lt;/code&gt; the original value so nothing gets shared by accident:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# member.rb
class Member
  include PleaseEncrypt
  please_encrypt :home_address, :phone
end

# please_encrypt.rb
module PleaseEncrypt
  # ...
  def pleasant_options
    # `dup` to avoid sharing across threads
    @pleasant_options ||= self.class.encryptables.dup
  end

  def pleasant_attributes
    @pleasant_attributes ||= # ...
  end

  module ClassMethods
    attr_accessor :encryptables

    def please_encrypt(*fields)
      self.encryptables = {}
      fields.each do |field|
        self.encryptables[field] = {
          algorithm: &amp;quot;AES-256-GCM&amp;quot;,
          key_method: :generate_key
        }
        
        define_method(&amp;quot;#{field}=&amp;quot;) do |value|
          result = encrypt(field, value)
          pleasant_attributes[field][:encrypted] = result
        end

        define_method(field) do
          decrypt(
            field,
            pleasant_attributes[field][:encrypted]
          )
        end
      end
    end
  end

  def encrypt(field, value)
    options = pleasant_options[field]
    cipher = OpenSSL::Cipher.new(
      options[:algorithm]
    )
    
    options[:key] = send(options[:key_method])
    cipher.encrypt
    # ...
  end

  def decrypt(field, value)
    options = pleasant_options[field]
    # ...
  end
  # ...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ll look at the full code later, but this highlights the major points. When &lt;code&gt;please_encrypt&lt;/code&gt; is called it takes each symbol and makes them into readers and writers. When setting the field the value is encrypted. When getting the field the value is decrypted. Pleasant!&lt;/p&gt;
&lt;p&gt;You release a new version of your gem and a few weeks after release you start seeing issues open up!&lt;/p&gt;
&lt;p&gt;😥&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/2e63bce4-3922-4351-8644-16918633ce0c.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;😰&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/ddf874fe-c21a-463d-a12b-7808a78d7c3a.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;😱&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/b32e5d6b-c913-4c0e-b3fd-7079239004b9.jpeg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;You haven’t seen anything like this in your testing and can’t manage to reproduce it. What is going on?&lt;/p&gt;
&lt;p&gt;A user has reported the only way they can reliably reproduce it. It requires running code using &lt;code&gt;please_encrypt&lt;/code&gt; inside of sidekiq jobs for 3-5 minutes straight.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class EncryptionFailureJob
  include Sidekiq::Job

  def perform
    m = Member.new
    m.phone = &amp;quot;123-456-7890&amp;quot;
    m.phone
  end
end

# sidekiq -C 10
loop do
  EncryptionFailureJob.perform_async
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The user can “fix” the error by forcing a retry in the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def perform
  m = Member.new
  m.phone = &amp;quot;123-456-7890&amp;quot;
  m.phone
rescue OpenSSL::Cipher::CipherError
  puts &amp;quot;retrying...&amp;quot;
  retry
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use this code to reproduce the issue, and it always succeeds within one &lt;code&gt;retry&lt;/code&gt; 👀.&lt;/p&gt;
&lt;p&gt;Running this way, after a few minutes you usually see an &lt;code&gt;OpenSSL::Cipher::CipherError&lt;/code&gt;. Yay…&lt;/p&gt;
&lt;p&gt;But why does it take so long? Maybe the overhead of Sidekiq interacting with Redis and managing the job server is reducing load. You know how you can get some load - &lt;code&gt;run_forever&lt;/code&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;run_forever do |iteration|
  m = Member.new
  m.phone = &amp;quot;123-456-7890&amp;quot;
  m.phone
rescue =&amp;gt; e
  puts &amp;quot;Iteration[#{iteration}] #{e.class}&amp;quot; 
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Doing that, you’re able to raise an &lt;code&gt;OpenSSL::Cipher::CipherError&lt;/code&gt; pretty quickly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;Iteration[3591] OpenSSL::Cipher::CipherError
Iteration[8093] OpenSSL::Cipher::CipherError
Iteration[12229] OpenSSL::Cipher::CipherError
Iteration[13368] OpenSSL::Cipher::CipherError
Iteration[18023] OpenSSL::Cipher::CipherError
Iteration[38276] OpenSSL::Cipher::CipherError
Iteration[63144] OpenSSL::Cipher::CipherError
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You look through recent changes. Luckily, in your case the most recent change you made was your attribute &lt;code&gt;dup&lt;/code&gt;, so you look deeper into that code. Is something wrong with the &lt;code&gt;||=&lt;/code&gt; maybe?&lt;/p&gt;
&lt;p&gt;Here’s the full source code. Can you find the issue?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ This is working code so you can try it to replicate the issue. It will properly encrypt and decrypt. But it is not meant for real use! Use real, vetted libraries for anything encryption/security related 🔐&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;module PleaseEncrypt
  def self.included(base)
    base.extend ClassMethods
  end
  
  module ClassMethods
    attr_accessor :encryptables

    def please_encrypt(*fields)
      self.encryptables = {}
      fields.each do |field|
        self.encryptables[field] = {
          algorithm: &amp;quot;AES-256-GCM&amp;quot;,
          key_method: :generate_key
        }
        
        define_method(&amp;quot;#{field}=&amp;quot;) do |value|
          result = encrypt(field, value)
          pleasant_attributes[field][:encrypted] = result
        end

        define_method(field) do
          decrypt(
            field,
            pleasant_attributes[field][:encrypted]
          )
        end
      end
    end
  end

  def encrypt(field, value)
    options = pleasant_options[field]
    cipher = OpenSSL::Cipher.new(
      options[:algorithm]
    )
    
    options[:key] = send(options[:key_method])
    cipher.encrypt
    options[:iv] = cipher.random_iv
    cipher.key = options[:key]
    encrypted = cipher.update(value) + cipher.final
    options[:auth_tag] = cipher.auth_tag
    Base64.encode64(encrypted)
  end

  def decrypt(field, value)
    options = pleasant_options[field]
    cipher = OpenSSL::Cipher.new(
      options[:algorithm]
    )
    
    data = Base64.decode64(value)
    cipher.decrypt
    cipher.iv = options[:iv]
    cipher.auth_tag = options[:auth_tag]
    cipher.key = options[:key]
    cipher.update(data) + cipher.final
  end

  def pleasant_options
    @pleasant_options ||= self.class.encryptables.dup
  end

  def pleasant_attributes
    @pleasant_attributes ||= hash_defaulter
  end

  def as_json
    pleasant_attributes.inject(hash_defaulter) do |hash, (k, v)|
      hash[k][:encrypted] = v[:encrypted]
      hash[k][:iv] = pleasant_options[k][:iv]
      hash[k][:auth_tag] = pleasant_options[k][:auth_tag]
      hash
    end
  end

  private

  def generate_key
    OpenSSL::Random.random_bytes(32)
  end

  def hash_defaulter
    Hash.new do |h, k|
      h[k] = {}
    end
  end
end

class Member
  include PleaseEncrypt
  please_encrypt :home_address, :phone
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You print the options and attributes, which is when you see the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = Member.new
m.phone = &amp;quot;123\n456\nok!&amp;quot;
m.home_address = &amp;quot;...&amp;quot;
puts m.pleasant_attributes
puts m.pleasant_options

{
  home_address: { ... },
  phone: { ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh no… you’re &lt;code&gt;dup&lt;/code&gt;’ing the class-level data (good!), but because &lt;code&gt;dup&lt;/code&gt; only performs a shallow copy, you are sharing nested objects (bad!).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;members = [
  Member.new,
  Member.new,
  Member.new
]
puts &#39;options id&#39;.ljust(12) +
     &#39; | home_address id&#39;.ljust(18) +
     &#39; | phone id&#39;.ljust(12)
puts &#39;-----------------------------------------&#39;

members.each do |member|
  opts = member.pleasant_options
  puts opts.object_id.to_s.ljust(15) +
       opts[:home_address].object_id.to_s.ljust(18) +
       opts[:phone].object_id.to_s.ljust(12)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means that per class, per attribute, every thread is modifying the same hash 😩, even across what &lt;em&gt;seemed&lt;/em&gt; like independent instances. When we print the &lt;code&gt;object_id&lt;/code&gt; on each of the hashes, we see that the &lt;em&gt;top-level&lt;/em&gt; hash is unique, but the nested hashes are all the same object.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;options id   | home_address id | phone id 
-----------------------------------------
2000           1920              1960        
2020           1920              1960        
2040           1920              1960 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But why does it take high load to break? You have your suspicions…&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/8p7giv.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Threading issues in CRuby can be ticking time-bombs. The GVL blocks parallel execution of Ruby code, so it takes more effort to force the right timing for unsafe context switches. If you see code that seems like it’s not thread safe but “works” - assume it will eventually break.&lt;/p&gt;
&lt;p&gt;The fix is frustratingly simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def pleasant_options
  @pleasant_options ||= begin
    duplicate = {}
    self.class.encryptables.each do |k, v|
      duplicate[k] = v.dup
    end
    duplicate
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you are duplicating the nested hashes as well. With this change, you no longer share any data between the class and the instances. Rerunning the &lt;code&gt;run_forever&lt;/code&gt; example there are no more errors 🙌🏼.&lt;/p&gt;
&lt;p&gt;This does leave you susceptible to deeply nested hashes being shared. If you’re using ActiveSupport or Rails, I’d recommend using &lt;code&gt;deep_dup&lt;/code&gt;, which handles nested hashes. If not, you could write a recursive method to handle it, or check for a gem (there seem to be many).&lt;/p&gt;
&lt;h4 id=&#34;back-in-reality-two&#34;&gt;Back in reality... part two&lt;/h4&gt;
&lt;p&gt;If this whole scenario seemed oddly specific, it’s because it is based on a real gem issue. This exact problem remained an open issue for years in the &lt;a href=&#34;https://github.com/attr-encrypted/attr_encrypted&#34;&gt;&lt;code&gt;attr_encrypted&lt;/code&gt;&lt;/a&gt;&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt; gem without anyone identifying the issue. All of the GitHub issue images are from real issues - running in Sidekiq and usually under load they saw intermittent encryption errors.&lt;/p&gt;
&lt;p&gt;How could this have been avoided? The simplest answer will sound familiar: don’t share class-level state. Even though in this case the sharing was unintentional, the macro metadata could have instead been used separate rather than trying to mix it into the instance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def encrypt(field, value)
  options = pleasant_options[field]
  cipher = OpenSSL::Cipher.new(
    options[:algorithm]
  )
    
  options[:key] = send(self.class.encryptables[field][:key_method])
  # ...
end

def decrypt(field, value)
  options = pleasant_options[field]
  cipher = OpenSSL::Cipher.new(
    self.class.encryptables[field][:algorithm]
  )
    
  # ...
end

def pleasant_options
  @pleasant_options ||= {}
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is easy to see in hindsight but is very easy to overlook in a larger code base.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;attr_encrypted&lt;/code&gt; is not Rails specific, technically. But if you’re in the Rails world using ActiveRecord you should &lt;a href=&#34;https://guides.rubyonrails.org/active_record_encryption.html&#34;&gt;use the built-in encryption&lt;/a&gt; instead.&lt;/p&gt;
&lt;h3 id=&#34;cleaning-up&#34;&gt;Cleaning up thread state&lt;/h3&gt;
&lt;p&gt;😮‍💨&lt;/p&gt;
&lt;p&gt;Now you’re thinking “is there a way I can avoid all of these headaches?”. You’re so tired of these class level ivars and their surprising behaviors. You’ve heard about something called “thread-locals” that sound really promising. They stay silo’d to your thread and can even act “global”&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt; without any chance of sharing&lt;sup id=&#34;fnref:14&#34;&gt;&lt;a href=&#34;#fn:14&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;14&lt;/a&gt;&lt;/sup&gt; 😍.&lt;/p&gt;
&lt;p&gt;As a test, you use them on the Fibonacci example from earlier and can’t reproduce any issues 😌. Within the thread its global, but each thread has its own local state, isolated from the others.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Fibonacci
  class &amp;lt;&amp;lt; self
    def result
      Thread.current[:fib_result]
    end

    def result=(value)
      Thread.current[:fib_result] = value
    end

    def calculate(n)
      self.result = Result.new
      self.result.value = fib(n)
    end

    def fib(n)
      return n if n &amp;lt;= 1
      fib(n - 1) + fib(n - 2)
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/9bebfe3546.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No class level ivars ✅ , no &lt;code&gt;dup&lt;/code&gt;ing ✅. Feeling confident that it’s a good approach when you need something global, you put this solution in your mental back pocket.&lt;/p&gt;
&lt;p&gt;Now you’re writing a new application - It’s a Rails app for sharing articles (because we don’t have enough of those), with a &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Article&lt;/code&gt; model:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class User &amp;lt; ApplicationRecord
  has_many :articles
end

class Article &amp;lt; ApplicationRecord
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a typical CRUD web application, and a couple requirements come up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Track the origin of a change. If an article is added, updated or deleted, a record is created to keep track of who did it and when.&lt;/li&gt;
&lt;li&gt;Add some I18N. You’ve got Japanese and Polish users going wild for this highly original site.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There’s so many places you could need to access this information, you decide to store it in a thread-safe, global way using thread-locals 🙌🏼.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  class &amp;lt;&amp;lt; self
    def user
      Thread.current[:app_user]
    end

    def user=(user)
	  Thread.current[:app_user] = user
      self.locale = user.locale
    end

    def locale
      Thread.current[:app_locale]
    end

    def locale=(locale)
      Thread.current[:app_locale] = locale
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 Not to spoil the ending, but ultimately I will recommend you avoid directly using &lt;code&gt;Thread.current[]&lt;/code&gt; in favor of other solutions, particularly since we’ll find later that it being “thread-local” is not really accurate.&lt;/p&gt;
&lt;p&gt;Just in case you get so excited at the idea of &lt;code&gt;Thread.current[]&lt;/code&gt; you start feverishly writing code using it and never finish reading part 2 🏃‍♂️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You use the thread-local data to pass the user into the Change we save.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# app/controllers/application_controller.rb
class ApplicationController
  def set_user
    AppContext.user = User.find(cookies.encrypted[&amp;quot;user_id&amp;quot;])
  end
end

# app/models/articles_controller.rb
class ArticlesController &amp;lt; ApplicationController
  before_action :set_user

  def create
    Article.create!(article_params)
  end
end

# app/models/change.rb
class Change &amp;lt; ApplicationRecord
  belongs_to :trackable, polymorphic: true
  belongs_to :user
end

# app/models/article.rb
class Article
  include Trackable
end

# app/models/concerns/trackable.rb
module Trackable
  extend ActiveSupport::Concern

  included do
    before_save :track_changes
    before_destroy :track_destroy
    has_many :changes, as: :trackable
  end

  private

  def track_changes
    action = if new_record?
      &amp;quot;create&amp;quot;
    else
      &amp;quot;update&amp;quot;
    end

    track_change(action)
  end

  def track_destroy
    track_change(&amp;quot;delete&amp;quot;)
  end

  def track_change(action)
    Change.create(
      trackable: self,
      action:,
      changes: attributes,
      user: AppContext.user
    )
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This seems to work really well. Well enough that you decide to expose a change history to your users so they can see edits that have been made to an article, and by who.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ArticlesController &amp;lt; ApplicationController
  # ...

  def show
    @article = Article.find(params[:id])
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-erb&#34; data-lang=&#34;erb&#34;&gt;&amp;lt;!-- app/views/articles/show.html.erb --&amp;gt;
	
&amp;lt;h1&amp;gt;&amp;lt;%= t(&#39;articles.changes.title&#39;) %&amp;gt;&amp;lt;/h1&amp;gt;
	
&amp;lt;table&amp;gt;
  &amp;lt;thead&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;th&amp;gt;&amp;lt;%= t(&#39;articles.changes.headers.article_id&#39;) %&amp;gt;&amp;lt;/th&amp;gt;
      &amp;lt;th&amp;gt;&amp;lt;%= t(&#39;articles.changes.headers.action&#39;) %&amp;gt;&amp;lt;/th&amp;gt;
      &amp;lt;th&amp;gt;&amp;lt;%= t(&#39;articles.changes.headers.changes&#39;) %&amp;gt;&amp;lt;/th&amp;gt;
      &amp;lt;th&amp;gt;&amp;lt;%= t(&#39;articles.changes.headers.user&#39;) %&amp;gt;&amp;lt;/th&amp;gt;
      &amp;lt;th&amp;gt;&amp;lt;%= t(&#39;articles.changes.headers.changed_at&#39;) %&amp;gt;&amp;lt;/th&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/thead&amp;gt;
  &amp;lt;tbody&amp;gt;
    &amp;lt;% @article.changes.each do |change| %&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;%= change.trackable_id %&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;%= t(&amp;quot;articles.changes.actions.#{change.action}&amp;quot;) %&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;
          &amp;lt;ul&amp;gt;
            &amp;lt;% change.changes.each do |attribute, value| %&amp;gt;
              &amp;lt;li&amp;gt;&amp;lt;%= t(&amp;quot;articles.attributes.#{attribute}&amp;quot;) %&amp;gt;: &amp;lt;%= value %&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;% end %&amp;gt;
          &amp;lt;/ul&amp;gt;
        &amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;%= change.user.email %&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;%= l(change.created_at, format: :long) %&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Things are going smoothly, but what good is posting articles without being able to discuss them?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# app/models/article.rb
class Discussion
  include Trackable
end

# app/controllers/discussions_controller.rb
class DiscussionsController &amp;lt; ApplicationController
  def create
    Discussion.create!(discussion_params)
  end

  def show
    @discussion = Discussion.find(params[:id])
  end
end

# assume a near identical &amp;quot;changes&amp;quot; view...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You test this out and it works on your machine 😬. Guess it’s time to deploy 🤷🏻‍♂️.&lt;/p&gt;
&lt;p&gt;Except… once you deploy these changes, you start receiving some weird reports from users. Users will sometimes see discussions render in a totally random language. Even worse, some changes are showing up associated with unrecognized users!&lt;/p&gt;
&lt;p&gt;Co się dzieje?!&lt;/p&gt;
&lt;p&gt;何が起こっているの？！&lt;/p&gt;
&lt;p&gt;What is happening?!&lt;/p&gt;
&lt;p&gt;You analyze your database and logs, and notice something strange:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The changes are only ever wrong for the &lt;code&gt;Discussion&lt;/code&gt; model&lt;/li&gt;
&lt;li&gt;The internationalizations are only ever wrong in the discussions views&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What’s the difference between &lt;code&gt;ArticlesController&lt;/code&gt; and &lt;code&gt;DiscussionsController&lt;/code&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ArticlesController &amp;lt; ApplicationController
  before_action :set_user

  def create
    Article.create!(article_params)
  end
end

class DiscussionsController &amp;lt; ApplicationController
  def create
    Discussion.create!(discussion_params)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Oh&lt;/em&gt; simple, there it is, you’re not setting &lt;code&gt;AppContext.user&lt;/code&gt; in the discussions controller. But if it’s not getting set then why is it a different user and locale? Why is it set to anything at all?&lt;/p&gt;
&lt;p&gt;You check the docs for thread-locals only to find…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They exist for the lifetime of the thread&lt;/li&gt;
&lt;li&gt;You know that everything in Ruby runs in a thread&lt;/li&gt;
&lt;li&gt;On multi threaded servers like Puma, threads are reused between requests so they can leak data you don’t clear out&lt;/li&gt;
&lt;li&gt;Even on single threaded servers like Unicorn, you still run within the Ruby “main” thread (&lt;code&gt;Thread.main&lt;/code&gt;). So you can even leak data you don’t clear out in single-threaded servers 🫠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/c3ff12657f.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well &lt;em&gt;that&lt;/em&gt; was pretty bad. Whenever a user went to the &lt;code&gt;DiscussionsController&lt;/code&gt;, it would use information from &lt;em&gt;whichever user used that thread last&lt;/em&gt;. It might be the same user, but it could just as easily be someone else.&lt;/p&gt;
&lt;p&gt;You make sure they get set on every controller now, but also get cleared out in every controller too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class AppContext
  # ...
  def clear
    [:app_user, :app_locale].each do |key|
      Thread.current[key] = nil
    end
  end
end

class ApplicationController
  after_action :clear_app_context

  def clear_app_context
    AppContext.clear
  end
end

class DiscussionsController &amp;lt; ApplicationController
  before_action :set_user
  # ...
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if someone forgets to add it to their controller, it won’t be set to any user at all - at the end of every request the thread-local is cleared out so it won’t be present&lt;sup id=&#34;fnref:15&#34;&gt;&lt;a href=&#34;#fn:15&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;15&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;You deploy the change and &lt;em&gt;almost&lt;/em&gt; everything is good.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Locales are good! No more Polish for English users, or Japanese for Polish users.&lt;/li&gt;
&lt;li&gt;When someone posts to a discussion, it consistently shows the right user in the change history 😮‍💨&lt;/li&gt;
&lt;li&gt;&lt;em&gt;But&lt;/em&gt;, there’s still an intermittent change showing up as the wrong user. What other code is there?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The controllers are good. How are the background jobs?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FirstTimePostingJob &amp;lt; ApplicationJob
  def perform(user)
    AppContext.user = user
    Congrats.new(user).call
  end
end

class ImageUploadJob &amp;lt; ApplicationJob
  def perform(article, file_url)
    # Uploads to a service 
    article.upload_from_url(file_url)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’re setting the user in one job, and picking it up in another 🤦🏻‍♂️. When the article is updated to add an image using the &lt;code&gt;file_url&lt;/code&gt;, sometimes the change is null, and sometimes it’s a totally random user. Hardly ever is it the actual user that added the image 😩.&lt;/p&gt;
&lt;p&gt;The same thing you did with controllers, you can’t forget to do in jobs. You need to clean them up the same way (after an action/job finishes), but the logic goes in a new place.&lt;/p&gt;
&lt;h4 id=&#34;using-sidekiq&#34;&gt;If you&#39;re using Sidekiq&lt;/h4&gt;
&lt;p&gt;If you’re using a mixture of Sidekiq and ActiveJob, or Sidekiq alone, a &lt;a href=&#34;https://github.com/sidekiq/sidekiq/wiki/Middleware&#34;&gt;Sidekiq Middleware&lt;/a&gt; is your best option. It will work for both job types because it is called internally by the Sidekiq server when performing a job.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ContextClearMiddleware
  include Sidekiq::ServerMiddleware

  def call(_worker, _job, _queue)
    yield
  ensure
    AppContext.clear
  end
end

Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add ContextClearMiddleware
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&#34;using-activejob&#34;&gt;If you&#39;re using ActiveJob&lt;/h4&gt;
&lt;p&gt;If you exclusively use ActiveJob, then an &lt;code&gt;after_perform&lt;/code&gt; in your base job class should work just as well, and will work even if you’re using SolidQueue, GoodJob, or any other job adapter that hooks into ActiveJob.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class ApplicationJob &amp;lt; ActiveJob::Base
  after_perform :clear_app_context

  def clear_app_context
    AppContext.clear
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;next-up&#34;&gt;Next Up&lt;/h3&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/1d541d2e-2d8c-4380-b166-a2c720da072a.jpeg&#34; alt=&#34;nested ruby concurrency&#34; style=&#34;height: 50%; width: 50%&#34;/&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;source&lt;/strong&gt;: jpcamara.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the first step into this Ruby concurrency series. In Part 2 we’ll dig into a few more topics: true thread locals + fibers, the layered Ruby concurrency model, reusing objects, some more complex threading coordination, and ideas for keeping your code and your dependencies thread safe. More soon 👋🏼!&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It doesn’t have to use threads by default, but there’s a good chance you are configured for &lt;em&gt;some&lt;/em&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;None of these servers force you to use multiple threads, but will usually use them by default. Puma’s OOTB configuration in rails uses 3 threads, for instance. &lt;a href=&#34;https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt&#34;&gt;https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They use some threads internally as well even if you don’t serve requests using more than 1 thread, possibly with the exclusion of Falcon since its core is the FiberScheduler.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You also don’t &lt;em&gt;have&lt;/em&gt; to use these job queues with threads either. But they all use them by default, or at least have internal threaded elements.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;You shouldn’t though.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html&#34;&gt;http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&#34;&gt;https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But if you’re using net-http, you’re using the timeout gem and have no choice.&lt;/p&gt;
&lt;p&gt;There was an attempt to remove timeout from net http that was reverted:&lt;a href=&#34;https://github.com/ruby/ruby/commit/f88bff770578583a708093f4a0d8b1483a1d2039&#34;&gt;https://github.com/ruby/ruby/commit/f88bff770578583a708093f4a0d8b1483a1d2039&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It IS safe to use when used inside a fiber scheduler like the async gem, because it swaps out the thread for a fiber timeout&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s ok to do once, up front, or controlled by mutexes. We’ll discuss that more in a bit.&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Sometimes you bake them into a cake for your friends 🎂. You just really love tempfiles 🥹.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;By “macros” I mean things like column encryption in Rails using &lt;code&gt;encrypts&lt;/code&gt;. Internally it uses a class level instance variable called &lt;code&gt;encrypted_attributes&lt;/code&gt; to track columns you are encrypting on that model.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But&lt;/em&gt; it is only set once when the class is loaded. If the class is reloaded, it must be locked by a mutex to safely change that value - Rails has a “reloader” that handles this, for instance.&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Naive because this is a terribly inefficient way to calculate Fibonacci sequences, which is why it’s a great example for demonstrating how CPU intensive work causes CRuby context switching&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;And most other languages&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Ruby 3.3 introduced the M:N thread scheduler, so there was a ton of refactoring of pthread internals. It may have been more impactful to threading than other versions - but every minor version is a year long large-scale effort that can easily have an impact on these private, internal behaviors&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This assumes you’re only loading classes once or safely handle reloads across threads (like Rails does)&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The reproduction steps are even a modified form of one of the issues &lt;a href=&#34;https://github.com/attr-encrypted/attr_encrypted/issues/372&#34;&gt;https://github.com/attr-encrypted/attr_encrypted/issues/372&lt;/a&gt; and here is the code that fixed the issue &lt;a href=&#34;https://github.com/attr-encrypted/attr_encrypted/pull/320/files&#34;&gt;https://github.com/attr-encrypted/attr_encrypted/pull/320/files&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’ll leave it to others to discuss the merits or pitfalls of using any globals at all, thread-local or otherwise 🤷🏻‍♂️&amp;#160;&lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:14&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;They’re totally susceptible to the copying state issue we just discussed but at least you’re aware of it now&amp;#160;&lt;a href=&#34;#fnref:14&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:15&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This could be achieved in a rack middleware as well, as opposed to an &lt;code&gt;after_action&lt;/code&gt;&amp;#160;&lt;a href=&#34;#fnref:15&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2024/image-5-13-24-7-52pm.png)

&gt; 👋🏼 This is a series on concurrency, parallelism and asynchronous programming in Ruby. It’s a deep dive, so it’s divided into 12 main parts:
&gt; 
&gt; - Your Ruby programs are always multi-threaded: Part 1
&gt; - [Your Ruby programs are always multi-threaded: Part 2](https://jpcamara.com/2024/06/23/your-ruby-programs.html)
&gt;   - [Consistent, request-local state](https://jpcamara.com/2024/06/27/consistent-requestlocal-state.html)
&gt; - [Ruby methods are colorless](https://jpcamara.com/2024/07/15/ruby-methods-are.html)
&gt; - [The Thread API](https://jpcamara.com/2024/08/26/the-thread-api.html)
&gt; - [Bitmasks, Ruby Threads and Interrupts, oh my!](https://jpcamara.com/2025/10/22/bitmasks-threads-and-interrupts-concurrent.html)
&gt; - [When good threads go bad](https://jpcamara.com/2025/12/30/when-good-threads-go-bad.html)
&gt; - Thread and its MaNy friends
&gt; - Fibers
&gt; - Processes, Ractors and alternative runtimes
&gt; - Scaling concurrency with streaming
&gt; - Abstracted, concurrent Ruby
&gt; - Closing thoughts, kicking the tires and tangents
&gt; - How I dive into CRuby concurrency
&gt; 
&gt; You’re reading “Your Ruby programs are always multi-threaded: Part 1”. I’ll update the links as each part is released, and include these links in each post.

- _**Part 1**_
- [It’s all threaded](#its-all-threaded)
- [Ok but how threaded is it _really_](#how-threaded-is-it)
- [Threading mistakes](#threading-mistakes)
	- 1. [Sharing module/class instance variables](#sharing-ivars)
		- [Heisenbugs](#heisenbugs)
		- [Ruby internals](#ruby-internals)
		- [Back in reality…](#back-in-reality)
	- 2. [Copying state to instances](#copying-state)
		- [Back in reality… part two](#back-in-reality-two)
	- 3a. [Cleaning up thread state](#cleaning-up)
		- [If you’re using Sidekiq](#using-sidekiq)
		- [If you’re using ActiveJob](#using-activejob)
- _**Part 2**_
	- 3b. [Sharing state with fibers](https://jpcamara.com/2024/06/23/your-ruby-programs.html#sharing-state-with-fibers)
		- [A wild Fiber appeared!](https://jpcamara.com/2024/06/23/your-ruby-programs.html#wild-fiber) 🌾 
		- [Ruby concurrency has layers](https://jpcamara.com/2024/06/23/your-ruby-programs.html#ruby-concurrency-layers)
		- [Actual thread-locals](https://jpcamara.com/2024/06/23/your-ruby-programs.html#actual-thread-locals)
		- [CurrentAttributes](https://jpcamara.com/2024/06/23/your-ruby-programs.html#current-attributes)
		- [RequestStore](https://jpcamara.com/2024/06/23/your-ruby-programs.html#request-store)
		- [Back in reality… part three](https://jpcamara.com/2024/06/23/your-ruby-programs.html#back-in-reality-three)
	- 4. [Reusing objects](https://jpcamara.com/2024/06/23/your-ruby-programs.html#reusing-objects)
	- 5. [Race conditions](https://jpcamara.com/2024/06/23/your-ruby-programs.html#race-conditions)
		- [Threading concepts](https://jpcamara.com/2024/06/23/your-ruby-programs.html#threading-concepts)
		- [Memoization / check-then-act](https://jpcamara.com/2024/06/23/your-ruby-programs.html#check-then-act)
		- [read-modify-write](https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write)
		- [Coordinating jobs](https://jpcamara.com/2024/06/23/your-ruby-programs.html#coordinating-jobs)
		- [read-modify-write shows up in many ways](https://jpcamara.com/2024/06/23/your-ruby-programs.html#read-modify-write-for-days)
- [Parting thoughts](https://jpcamara.com/2024/06/23/your-ruby-programs.html#parting-thoughts)
- [Tips for auditing gems](https://jpcamara.com/2024/06/23/your-ruby-programs.html#tips-for-gems) 💎 
- [I use Falcon - Fibers are safe from this right?](https://jpcamara.com/2024/06/23/your-ruby-programs.html#using-falcon)
- [I use JRuby/TruffleRuby…](https://jpcamara.com/2024/06/23/your-ruby-programs.html#using-jruby-truffle)
- [Preforking / Reforking servers](https://jpcamara.com/2024/06/23/your-ruby-programs.html#reforking)
- [What about using mutexes for globals?](https://jpcamara.com/2024/06/23/your-ruby-programs.html#mutexes-for-globals)
- [It’s not doom and gloom, it’s detect and correct](https://jpcamara.com/2024/06/23/your-ruby-programs.html#detect-and-correct)
- [Takeaways](https://jpcamara.com/2024/06/23/your-ruby-programs.html#takeaways) 🥡

&lt;h2 id=&#34;its-all-threaded&#34;&gt;It&#39;s all threaded&lt;/h2&gt;

If you run a web server using [Puma](https://github.com/puma/puma), [Falcon](https://github.com/socketry/falcon)[^1], [Webrick](https://github.com/ruby/webrick), or [Agoo](https://github.com/ohler55/agoo)[^2], you use threads.

If you run background jobs using [Sidekiq](https://github.com/sidekiq/sidekiq), [GoodJob](https://github.com/bensheldon/good_job), [SolidQueue](https://github.com/rails/solid_queue)[^3], or [Que](https://github.com/que-rb/que), you use threads.

If you use [ActiveRecord](https://github.com/rails/rails), or [Rails](https://github.com/rails/rails), you use threads.

If you use the [NewRelic](https://github.com/newrelic/newrelic-ruby-agent), [DataDog](https://github.com/DataDog/dd-trace-rb), [Sentry](https://github.com/getsentry/sentry-ruby), [LaunchDarkly](https://github.com/launchdarkly/ruby-server-sdk), or [Honeybadger](https://github.com/honeybadger-io/honeybadger-ruby) gems, you use threads.

If you use [net/http](https://github.com/ruby/net-http) (or any gem internally using it like [HTTParty](https://github.com/jnunemaker/httparty) or [RestClient](https://github.com/rest-client/rest-client)), [httpx](https://gitlab.com/os85/httpx), or the [Timeout](https://github.com/ruby/timeout)[^4] gem, you use threads 🧵.

And because of the above, even if you use a forked, single-threaded server like [Pitchfork](https://github.com/Shopify/pitchfork) or [Unicorn](https://github.com/defunkt/unicorn), or run [Puma](https://github.com/puma/puma) in worker mode with no additional threads, or run jobs with [Resque](https://github.com/resque/resque), or just use a basic Ruby rake task, you are using threads!

The point isn’t to be exhaustive. There are plenty more gems which use threads internally - you’re probably using some I haven’t listed. I’m also intentionally avoiding nuance - there are varying levels of threaded-ness.

The point _is_ to demonstrate that threads are **everywhere** in Ruby, even if you are not working with them explicitly. Always write your code to be as thread safe as possible. Act as if you’re in highly threaded code and you’ll live a much happier life.

&lt;h2 id=&#34;how-threaded-is-it&#34;&gt;Ok but how threaded is it &lt;em&gt;really&lt;/em&gt;&lt;/h2&gt;

The degree of thread safety concern you need to have _does_ vary depending on how threaded your Ruby environment is. 

- Sidekiq, Puma, SolidQueue, or GoodJob: very threaded. If there are threading bugs in code you use they will come up, eventually.
- Falcon: Can be configured to be threaded. If it is, threading bugs will come up eventually. By default it only uses Fibers, which can still be susceptible to some concurrency issues.
- Pitchfork, Unicorn or Resque: not threaded, but you’re probably using threaded gems or writing threaded code. I’ll discuss later how these gems can still hit threading issues on process based servers, and other concurrency adjacent issues you might encounter.
- A one-off Ruby script with no gems - you exist in a thread but if it’s just something like processing files sequentially, practically not threaded. You’re probably fine, but YMMV 😬 

It’s not just about writing explicitly threaded code that is safe to run. If you’re using `Thread.new`, you know you’re working with threaded code, and you know you need to proceed carefully. If you don’t know that, be careful! Threading has many sharp edges - it’s easy to get wrong. 

Threads introduce non-determinism into your code - what works on one execution may not work on the next, even with identical inputs. It can lull you into a false sense of security, because that failure state may not appear for a long time. Writing safe threaded code takes careful analysis and planning. 

![](https://cdn.uploads.micro.blog/98548/2024/24c9850ebd.png)

&gt; **source**: Eli and JP Camara, [https://x.com/logicalcomic](https://x.com/logicalcomic)

Writing safe threaded code can be difficult, but writing safe non-threaded Ruby code in a threaded environment doesn’t have to be. There are some top mistakes I’ve seen (or personally made) to keep an eye out for in your own code, and code in gems you bring into your projects. Let’s take a look at them, and we’ll learn some core Ruby/CRuby principles along the way as well.

&lt;h2 id=&#34;threading-mistakes&#34;&gt;Threading mistakes&lt;/h2&gt;

&lt;h3 id=&#34;sharing-ivars&#34;&gt;Sharing module/class instance variables&lt;/h3&gt;

&gt; 📝 For brevity, I’ll be referring to instance variables as “ivars”

If there’s a classic example of what _not_ to do for thread safety, it&#39;s maintaining and modifying class-level ivars[^5].

Not knowing that, you embark on implementing some file processing code. Finding yourself in need of code that splits a file into chunks by newline, you write the following:

	class FileSplitter
	  def self.split(file_path, max_lines)
	    lines = []
	    files = []
	    File.open(file_path) do |file|
	      file.each_line do |line|
	        lines &lt;&lt; line
	        if lines.size &gt; max_lines
	          output = Tempfile.new
	          lines.each do |line|
	            output.write(line)
	          end
	          lines = []
	          files &lt;&lt; output
	        end
	      end
	
	      # handle any remaining lines
	      if lines.size &gt; 0
	        output = Tempfile.new
	        lines.each do |line|
	          output.write(line)
	        end
	        files &lt;&lt; output
	      end
	
	      files
	    end
	  end
	end

The `split` method takes a file path, a number of max lines per file, and returns an array of Tempfiles. Each Tempfile contains a chunk of the original file. In some cases you upload each chunk to a file service like S3, in other cases you hand them off to methods for further processing[^6].

The method is kind of hefty, and there’s some clear duplication so you decide to refactor.

![](https://cdn.uploads.micro.blog/98548/2024/6d3eb93e-bff9-485f-b91f-1c929ca1ba77.jpeg)

&gt; **source**: [https://theycantalk.com/post/710363232083361793/plans](https://theycantalk.com/post/710363232083361793/plans)

You decide the logical split in the code is around writing to the temp files, a new method you’ll call `write_lines`. There are so many variables that it just seems easier to use ivars instead of passing everything around.

	class FileSplitter
	  def self.split(file_path, max_lines)
	    @lines = []
	    @files = []
	    File.open(file_path) do |file|
	      file.each_line do |line|
	        @lines &lt;&lt; line
	        write_lines(max_lines)
	      end
	      # force remaining to write
	      write_lines
	      @files
	    end
	  end
	
	  def self.write_lines(max = nil)
	    return if @lines.empty?
	
	    if max.nil? || @lines.size &gt; max
	      @output = Tempfile.new
	      @lines.each do |line|
	        @output.write(line)
	      end
	      @lines = []
	      @files &lt;&lt; @output
	    end
	  end
	end

Since the method is at the class level, the ivars are class level as well. For any code calling these methods, they only exist once. Every thread shares the same class object.

&gt; ~~📝 If this doesn’t _look_ like class-level ivars, you may be used to the more explicit syntax using double at-signs. For example, `@@lines`. These are equivalent, but when you’re inside a class method, class-level ivars only need one at-sign. When inside a class method, the instance is the class itself.~~
&gt; 
&gt; ~~Another format of this would be a class level `attr_accessor`. So if you see a class method with `self.lines = []` or see code like `FileSplitter.lines = []`, these can _also_ be class level ivars!~~
&gt; 
&gt; *Some commenters have pointed at that the above is not accurate - until I update it more accurately, see the [ruby lang docs](https://www.ruby-lang.org/en/documentation/faq/8/) for more information about class level instance variable differences*

You are running this code in a background job using Sidekiq, running with multiple threads on CRuby 3.3. Most of the time the job runs fine, but you’ve received a few reports from people missing data, and some people even seeing data that wasn’t in their files! Uh oh, what is happening?!

The problem is that every thread is sharing the same data, and modifying it concurrently. If multiple jobs are running and calling `FileSplitter.split`, they are all trying to write to the same piece of memory. We can follow along with the code line by line to see where things break down:

	### Thread 1
	# FileSplitter.split(path, max) -&gt;
	@lines = []
	@files = []
	# ...
	@output = Tempfile.new
	@output.write(line) # `write` causes the thread to sleep so Thread 2 starts
	
	### Thread 2
	# Calling `split` resets the instance variables
	# Now each thread is using the same variables!
	# FileSplitter.split(path, max) -&gt;
	@lines = []
	@files = []
	# ...
	# Next time it wakes up, Thread 1 will be appending to the same array as Thread 2 💀 
	@lines &lt;&lt; line
	# ...
	# Thread 1&#39;s Tempfile is overwritten and lost, along with anything written to it 👋🏼
	@output = Tempfile.new
	@output.write(line) # Now `write` causes Thread 2 to sleep
	
	### Thread 1 wakes up and continues processing
	# The next line from Thread 1 gets written to the output file from Thread 2
	# We are now 100% off the rails...
	# Pour one out for our mix and matched 
	# user data ☠️☠️☠️
	@lines.each do |line|
	  @output.write(line)
	end

Or the same flow, visualized:

![](https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-page-1.drawio-3.png)

&gt; **source**: jpcamara.com

The simplest advice about the above code?

Don’t use shared instance variables in classes, ever 🙅🏻‍♂️. Assume that a class level ivar is a [code smell](https://en.m.wikipedia.org/wiki/Code_smell), until proven otherwise.

&gt; 📝 There is a caveat to this, and its configuration and initialization class level ivars. It is common to set global configuration values and macros[^7] using class level ivars. The difference is that these are written once on load of your application, and then only ever _read_ past that point. If they are not properly frozen you _could_ still corrupt them by modifying them during program execution. Which is why things like Rails middleware `freeze` the middleware array to try and avoid this issue.

How can we make this code thread safe? Use regular Ruby instances. We don’t have to change much.

	class FileSplitter
	  def initialize(file_path, max_lines)
	    @file_path = file_path
	    @max_lines = max_lines
	    @lines = []
	  end
	
	  def split
	    @files = []
	
	    File.open(@file_path) do |file|
	      file.each_line do |line|
	        @lines &lt;&lt; line
	        write_lines(max_lines)
	      end
	      # force remaining to write
	      write_lines
	      @files
	    end
	  end
	
	  private
	
	  def write_lines(max = nil)
	    return if @lines.empty?
	
	    if max.nil? || @lines.size &gt; max
	      # Also remove @output as an ivar,
	      # we only use it right here so it
	      # being an ivar wasn&#39;t necessary 
	      output = Tempfile.new
	      @lines.each do |line|
	        output.write(line)
	      end
	      @lines = []
	      @files &lt;&lt; output
	    end
	  end
	end

Now when you use this code, each thread creates its own `FileSplitter`:

	### Thread 1
	splitter = FileSplitter.new(path, max)
	splitter.split
	
	### Thread 2
	splitter = FileSplitter.new(path, max)
	splitter.split

Each thread now safely uses its own independent, isolated instance 😌.

&lt;h4 id=&#34;heisenbugs&#34;&gt;Heisenbugs&lt;/h4&gt;

![](https://pbmo.files.wordpress.com/2012/11/walter-white1.png)

&gt; **source**: https://thedailyomnivore.net/2012/11/30/heisenbug/

&gt; A heisenbug is a software bug that seems to disappear or alter its behavior when one attempts to study it.
&gt; 
&gt; The term is a pun on the name of Werner Heisenberg, the physicist who first asserted the observer effect of quantum mechanics, which states that the act of observing a system inevitably alters its state.

The effects of sharing class-level ivars can be deceptive. For instance, try running the following:

	class Result
	  attr_accessor :value
	end
	
	class Fibonacci
	  class &lt;&lt; self
	    attr_accessor :result
	
	    def calculate(n)
	      self.result = Result.new
	      self.result.value = fib(n)
	    end
	
	    def fib(n)
	      return n if n &lt;= 1
	      fib(n - 1) + fib(n - 2)
	    end
	  end
	end
	
	answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
	
	answers.size.times.map do |n|
	  Thread.new do
	    Fibonacci.calculate(n)
	    answer = answers[n]
	    result = Fibonacci.result.value
	
	    if result != answer
	      raise &#34;[#{result}] != [#{answer}]&#34;
	    end
	  end
	end.map(&amp;:join)

Here we have a naive[^8], basic Fibonacci generator, and we verify each result against an array of precomputed results. We spawn as many threads as there are precomputed results to calculate against. We’re storing the result in a class-level ivar that is being shared across threads - that shouldn’t end well! If any results don’t match, we raise an error.

Run this example and… it doesn’t fail in CRuby 🤔. This might lull you into thinking it isn’t an issue.

Is CRuby somehow thread safe? **No**, we’ve already established with the `FileSplitter` example that it is not inherently thread safe.

Maybe that Global VM Lock (GVL) you’ve heard about is protecting you from this issue? **Unintentionally**, yes.

In CRuby, only one thread running Ruby code can run at a time. That means for our example you can’t get our class data to overwrite without what’s called a “context switch”: something that would cause the Ruby runtime to swap between our threads, even mid operation like in a method call or variable assignment.

There are two common reasons context gets switched between threads in CRuby, which can result in operations only partially completing (ie, setting the proper result, then checking that result):

1. ~100ms of Ruby processing have elapsed
2. A blocking operation has been invoked

Neither of these cases are met by our example. You’re benefiting from low overhead and a lack of blocking operations - there isn’t enough going on to cause a context switch. Without (1) or (2), each thread runs and completes in entirety before the next thread runs.

&gt; 📝 If you don’t know what the GVL is - or only know it from a high level - you’ll learn all about it in “Concurrent, colorless Ruby” later on

I titled this section “Heisenbugs” because you can get the bug to happen by altering things in what would appear to be innocuous ways. We’ll try two things:

For (1), we’ll try with more calculations:

	answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817]
	
	answers.size.times.map do |n|
	  Thread.new do
	    Fibonacci.calculate(n)
	    answer = answers[n]
	    result = Fibonacci.result.value
	
	    if result != answer
	      raise &#34;[#{result}] != [#{answer}]&#34;
	    end
	  end
	end.map(&amp;:join)

💥! We get an error raised! So what happened?

```text
[] != [832040] (RuntimeError)
```
In the recursive Fibonacci solution, the code gets exponentially slower the more numbers we try to generate. And we now know when pure Ruby code is being run, the CRuby thread scheduler only allows each thread to run in 100ms time slices. This is so each thread can share processing time.

&gt; 📝 CPU intensive work doesn’t run more efficiently with threads in CRuby, but you will still encounter it on threaded servers.

We can see this time slicing in action with the `gvl-tracing` gem, which creates a timeline of thread context switching using the [CRuby GVL Instrumentation API](https://bugs.ruby-lang.org/issues/18339):

	require &#34;gvl-tracing&#34;
	
	GvlTracing.start(&#34;timeline.json&#34;) do
	  answers.size.times.map do |n|
	    Thread.new do
          Fibonacci.calculate(n)
	      answer = answers[n]
	      result = Fibonacci.result.value
	
	      if result != answer
	        raise &#34;[#{result}] != [#{answer}]&#34;
	      end
	    end
	  end.map(&amp;:join)
	end

![](https://cdn.uploads.micro.blog/98548/2024/1d4007c2a5.jpeg)

&gt; **source**: jpcamara.com

Our threads’ `running` slices form a sort of waterfall because of the GVL. They spend most of their time in the `wants_gvl` state, which means they’re ready for work but are waiting to acquire the GVL. When they run, they can only hold the GVL for around 100ms of Ruby processing before the CRuby scheduler passes control to another thread. You see each thread in a `running` state for small slices of time - the highlighted thread ran 97ms before having control pass back. As the Fibonacci numbers get larger, the threads need more 100ms slices to finish, which is why each thread has so many `running` blocks.

It’s also why we start to hit issues with our class level ivars. The scheduler can stop a thread mid operation, or in between variable assignments. It takes more iterations than shown, but we can visualize the issue like this:

![](https://cdn.uploads.micro.blog/98548/2024/ruby-is-all-threads-fibonacci.drawio-1.png)

&gt; **source**: jpcamara.com

Can we try a bit harder and get our original example to break as well? Even though it has low throughput?

Let’s create a helper to simulate some load.

	require &#34;concurrent-ruby&#34;
	
	def run_forever(pool_size: 10)
	  pool = Concurrent::FixedThreadPool.new(
	    pool_size
	  )
	  i = Concurrent::AtomicFixnum.new
	
	  loop do
	    pool.post do
	      yield i.increment
	    end
	  end
	end
	

In the `run_forever` method we utilize a gem called `concurrent-ruby` to endlessly run our code across multiple threads. It uses a pool of 10 threads, and each time a thread runs it maintains a thread-safe counter which it yields as the current iteration. We can now use it to run our original Fibonacci example:

	answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
	
	run_forever do |iteration|
	  n = iteration % answers.size
	  Fibonacci.calculate(n)
	  answer = answers[n]
	  result = Fibonacci.result.value
	
	  if result != answer
	    raise &#34;[#{result}] != [#{answer}]&#34;
	  end
	rescue =&gt; e
	  puts &#34;Iteration[#{iteration}] #{e.message}&#34;
	end

💥. This time, we start seeing errors!

```text
Iteration[36981] [13] != [34]
Iteration[154569] [89] != [144]
Iteration[173571] [89] != [21]
Iteration[197573] [] != [144]
Iteration[199483] [] != [89]
Iteration[201395] [] != [144]
Iteration[203330] [] != [55]
Iteration[207180] [5] != [144]
Iteration[209117] [5] != [144]
Iteration[211039] [5] != [55]
Iteration[214849] [5] != [89]
Iteration[234986] [8] != [89]
Iteration[221961] [1] != [144]
Iteration[223872] [1] != [144]
Iteration[225767] [1] != [34]
Iteration[244243] [0] != [144]
Iteration[218269] [1] != [144]
Iteration[236807] [8] != [144]
Iteration[240446] [55] != [89]
Iteration[231318] [1] != [34]
Iteration[246175] [0] != [13]
```
For (2), let’s try it again but call `puts`, a blocking operation:

	answers.size.times.map do |n|
	  Thread.new do
	    Fibonacci.calculate(n)
	    puts &#34;Get ready to check for #{n}!&#34;
	    answer = answers[n]
	    result = Fibonacci.result.value
	
	    if result != answer
	      raise &#34;[#{result}] != [#{answer}]&#34;
	    end
	  end
	end.map(&amp;:join)

`puts` gives us a pretty reliable failure:

```text
[144] != [0] (RuntimeError)
Get ready to check for 1!
```
The same basic idea happens - we perform the calculation, but before checking the result we print the message `Get ready to check for #{n}!`. This is a blocking operation in CRuby[^9] - it causes our thread to yield and another thread to get scheduled. When the original thread regains control there’s a good chance the value has been swapped out and the comparison will fail. This is the main reason our original `FileSplitter` code failed so reliably - the `Tempfile` `write` method is a blocking operation and caused a context switch.

&lt;h4 id=&#34;ruby-internals&#34;&gt;Ruby internals&lt;/h4&gt;

Interesting side note, this was my original code for this section, which I initially tried on CRuby 3.2.

	class Repeater
	  class &lt;&lt; self
	    attr_accessor :result
	
	    def repeat_by(content, n)
	      self.result = [content] * n
	    end
	  end
	end
	
	100.times do
	  100.times.map do |n|
	    Thread.new do
	      Repeater.repeat_by(&#34;hello&#34;, n)
	      array_size = Repeater.result.size
	      if array_size != n
	        raise &#34;[#{array_size}] should be [#{n}]?&#34;
	      end
	    end
	  end.map(&amp;:join)
	end

```text
[88] should be [98]? (RuntimeError)
```
That code failed reliably on Mac and Linux on Ruby 3.2. But on Ruby 3.3, I couldn’t get it to fail no matter how many times I ran it. Even between minor Ruby versions the thread internals can change enough to invalidate an evaluation of unsafe code. In this case the code benefited from the change, but there are no guarantees the next version won’t fail even more frequently than 3.2[^10].

Using our `run_forever` helper, however, we can eventually get this to fail on CRuby 3.3:

	run_forever do |iteration|
	  n = rand(100)
	  Repeater.repeat_by(&#34;hello&#34;, n)
	  array_size = Repeater.result.size
	  if array_size != n
	    raise &#34;[#{array_size}] should be [#{n}]?&#34;
	  end
	rescue StandardError =&gt; e
	  puts &#34;Iteration[#{iteration}] #{e.message}&#34;
	end

```text
Iteration[323650] [52] should be [35]?
Iteration[408269] [59] should be [18]?
Iteration[563087] [51] should be [16]?
Iteration[623992] [8] should be [91]?
```
It takes hundreds of thousands of attempts but it does ultimately fail. But why did running it so many times cause it to fail? There’s no blocking operation. It shouldn’t take 100ms to run. In “Concurrent, colorless Ruby” we’ll dig even further into other reasons your thread context switches.

&lt;h4 id=&#34;back-in-reality&#34;&gt;Back in reality...&lt;/h4&gt;

The community has a _much_ better understanding of this type of issue now vs 10+ years ago. But it still happens and I’ve seen it recently in gems, even ones that are well maintained. 

Watch out for this in your own code. Watch out for this in code reviews. Watch out for this in gems. If you see it and it _seems_ to run ok, use `run_forever` and you’ll likely see it fail.

Assume shared class instance variables are a bad idea. Knowing that you try something new…

&lt;h3 id=&#34;copying-state&#34;&gt;Copying state to instances&lt;/h3&gt;

You’re maintaining the `please_encrypt` gem (a very polite encryption layer), and you have a class level macro that defines which attributes to encrypt:

	class Member
	  include PleaseEncrypt
	  please_encrypt :home_address, :phone
	end

Learning from your `FileSplitter` issue, you’ve removed all of your shared class level ivars in the gem. But you’ve just added a new class level ivar that you’re _sure_ is safe.

You know not to _share_ class-level instance variables, but you are now using some for default values. The macro defines class level ivars containing metadata about the encryption. It’s only defined once on class load, so you won’t be contending with threads overwriting each other[^11].

When an instance uses the `encryptables` metadata, you `dup` the original value so nothing gets shared by accident:

	# member.rb
	class Member
	  include PleaseEncrypt
	  please_encrypt :home_address, :phone
	end
	
	# please_encrypt.rb
	module PleaseEncrypt
	  # ...
	  def pleasant_options
	    # `dup` to avoid sharing across threads
	    @pleasant_options ||= self.class.encryptables.dup
	  end
	
	  def pleasant_attributes
	    @pleasant_attributes ||= # ...
	  end
	
	  module ClassMethods
	    attr_accessor :encryptables
	
	    def please_encrypt(*fields)
	      self.encryptables = {}
	      fields.each do |field|
	        self.encryptables[field] = {
	          algorithm: &#34;AES-256-GCM&#34;,
	          key_method: :generate_key
	        }
	        
	        define_method(&#34;#{field}=&#34;) do |value|
	          result = encrypt(field, value)
	          pleasant_attributes[field][:encrypted] = result
	        end
	
	        define_method(field) do
	          decrypt(
	            field,
	            pleasant_attributes[field][:encrypted]
	          )
	        end
	      end
	    end
	  end
	
	  def encrypt(field, value)
	    options = pleasant_options[field]
	    cipher = OpenSSL::Cipher.new(
	      options[:algorithm]
	    )
	    
	    options[:key] = send(options[:key_method])
	    cipher.encrypt
	    # ...
	  end
	
	  def decrypt(field, value)
	    options = pleasant_options[field]
	    # ...
	  end
	  # ...
	end

We’ll look at the full code later, but this highlights the major points. When `please_encrypt` is called it takes each symbol and makes them into readers and writers. When setting the field the value is encrypted. When getting the field the value is decrypted. Pleasant!

You release a new version of your gem and a few weeks after release you start seeing issues open up! 

😥 

![](https://cdn.uploads.micro.blog/98548/2024/2e63bce4-3922-4351-8644-16918633ce0c.jpeg)

😰

![](https://cdn.uploads.micro.blog/98548/2024/ddf874fe-c21a-463d-a12b-7808a78d7c3a.jpeg)

😱 

![](https://cdn.uploads.micro.blog/98548/2024/b32e5d6b-c913-4c0e-b3fd-7079239004b9.jpeg)

You haven’t seen anything like this in your testing and can’t manage to reproduce it. What is going on?

A user has reported the only way they can reliably reproduce it. It requires running code using `please_encrypt` inside of sidekiq jobs for 3-5 minutes straight.

	class EncryptionFailureJob
	  include Sidekiq::Job
	
	  def perform
	    m = Member.new
	    m.phone = &#34;123-456-7890&#34;
	    m.phone
	  end
	end
	
	# sidekiq -C 10
	loop do
	  EncryptionFailureJob.perform_async
	end

The user can “fix” the error by forcing a retry in the code:

	def perform
	  m = Member.new
	  m.phone = &#34;123-456-7890&#34;
	  m.phone
	rescue OpenSSL::Cipher::CipherError
	  puts &#34;retrying...&#34;
	  retry
	end

You can use this code to reproduce the issue, and it always succeeds within one `retry` 👀.

Running this way, after a few minutes you usually see an `OpenSSL::Cipher::CipherError`. Yay…

But why does it take so long? Maybe the overhead of Sidekiq interacting with Redis and managing the job server is reducing load. You know how you can get some load - `run_forever`!

	run_forever do |iteration|
	  m = Member.new
	  m.phone = &#34;123-456-7890&#34;
	  m.phone
	rescue =&gt; e
	  puts &#34;Iteration[#{iteration}] #{e.class}&#34; 
	end

Doing that, you’re able to raise an `OpenSSL::Cipher::CipherError` pretty quickly:

```text
Iteration[3591] OpenSSL::Cipher::CipherError
Iteration[8093] OpenSSL::Cipher::CipherError
Iteration[12229] OpenSSL::Cipher::CipherError
Iteration[13368] OpenSSL::Cipher::CipherError
Iteration[18023] OpenSSL::Cipher::CipherError
Iteration[38276] OpenSSL::Cipher::CipherError
Iteration[63144] OpenSSL::Cipher::CipherError
```
You look through recent changes. Luckily, in your case the most recent change you made was your attribute `dup`, so you look deeper into that code. Is something wrong with the `||=` maybe?

Here’s the full source code. Can you find the issue?

&gt; ⚠️ This is working code so you can try it to replicate the issue. It will properly encrypt and decrypt. But it is not meant for real use! Use real, vetted libraries for anything encryption/security related 🔐 

	module PleaseEncrypt
	  def self.included(base)
	    base.extend ClassMethods
	  end
	  
	  module ClassMethods
	    attr_accessor :encryptables
	
	    def please_encrypt(*fields)
	      self.encryptables = {}
	      fields.each do |field|
	        self.encryptables[field] = {
	          algorithm: &#34;AES-256-GCM&#34;,
	          key_method: :generate_key
	        }
	        
	        define_method(&#34;#{field}=&#34;) do |value|
	          result = encrypt(field, value)
	          pleasant_attributes[field][:encrypted] = result
	        end
	
	        define_method(field) do
	          decrypt(
	            field,
	            pleasant_attributes[field][:encrypted]
	          )
	        end
	      end
	    end
	  end
	
	  def encrypt(field, value)
	    options = pleasant_options[field]
	    cipher = OpenSSL::Cipher.new(
	      options[:algorithm]
	    )
	    
	    options[:key] = send(options[:key_method])
	    cipher.encrypt
	    options[:iv] = cipher.random_iv
	    cipher.key = options[:key]
	    encrypted = cipher.update(value) + cipher.final
	    options[:auth_tag] = cipher.auth_tag
	    Base64.encode64(encrypted)
	  end
	
	  def decrypt(field, value)
	    options = pleasant_options[field]
	    cipher = OpenSSL::Cipher.new(
	      options[:algorithm]
	    )
	    
	    data = Base64.decode64(value)
	    cipher.decrypt
	    cipher.iv = options[:iv]
	    cipher.auth_tag = options[:auth_tag]
	    cipher.key = options[:key]
	    cipher.update(data) + cipher.final
	  end
	
	  def pleasant_options
	    @pleasant_options ||= self.class.encryptables.dup
	  end
	
	  def pleasant_attributes
	    @pleasant_attributes ||= hash_defaulter
	  end
	
	  def as_json
	    pleasant_attributes.inject(hash_defaulter) do |hash, (k, v)|
	      hash[k][:encrypted] = v[:encrypted]
	      hash[k][:iv] = pleasant_options[k][:iv]
	      hash[k][:auth_tag] = pleasant_options[k][:auth_tag]
	      hash
	    end
	  end
	
	  private
	
	  def generate_key
	    OpenSSL::Random.random_bytes(32)
	  end
	
	  def hash_defaulter
	    Hash.new do |h, k|
	      h[k] = {}
	    end
	  end
	end
	
	class Member
	  include PleaseEncrypt
	  please_encrypt :home_address, :phone
	end

You print the options and attributes, which is when you see the following:

	m = Member.new
	m.phone = &#34;123\n456\nok!&#34;
	m.home_address = &#34;...&#34;
	puts m.pleasant_attributes
	puts m.pleasant_options
	
	{
	  home_address: { ... },
	  phone: { ... }
	}

Oh no… you’re `dup`’ing the class-level data (good!), but because `dup` only performs a shallow copy, you are sharing nested objects (bad!). 

	members = [
	  Member.new,
	  Member.new,
	  Member.new
	]
	puts &#39;options id&#39;.ljust(12) +
	     &#39; | home_address id&#39;.ljust(18) +
	     &#39; | phone id&#39;.ljust(12)
	puts &#39;-----------------------------------------&#39;
	
	members.each do |member|
	  opts = member.pleasant_options
	  puts opts.object_id.to_s.ljust(15) +
	       opts[:home_address].object_id.to_s.ljust(18) +
	       opts[:phone].object_id.to_s.ljust(12)
	end

This means that per class, per attribute, every thread is modifying the same hash 😩, even across what _seemed_ like independent instances. When we print the `object_id` on each of the hashes, we see that the _top-level_ hash is unique, but the nested hashes are all the same object.

```text
options id   | home_address id | phone id 
-----------------------------------------
2000           1920              1960        
2020           1920              1960        
2040           1920              1960 
```
But why does it take high load to break? You have your suspicions…

![](https://cdn.uploads.micro.blog/98548/2024/8p7giv.gif)

Threading issues in CRuby can be ticking time-bombs. The GVL blocks parallel execution of Ruby code, so it takes more effort to force the right timing for unsafe context switches. If you see code that seems like it’s not thread safe but “works” - assume it will eventually break.

The fix is frustratingly simple:

	def pleasant_options
	  @pleasant_options ||= begin
	    duplicate = {}
	    self.class.encryptables.each do |k, v|
	      duplicate[k] = v.dup
	    end
	    duplicate
	  end
	end

Now you are duplicating the nested hashes as well. With this change, you no longer share any data between the class and the instances. Rerunning the `run_forever` example there are no more errors 🙌🏼.

This does leave you susceptible to deeply nested hashes being shared. If you’re using ActiveSupport or Rails, I’d recommend using `deep_dup`, which handles nested hashes. If not, you could write a recursive method to handle it, or check for a gem (there seem to be many).

&lt;h4 id=&#34;back-in-reality-two&#34;&gt;Back in reality... part two&lt;/h4&gt;

If this whole scenario seemed oddly specific, it’s because it is based on a real gem issue. This exact problem remained an open issue for years in the [`attr_encrypted`](https://github.com/attr-encrypted/attr_encrypted)[^12] gem without anyone identifying the issue. All of the GitHub issue images are from real issues - running in Sidekiq and usually under load they saw intermittent encryption errors.

How could this have been avoided? The simplest answer will sound familiar: don’t share class-level state. Even though in this case the sharing was unintentional, the macro metadata could have instead been used separate rather than trying to mix it into the instance:

	def encrypt(field, value)
	  options = pleasant_options[field]
	  cipher = OpenSSL::Cipher.new(
	    options[:algorithm]
	  )
	    
	  options[:key] = send(self.class.encryptables[field][:key_method])
	  # ...
	end
	
	def decrypt(field, value)
	  options = pleasant_options[field]
	  cipher = OpenSSL::Cipher.new(
	    self.class.encryptables[field][:algorithm]
	  )
	    
	  # ...
	end
	
	def pleasant_options
	  @pleasant_options ||= {}
	end

This is easy to see in hindsight but is very easy to overlook in a larger code base.

`attr_encrypted` is not Rails specific, technically. But if you’re in the Rails world using ActiveRecord you should [use the built-in encryption](https://guides.rubyonrails.org/active_record_encryption.html) instead.

&lt;h3 id=&#34;cleaning-up&#34;&gt;Cleaning up thread state&lt;/h3&gt;

😮‍💨 

Now you’re thinking “is there a way I can avoid all of these headaches?”. You’re so tired of these class level ivars and their surprising behaviors. You’ve heard about something called “thread-locals” that sound really promising. They stay silo’d to your thread and can even act “global”[^13] without any chance of sharing[^14] 😍.

As a test, you use them on the Fibonacci example from earlier and can’t reproduce any issues 😌. Within the thread its global, but each thread has its own local state, isolated from the others.

	class Fibonacci
	  class &lt;&lt; self
	    def result
	      Thread.current[:fib_result]
	    end
	
	    def result=(value)
	      Thread.current[:fib_result] = value
	    end
	
	    def calculate(n)
	      self.result = Result.new
	      self.result.value = fib(n)
	    end
	
	    def fib(n)
	      return n if n &lt;= 1
	      fib(n - 1) + fib(n - 2)
	    end
	  end
	end

![](https://cdn.uploads.micro.blog/98548/2024/9bebfe3546.png)

&gt; **source**: jpcamara.com

No class level ivars ✅ , no `dup`ing ✅. Feeling confident that it’s a good approach when you need something global, you put this solution in your mental back pocket.

Now you’re writing a new application - It’s a Rails app for sharing articles (because we don’t have enough of those), with a `User` and `Article` model:

	class User &lt; ApplicationRecord
	  has_many :articles
	end
	
	class Article &lt; ApplicationRecord
	end

It’s a typical CRUD web application, and a couple requirements come up:

1. Track the origin of a change. If an article is added, updated or deleted, a record is created to keep track of who did it and when.
2. Add some I18N. You’ve got Japanese and Polish users going wild for this highly original site.

There’s so many places you could need to access this information, you decide to store it in a thread-safe, global way using thread-locals 🙌🏼.

	class AppContext
	  class &lt;&lt; self
	    def user
	      Thread.current[:app_user]
	    end
	
	    def user=(user)
		  Thread.current[:app_user] = user
	      self.locale = user.locale
	    end
	
	    def locale
	      Thread.current[:app_locale]
	    end
	
	    def locale=(locale)
	      Thread.current[:app_locale] = locale
	    end
	  end
	end

&gt; 📝 Not to spoil the ending, but ultimately I will recommend you avoid directly using `Thread.current[]` in favor of other solutions, particularly since we’ll find later that it being “thread-local” is not really accurate.
&gt; 
&gt; Just in case you get so excited at the idea of `Thread.current[]` you start feverishly writing code using it and never finish reading part 2 🏃‍♂️

You use the thread-local data to pass the user into the Change we save.

	# app/controllers/application_controller.rb
	class ApplicationController
	  def set_user
	    AppContext.user = User.find(cookies.encrypted[&#34;user_id&#34;])
	  end
	end
	
	# app/models/articles_controller.rb
	class ArticlesController &lt; ApplicationController
	  before_action :set_user
	
	  def create
	    Article.create!(article_params)
	  end
	end
	
	# app/models/change.rb
	class Change &lt; ApplicationRecord
	  belongs_to :trackable, polymorphic: true
	  belongs_to :user
	end
	
	# app/models/article.rb
	class Article
	  include Trackable
	end
	
	# app/models/concerns/trackable.rb
	module Trackable
	  extend ActiveSupport::Concern
	
	  included do
	    before_save :track_changes
	    before_destroy :track_destroy
	    has_many :changes, as: :trackable
	  end
	
	  private
	
	  def track_changes
	    action = if new_record?
	      &#34;create&#34;
	    else
	      &#34;update&#34;
	    end
	
	    track_change(action)
	  end
	
	  def track_destroy
	    track_change(&#34;delete&#34;)
	  end
	
	  def track_change(action)
	    Change.create(
	      trackable: self,
	      action:,
	      changes: attributes,
	      user: AppContext.user
	    )
	  end
	end

This seems to work really well. Well enough that you decide to expose a change history to your users so they can see edits that have been made to an article, and by who.

	class ArticlesController &lt; ApplicationController
	  # ...
	
	  def show
	    @article = Article.find(params[:id])
	  end
	end

```erb
&lt;!-- app/views/articles/show.html.erb --&gt;
	
&lt;h1&gt;&lt;%= t(&#39;articles.changes.title&#39;) %&gt;&lt;/h1&gt;
	
&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;%= t(&#39;articles.changes.headers.article_id&#39;) %&gt;&lt;/th&gt;
      &lt;th&gt;&lt;%= t(&#39;articles.changes.headers.action&#39;) %&gt;&lt;/th&gt;
      &lt;th&gt;&lt;%= t(&#39;articles.changes.headers.changes&#39;) %&gt;&lt;/th&gt;
      &lt;th&gt;&lt;%= t(&#39;articles.changes.headers.user&#39;) %&gt;&lt;/th&gt;
      &lt;th&gt;&lt;%= t(&#39;articles.changes.headers.changed_at&#39;) %&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;% @article.changes.each do |change| %&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;%= change.trackable_id %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= t(&#34;articles.changes.actions.#{change.action}&#34;) %&gt;&lt;/td&gt;
        &lt;td&gt;
          &lt;ul&gt;
            &lt;% change.changes.each do |attribute, value| %&gt;
              &lt;li&gt;&lt;%= t(&#34;articles.attributes.#{attribute}&#34;) %&gt;: &lt;%= value %&gt;&lt;/li&gt;
            &lt;% end %&gt;
          &lt;/ul&gt;
        &lt;/td&gt;
        &lt;td&gt;&lt;%= change.user.email %&gt;&lt;/td&gt;
        &lt;td&gt;&lt;%= l(change.created_at, format: :long) %&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;% end %&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
```
Things are going smoothly, but what good is posting articles without being able to discuss them?

	# app/models/article.rb
	class Discussion
	  include Trackable
	end
	
	# app/controllers/discussions_controller.rb
	class DiscussionsController &lt; ApplicationController
	  def create
	    Discussion.create!(discussion_params)
	  end
	
	  def show
	    @discussion = Discussion.find(params[:id])
	  end
	end
	
	# assume a near identical &#34;changes&#34; view...

You test this out and it works on your machine 😬. Guess it’s time to deploy 🤷🏻‍♂️.

Except… once you deploy these changes, you start receiving some weird reports from users. Users will sometimes see discussions render in a totally random language. Even worse, some changes are showing up associated with unrecognized users!

Co się dzieje?!

何が起こっているの？！

What is happening?!

You analyze your database and logs, and notice something strange:

1. The changes are only ever wrong for the `Discussion` model
2. The internationalizations are only ever wrong in the discussions views

What’s the difference between `ArticlesController` and `DiscussionsController`?

	class ArticlesController &lt; ApplicationController
	  before_action :set_user
	
	  def create
	    Article.create!(article_params)
	  end
	end
	
	class DiscussionsController &lt; ApplicationController
	  def create
	    Discussion.create!(discussion_params)
	  end
	end

_Oh_ simple, there it is, you’re not setting `AppContext.user` in the discussions controller. But if it’s not getting set then why is it a different user and locale? Why is it set to anything at all?

You check the docs for thread-locals only to find…

- They exist for the lifetime of the thread
- You know that everything in Ruby runs in a thread
- On multi threaded servers like Puma, threads are reused between requests so they can leak data you don’t clear out
- Even on single threaded servers like Unicorn, you still run within the Ruby “main” thread (`Thread.main`). So you can even leak data you don’t clear out in single-threaded servers 🫠

![](https://cdn.uploads.micro.blog/98548/2024/c3ff12657f.png)

&gt; **source**: jpcamara.com

Well _that_ was pretty bad. Whenever a user went to the `DiscussionsController`, it would use information from _whichever user used that thread last_. It might be the same user, but it could just as easily be someone else.

You make sure they get set on every controller now, but also get cleared out in every controller too.

	class AppContext
	  # ...
	  def clear
	    [:app_user, :app_locale].each do |key|
	      Thread.current[key] = nil
	    end
	  end
	end
	
	class ApplicationController
	  after_action :clear_app_context
	
	  def clear_app_context
	    AppContext.clear
	  end
	end
	
	class DiscussionsController &lt; ApplicationController
	  before_action :set_user
	  # ...
	end

Now if someone forgets to add it to their controller, it won’t be set to any user at all - at the end of every request the thread-local is cleared out so it won’t be present[^15].

You deploy the change and _almost_ everything is good.

- Locales are good! No more Polish for English users, or Japanese for Polish users.
- When someone posts to a discussion, it consistently shows the right user in the change history 😮‍💨 
- _But_, there’s still an intermittent change showing up as the wrong user. What other code is there?

The controllers are good. How are the background jobs?

	class FirstTimePostingJob &lt; ApplicationJob
	  def perform(user)
	    AppContext.user = user
	    Congrats.new(user).call
	  end
	end
	
	class ImageUploadJob &lt; ApplicationJob
	  def perform(article, file_url)
	    # Uploads to a service 
	    article.upload_from_url(file_url)
	  end
	end

You’re setting the user in one job, and picking it up in another 🤦🏻‍♂️. When the article is updated to add an image using the `file_url`, sometimes the change is null, and sometimes it’s a totally random user. Hardly ever is it the actual user that added the image 😩.

The same thing you did with controllers, you can’t forget to do in jobs. You need to clean them up the same way (after an action/job finishes), but the logic goes in a new place.

&lt;h4 id=&#34;using-sidekiq&#34;&gt;If you&#39;re using Sidekiq&lt;/h4&gt;

If you’re using a mixture of Sidekiq and ActiveJob, or Sidekiq alone, a [Sidekiq Middleware](https://github.com/sidekiq/sidekiq/wiki/Middleware) is your best option. It will work for both job types because it is called internally by the Sidekiq server when performing a job.

	class ContextClearMiddleware
	  include Sidekiq::ServerMiddleware
	
	  def call(_worker, _job, _queue)
	    yield
	  ensure
	    AppContext.clear
	  end
	end
	
	Sidekiq.configure_server do |config|
	  config.server_middleware do |chain|
	    chain.add ContextClearMiddleware
	  end
	end

&lt;h4 id=&#34;using-activejob&#34;&gt;If you&#39;re using ActiveJob&lt;/h4&gt;

If you exclusively use ActiveJob, then an `after_perform` in your base job class should work just as well, and will work even if you’re using SolidQueue, GoodJob, or any other job adapter that hooks into ActiveJob.

	class ApplicationJob &lt; ActiveJob::Base
	  after_perform :clear_app_context
	
	  def clear_app_context
	    AppContext.clear
	  end
	end

### Next Up
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2024/1d541d2e-2d8c-4380-b166-a2c720da072a.jpeg&#34; alt=&#34;nested ruby concurrency&#34; style=&#34;height: 50%; width: 50%&#34;/&gt;

&gt; **source**: jpcamara.com

This is the first step into this Ruby concurrency series. In Part 2 we’ll dig into a few more topics: true thread locals + fibers, the layered Ruby concurrency model, reusing objects, some more complex threading coordination, and ideas for keeping your code and your dependencies thread safe. More soon 👋🏼!

[^1]:	It doesn’t have to use threads by default, but there’s a good chance you are configured for _some_

[^2]:	None of these servers force you to use multiple threads, but will usually use them by default. Puma’s OOTB configuration in rails uses 3 threads, for instance. [https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt](https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt)

    They use some threads internally as well even if you don’t serve requests using more than 1 thread, possibly with the exclusion of Falcon since its core is the FiberScheduler.

[^3]:	You also don’t _have_ to use these job queues with threads either. But they all use them by default, or at least have internal threaded elements.

[^4]:	You shouldn’t though.

    [http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html)

    [https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/](https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/)

    But if you’re using net-http, you’re using the timeout gem and have no choice.

    There was an attempt to remove timeout from net http that was reverted:[https://github.com/ruby/ruby/commit/f88bff770578583a708093f4a0d8b1483a1d2039](https://github.com/ruby/ruby/commit/f88bff770578583a708093f4a0d8b1483a1d2039)

    It IS safe to use when used inside a fiber scheduler like the async gem, because it swaps out the thread for a fiber timeout

[^5]:	It’s ok to do once, up front, or controlled by mutexes. We’ll discuss that more in a bit.

[^6]:	Sometimes you bake them into a cake for your friends 🎂. You just really love tempfiles 🥹.

[^7]:	By “macros” I mean things like column encryption in Rails using `encrypts`. Internally it uses a class level instance variable called `encrypted_attributes` to track columns you are encrypting on that model. 

    _But_ it is only set once when the class is loaded. If the class is reloaded, it must be locked by a mutex to safely change that value - Rails has a “reloader” that handles this, for instance.

[^8]:	Naive because this is a terribly inefficient way to calculate Fibonacci sequences, which is why it’s a great example for demonstrating how CPU intensive work causes CRuby context switching

[^9]:	And most other languages

[^10]:	Ruby 3.3 introduced the M:N thread scheduler, so there was a ton of refactoring of pthread internals. It may have been more impactful to threading than other versions - but every minor version is a year long large-scale effort that can easily have an impact on these private, internal behaviors

[^11]:	This assumes you’re only loading classes once or safely handle reloads across threads (like Rails does)

[^12]:	The reproduction steps are even a modified form of one of the issues [https://github.com/attr-encrypted/attr\_encrypted/issues/372](https://github.com/attr-encrypted/attr_encrypted/issues/372) and here is the code that fixed the issue [https://github.com/attr-encrypted/attr\_encrypted/pull/320/files](https://github.com/attr-encrypted/attr_encrypted/pull/320/files)

[^13]:	I’ll leave it to others to discuss the merits or pitfalls of using any globals at all, thread-local or otherwise 🤷🏻‍♂️ 

[^14]:	They’re totally susceptible to the copying state issue we just discussed but at least you’re aware of it now

[^15]:	This could be achieved in a rack middleware as well, as opposed to an `after_action`
</source:markdown>
    </item>
    
    <item>
      <title>A comprehensive guide to PgBouncer/Postgres compatibility</title>
      <link>https://jpcamara.com/2023/05/25/a-comprehensive-guide.html</link>
      <pubDate>Thu, 25 May 2023 21:23:46 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2023/05/25/a-comprehensive-guide.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/efb3a4281b.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;My previous post, &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html&#34; target=&#34;_blank&#34;&gt;PgBouncer is important, useful, and fraught with peril&lt;/a&gt;, was a &lt;em&gt;deep&lt;/em&gt; dive into Postgres feature compatibility with different modes of PgBouncer.&lt;/p&gt;
&lt;p&gt;I’m happy with how it came out and it was well received. I &lt;em&gt;think&lt;/em&gt; it is the most comprehensive guide to Postgres/PgBouncer compatibility that exists. But it might be a daunting read for some (9k words 😅), and the name doesn’t clearly convey what it offers - I want it to be easily found when searching for PgBouncer/Postgres pooling questions, and be quickly digestible on the fly.&lt;/p&gt;
&lt;p&gt;Below is a list of topics you may be looking for more information about. While PgBouncer is the most popular option, most of it applies to any of the Postgres pooling options available (&lt;a href=&#34;https://github.com/supabase/supavisor&#34;&gt;Supavisor&lt;/a&gt;, &lt;a href=&#34;https://github.com/postgresml/pgcat&#34;&gt;PgCat&lt;/a&gt;, &lt;a href=&#34;https://github.com/yandex/odyssey&#34;&gt;Odyssey&lt;/a&gt;, etc).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Looking to understand how different &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#turn-it-on&#34; target=&#34;_blank&#34;&gt;PgBouncer modes&lt;/a&gt; work? See &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#session-mode&#34; target=&#34;_blank&#34;&gt;session mode&lt;/a&gt;, &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#statement-mode&#34; target=&#34;_blank&#34;&gt;statement mode&lt;/a&gt;, and &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#transaction-mode&#34; target=&#34;_blank&#34;&gt;transaction mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Want to understand connection pooling a little better? &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#connection-pooling&#34; target=&#34;_blank&#34;&gt;See this section on pooling approaches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Looking at the &lt;a href=&#34;https://www.pgbouncer.org/features.html&#34; target=&#34;_blank&#34;&gt;PgBouncer compatibility table&lt;/a&gt; and wanting more detail on the implications of each feature? See the next section.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;sql-feature-map-for-pooling-modes&#34;&gt;SQL feature map for pooling modes&lt;/h3&gt;
&lt;p&gt;Below is each compatibility table feature linked to the original post. If there isn’t a link, it means the feature works the same way with or without PgBouncer.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;
				Feature
			&lt;/th&gt;
			&lt;th&gt;
				Session pooling
			&lt;/th&gt;
			&lt;th&gt;
				Transaction pooling 
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Startup parameters*
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#statement-timeouts&#34; target=&#34;_blank&#34;&gt;SET/RESET statement_timeout&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#lock-timeouts&#34; target=&#34;_blank&#34;&gt;SET/RESET lock_timeout&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#listen-notify&#34; target=&#34;_blank&#34;&gt;LISTEN/NOTIFY&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				WITHOUT HOLD CURSOR
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;WITH HOLD CURSOR&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements&#34; target=&#34;_blank&#34;&gt;Protocol-level prepared plans&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes, with some gotchas**
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements&#34; target=&#34;_blank&#34;&gt;PREPARE / DEALLOCATE 
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				ON COMMIT DROP temp tables
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;PRESERVE/DELETE ROWS temp tables&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Cached plan reset
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;LOAD statement&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#session-level-locks&#34; target=&#34;_blank&#34;&gt;Session-level advisory locks&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;* Startup parameters are: &lt;code&gt;client_encoding&lt;/code&gt;, &lt;code&gt;datestyle&lt;/code&gt;, &lt;code&gt;timezone&lt;/code&gt;, and &lt;code&gt;standard_conforming_strings&lt;/code&gt;. PgBouncer detects their changes and so it can guarantee they remain consistent for the client.&lt;/p&gt;
&lt;p&gt;** PgBouncer 1.21 introduced protocol-level prepared plan support, though there are some current gotchas which may be resolved in Postgres 17. You can find out more in the &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements&#34;&gt;original post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m biased, but I think it’s a pretty good read in entirety as well. There’s more sections on other gotchas, community pooling suggestions, and a look at work improving Postgres internals to lighten the need for poolers.&lt;/p&gt;
&lt;p&gt;Thanks for reading, and I hope it answers your questions!&lt;/p&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2023/efb3a4281b.png)

My previous post, &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html&#34; target=&#34;_blank&#34;&gt;PgBouncer is important, useful, and fraught with peril&lt;/a&gt;, was a _deep_ dive into Postgres feature compatibility with different modes of PgBouncer.

I’m happy with how it came out and it was well received. I _think_ it is the most comprehensive guide to Postgres/PgBouncer compatibility that exists. But it might be a daunting read for some (9k words 😅), and the name doesn’t clearly convey what it offers - I want it to be easily found when searching for PgBouncer/Postgres pooling questions, and be quickly digestible on the fly.

Below is a list of topics you may be looking for more information about. While PgBouncer is the most popular option, most of it applies to any of the Postgres pooling options available ([Supavisor](https://github.com/supabase/supavisor), [PgCat](https://github.com/postgresml/pgcat), [Odyssey](https://github.com/yandex/odyssey), etc).

- Looking to understand how different &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#turn-it-on&#34; target=&#34;_blank&#34;&gt;PgBouncer modes&lt;/a&gt; work? See &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#session-mode&#34; target=&#34;_blank&#34;&gt;session mode&lt;/a&gt;, &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#statement-mode&#34; target=&#34;_blank&#34;&gt;statement mode&lt;/a&gt;, and &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#transaction-mode&#34; target=&#34;_blank&#34;&gt;transaction mode&lt;/a&gt;
- Want to understand connection pooling a little better? &lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#connection-pooling&#34; target=&#34;_blank&#34;&gt;See this section on pooling approaches&lt;/a&gt;
- Looking at the &lt;a href=&#34;https://www.pgbouncer.org/features.html&#34; target=&#34;_blank&#34;&gt;PgBouncer compatibility table&lt;/a&gt; and wanting more detail on the implications of each feature? See the next section.

### SQL feature map for pooling modes
Below is each compatibility table feature linked to the original post. If there isn’t a link, it means the feature works the same way with or without PgBouncer.

&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;
				Feature
			&lt;/th&gt;
			&lt;th&gt;
				Session pooling
			&lt;/th&gt;
			&lt;th&gt;
				Transaction pooling 
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Startup parameters*
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#statement-timeouts&#34; target=&#34;_blank&#34;&gt;SET/RESET statement_timeout&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#lock-timeouts&#34; target=&#34;_blank&#34;&gt;SET/RESET lock_timeout&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#listen-notify&#34; target=&#34;_blank&#34;&gt;LISTEN/NOTIFY&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				WITHOUT HOLD CURSOR
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;WITH HOLD CURSOR&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements&#34; target=&#34;_blank&#34;&gt;Protocol-level prepared plans&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes, with some gotchas**
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements&#34; target=&#34;_blank&#34;&gt;PREPARE / DEALLOCATE 
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				ON COMMIT DROP temp tables
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;PRESERVE/DELETE ROWS temp tables&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Cached plan reset
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#unavailable&#34; target=&#34;_blank&#34;&gt;LOAD statement&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				&lt;a href=&#34;https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#session-level-locks&#34; target=&#34;_blank&#34;&gt;Session-level advisory locks&lt;/a&gt;
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&gt; \* Startup parameters are: `client_encoding`, `datestyle`, `timezone`, and `standard_conforming_strings`. PgBouncer detects their changes and so it can guarantee they remain consistent for the client.
&gt; 
&gt; \*\* PgBouncer 1.21 introduced protocol-level prepared plan support, though there are some current gotchas which may be resolved in Postgres 17. You can find out more in the [original post](https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html#prepared-statements).

I’m biased, but I think it’s a pretty good read in entirety as well. There’s more sections on other gotchas, community pooling suggestions, and a look at work improving Postgres internals to lighten the need for poolers.

Thanks for reading, and I hope it answers your questions!

</source:markdown>
    </item>
    
    <item>
      <title>PgBouncer is useful, important, and fraught with peril</title>
      <link>https://jpcamara.com/2023/04/12/pgbouncer-is-useful.html</link>
      <pubDate>Wed, 12 Apr 2023 21:40:33 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2023/04/12/pgbouncer-is-useful.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/e0723a5982.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Updated &lt;strong&gt;2024-09-17&lt;/strong&gt; to reflect updated PgBouncer support for protocol-level prepared statements 🐘&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To start, I want to say that I’m appreciative that PgBouncer exists and the work its open source maintainers put into it. I also love working with PostgreSQL, and I’m thankful for the incredible amount of work and improvements that go into it as well.&lt;/p&gt;
&lt;p&gt;I also think community and industry enthusiasm around Postgres is at an all time high. There are more managed hosting options than ever (&lt;a href=&#34;https://www.crunchydata.com&#34;&gt;Crunchy Data&lt;/a&gt;, &lt;a href=&#34;https://render.com/docs/databases&#34;&gt;Render&lt;/a&gt;, &lt;a href=&#34;https://fly.io/docs/postgres/&#34;&gt;Fly.io&lt;/a&gt;, and on and on), deep extensions like &lt;a href=&#34;https://postgresml.org&#34;&gt;PostgresML&lt;/a&gt;, &lt;a href=&#34;https://www.citusdata.com&#34;&gt;Citus&lt;/a&gt; and &lt;a href=&#34;https://www.timescale.com&#34;&gt;Timescale&lt;/a&gt;, serverless options like &lt;a href=&#34;https://neon.tech&#34;&gt;Neon&lt;/a&gt;, and real-time services like &lt;a href=&#34;https://supabase.com&#34;&gt;Supabase&lt;/a&gt; with Postgres at their center. Postgres is a robust, advanced and &lt;em&gt;fast&lt;/em&gt; RDBMS capable of handling the needs of most every application.&lt;/p&gt;
&lt;p&gt;I just find the current state of recommendations and guidance around scaling Postgres to be confounding. And it feels surprising for new Postgres users to discover that one of the most common scaling options relies on a solution like PgBouncer.&lt;/p&gt;
&lt;p&gt;Over the years I’ve read dozens of articles around scaling and maintaining Postgres databases, and they always understate the impact of PgBouncer on your application. They casually mention unusable features without any exploration, or the numerous ways you can silently break expected query behavior. The advice is just to turn it on. &lt;strong&gt;I want it to be clear that as your application scales, PgBouncer is often necessary but isn’t free&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The following sections provide an overview of what connection pooling is in general, how connection pooling modes work in PgBouncer and similar tools, and then I dig into every Postgres feature that does not work in PgBouncer transaction mode and what the implications are. This is the PgBouncer article I wish existed the first time I used it - let’s get going 🐘!&lt;/p&gt;
&lt;h3 id=&#34;contents&#34;&gt;Contents&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#connection-pooling&#34;&gt;What is connection pooling?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#separate-tool&#34;&gt;Why do I need a separate tool from Postgres?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#framework-pool&#34;&gt;Framework pooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#client-pool&#34;&gt;Client proxy pooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#server-pool&#34;&gt;Server proxy pooling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#turn-it-on&#34;&gt;Can I just turn on PgBouncer and get scaling for free?&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#session-mode&#34;&gt;Session mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#statement-mode&#34;&gt;Statement mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#transaction-mode&#34;&gt;Transaction mode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#perils&#34;&gt;Perils&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#invalid-statements&#34;&gt;Detecting invalid statements 😑&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#lock-timeouts&#34;&gt;Lock timeouts (SET/RESET) 🔓&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#statement-timeouts&#34;&gt;Statement timeouts (SET/RESET) ⏳&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#transparency&#34;&gt;Transparency 👻&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#prepared-statements&#34;&gt;Prepared Statements (PREPARE/DEALLOCATE, Protocol-level prepared plans) ✔️&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#throughput&#34;&gt;Pool throughout / Long running queries 🏃‍♂️&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#session-level-locks&#34;&gt;Session Level Advisory Locks 🔐 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#listen-notify&#34;&gt;Listen/Notify 📣&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#single-threaded&#34;&gt;The single thread 🪡 &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#pg_dump&#34;&gt;pg_dump 🚮&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#unavailable&#34;&gt;Other unavailable features 🫥&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#linting&#34;&gt;Linting 🧶&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#future-improvements&#34;&gt;Can we improve connections without a pooler?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#alternatives&#34;&gt;PgBouncer alternatives&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;connection-pooling&#34;&gt;What is connection pooling?&lt;/h2&gt;
&lt;p&gt;PgBouncer is a lightweight connection pooler for PostgreSQL. What does that mean exactly? What is connection pooling and why is it needed?&lt;/p&gt;
&lt;p&gt;Opening a connection is expensive: a new Postgres client connection involves TCP setup, process creation and backend initialization – all of which are costly in terms of time and system resources. A connection pool keeps a set of connections available for reuse so we can avoid that overhead past initial connection.&lt;/p&gt;
&lt;p&gt;There are three main levels of connection pooling&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;framework-pool&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/0c4ffd30c0.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Framework connection pooling&lt;/strong&gt;. This is a common feature of many frameworks/libraries. Within a given process, you maintain a pool of active connections that are shared between code, generally running across threads. Whenever you handle some processing in a server request, a background process, a job, etc, you open a connection and keep that connection open. When that piece of work finishes and a new piece of work starts, you can reuse the connection without the expense of opening a new connection to the database every single time. These connections are usually local to a particular operating system process, so you gain no benefit outside of that process (and if you’re scaling Postgres, you probably have lots of processes)&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;client-pool&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/d281d0294d.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;One level higher, you can have &lt;strong&gt;client level connection pooling&lt;/strong&gt; outside of your code. PgBouncer can handle this, and instead of independent unsharable process-isolated connections you proxy all of your connections through PgBouncer. But it runs on your server, so you still cannot share connections between servers (and again, when needing to do it you probably have lots of servers).&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;server-pool&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/cd987b4e04.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server level connection pooling&lt;/strong&gt;. Here we host PgBouncer independent of our servers and connect to a single central PgBouncer instance&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. This is the most robust form of connection pooling because independent of anything else in your code or server, you are guaranteed that any client connection is coming from the pool.&lt;/p&gt;
&lt;h2 id=&#34;separate-tool&#34;&gt;Why do I need a separate tool from Postgres?&lt;/h2&gt;
&lt;p&gt;That’s all great but&amp;hellip; why do we need it?&lt;/p&gt;
&lt;p&gt;There are two primary layers to this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maintaining connections is beneficial as a base feature. Less memory and io churn, less latency before running queries. Less pressure on the database constantly opening and closing connections.&lt;/li&gt;
&lt;li&gt;Postgres connections get expensive very quickly. &lt;em&gt;Surprisingly&lt;/em&gt; quickly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here are some general community guidelines around allowable Postgres connection counts based on a mixture of community experience and specific benchmarking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In terms of what some managed services even offer: &lt;a href=&#34;https://supabase.com/blog/supabase-pgbouncer&#34;&gt;Supabase&lt;/a&gt; offers a max of &lt;em&gt;50&lt;/em&gt; connections, &lt;a href=&#34;https://neon.tech&#34;&gt;Neon&lt;/a&gt; offers a max of &lt;em&gt;100&lt;/em&gt; connections, and &lt;a href=&#34;https://render.com/docs/databases#connecting-to-your-database&#34;&gt;Render&lt;/a&gt; offers a max of 397 connections.&lt;/li&gt;
&lt;li&gt;The general upper bound recommendation is a &lt;em&gt;max&lt;/em&gt; of 500 active connections. Services like &lt;a href=&#34;https://elements.heroku.com/addons/heroku-postgresql&#34;&gt;Heroku Postgres&lt;/a&gt; even &lt;em&gt;enforce&lt;/em&gt; a hard limit of 500 connections&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;Even at 500 connections, your server is going to be strained. &lt;a href=&#34;https://www.enterprisedb.com/postgres-tutorials/why-you-should-use-connection-pooling-when-setting-maxconnections-postgres&#34;&gt;This more recent (as of 2023) enterprisedb article&lt;/a&gt; analyzed connection performance and found that 300-400 active connections seems optimal. This &lt;a href=&#34;https://brandur.org/postgres-connections&#34;&gt;article from Brandur&lt;/a&gt; is older (2018) but seems to reinforce this idea as well&lt;/li&gt;
&lt;li&gt;There have been &lt;a href=&#34;https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462&#34;&gt;some more recent connection improvements in Postgres&lt;/a&gt; (as of version 14) handling idle connections more efficiently&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;, but active connections are still expensive&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; and idle connections have not reached the scale of a dedicated pooler&lt;/li&gt;
&lt;li&gt;The reality of 500 connections is it sounds extremely low but those connections can handle &lt;em&gt;a ton of throughput&lt;/em&gt;. The &lt;em&gt;problem&lt;/em&gt; is, as a metric of pure concurrency, real connections have a hard upper limit. So if you try to have five thousand clients connect simultaneously, you’re going to start getting loads of connection errors&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To improve the cost of connection overhead, general connection pooling is helpful and a PgBouncer instance in its default session based mode can handle it. But to improve concurrency things have to get a bit&amp;hellip; &lt;em&gt;quirky&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There are two modes in PgBouncer which give clients access to more connections than Postgres &lt;em&gt;actually&lt;/em&gt; has available. They rely on the idea that at any given time many of your connections are idle, so you can free up usage of idle connections to improve concurrency.&lt;/p&gt;
&lt;h2 id=&#34;turn-it-on&#34;&gt;Can I just turn on PgBouncer and get scaling for free?&lt;/h2&gt;
&lt;p&gt;Kind of? But not really? It’s complicated.&lt;/p&gt;
&lt;p&gt;Internally, PgBouncer will manage a pool of connections for you. The default pooling mode it starts with, session pooling, is conservative, and in most cases will not provide improved concurrency&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I’m going to hand wave a bit past two of the modes and focus on the typical recommendation.&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;session-mode&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/7391257b1c.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session mode&lt;/strong&gt; is the default and most conservative mode. This is a 1:1 - your local connection truly holds onto a full connection until you close it. This does little to help you scale connection concurrency, but it helps with latency and connection churn overhead.&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;statement-mode&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/6e486ad1b1.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Statement mode&lt;/strong&gt; is the most aggressive mode and means your connection goes back into the pool after &lt;em&gt;every statement&lt;/em&gt;. You lose the ability to use transactions 😰 - that seems wild and unusable for only the most specialized of use cases.&lt;/p&gt;
&lt;p&gt;&lt;span id=&#34;transaction-mode&#34;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/9e0451a965.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The mode that results in a more sane balance of improved concurrency and retained critical database features is &lt;strong&gt;transaction mode&lt;/strong&gt;. Transaction mode means your connection stays consistent as long as you’re in a transaction. Once the transaction finishes, your code &lt;em&gt;thinks&lt;/em&gt; it still has real connection but PgBouncer actually releases the connection back into the pool internally. This is &lt;em&gt;session sharing&lt;/em&gt;, your session is going to be shared with other connections without being reset or closed.&lt;/p&gt;
&lt;p&gt;Transaction mode is a powerful concept. Your code in general has lots of database downtime. Most code does not solely operate on the database - it takes CPU cycles, interacts with files, makes network calls, and calls other data stores. During that time, your connection sits idle and unused for what in computing and database terms is an eternity. By releasing that back into the pool outside of transactions you free up your idle connection for use by a client who actually needs it. This way your 500 available connections can services thousands of clients, instead of a 1:1 with the number of available connections.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- connection is actually pulled from the pool inside PgBouncer
BEGIN;
INSERT INTO...;
UPDATE;
COMMIT;
-- connection goes back to the pool inside PgBouncer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem with transaction mode is that this tiny configuration change quietly changes not only your ability to scale, but also the way your connections &lt;em&gt;behave&lt;/em&gt;. It breaks the expected command semantics between client and database server. And understanding whether you’ve gotten things right in transaction mode is &lt;em&gt;very difficult&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Let’s say you’ve been operating with PgBouncer in session mode (or operating without a proxy at all), and you make the switch to transaction mode. Your perspective on how you can use Postgres needs to change - so now we’re onto the &lt;em&gt;peril&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&#34;perils&#34;&gt;Perils&lt;/h2&gt;
&lt;p&gt;Many of the following items are documented shortcomings of PgBouncer in &lt;a href=&#34;#transaction-mode&#34;&gt;transaction mode&lt;/a&gt;. But:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They’re treated lightly&lt;/li&gt;
&lt;li&gt;Their repercussions and downsides are not discussed&lt;/li&gt;
&lt;li&gt;PgBouncer is often recommended without mentioning them&lt;/li&gt;
&lt;li&gt;PgBouncer is often recommended at the same time as recommending incompatible transaction mode features like session level advisory locks and session level statement timeouts&lt;/li&gt;
&lt;li&gt;The non-determinism introduced by using incompatible statements is not discussed (ie, I execute a statement in Process A and suddenly Process B errors out due to it)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Assume anytime I mention PgBouncer after this point I am referring to &lt;a href=&#34;#transaction-mode&#34;&gt;transaction mode&lt;/a&gt;. Here we go!&lt;/p&gt;
&lt;h3 id=&#34;invalid-statements&#34;&gt;Detecting invalid statements 😑&lt;/h3&gt;
&lt;p&gt;PgBouncer happily accepts statements that are not supported in transaction mode. The problem is pushed onto the developer, which means they &lt;em&gt;can&lt;/em&gt; and &lt;em&gt;will&lt;/em&gt; get it wrong&lt;sup id=&#34;fnref:8&#34;&gt;&lt;a href=&#34;#fn:8&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This is by design. The sense I get is that PgBouncer was specifically architected to not analyze any statements and so it would be a big change for them to handle this&lt;sup id=&#34;fnref:9&#34;&gt;&lt;a href=&#34;#fn:9&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;9&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Amazon has a similar tool to PgBouncer called RDS Proxy, and it has a feature called “&lt;a href=&#34;https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-managing.html#rds-proxy-pinning&#34;&gt;connection pinning&lt;/a&gt;”. If it detects a statement that is incompatible with transaction mode, it will automatically hold that connection for that client for the duration of their session.&lt;/p&gt;
&lt;p&gt;This is both highly useful and simultaneously problematic. It means query behavior is consistent with your expectations (🙌🏼) but also that you can silently kill all concurrency benefits (😑). If enough queries are run that trigger connection pinning, all of a sudden you may throttle your &lt;a href=&#34;#throughput&#34;&gt;throughput&lt;/a&gt;. But it does give you an escape hatch for safely running statements which are not transaction compatible without having to jump through any hoops.&lt;/p&gt;
&lt;p&gt;I’d be fine with some logging I could monitor. As far as I can tell there is nothing like this in PgBouncer, and so all the burden lands on you to get it right. As one engineer, or a few engineers, all aware of potential issues, you can probably maintain that. But what about dozens of engineers? Or hundreds? Thousands? All with varying levels of experience with databases and poolers? There’s going to be mistakes.&lt;/p&gt;
&lt;h3 id=&#34;lock-timeouts&#34;&gt;Lock Timeouts (SET/RESET) 🔓&lt;/h3&gt;
&lt;p&gt;Unless you &lt;em&gt;like&lt;/em&gt; app downtime, you should be using a &lt;code&gt;lock_timeout&lt;/code&gt; when running &lt;a href=&#34;https://www.postgresql.org/docs/current/ddl.html&#34;&gt;DDL&lt;/a&gt;. It’s a critical aspect of &lt;a href=&#34;https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680&#34;&gt;zero downtime migrations&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea is to set it to a limit that would be acceptable for queries in your application to slowdown by - waiting to acquire a lock can cause related queries to queue up behind your DDL operation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- slow select
SELECT * FROM my_table;

-- DDL starts in separate process, blocked on acquiring the lock by the 
--    slow query
ALTER TABLE my_table...

-- Subsequent queries start queuing up...
SELECT * FROM my_table WHERE id = 123;
SELECT * FROM my_table WHERE id = 234;
--- ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that scenario, the slow query is running the show. Until it finishes, &lt;em&gt;all the other queries to that table are stuck&lt;/em&gt;. That goes on long enough and users can’t use the system. A bit longer and your app starts timing out. A bit longer you’re running out of &lt;em&gt;connections&lt;/em&gt;. Now you’re staring at a total app outage, about ready to kill all of your connections in a desperate attempt to salvage things, contemplating a career change to landscaping where you can at most impact one person at a time, right? That sounds nice, doesn’t it?&lt;/p&gt;
&lt;p&gt;I’ve of course never experienced that. I’m just &lt;em&gt;very&lt;/em&gt; creative 💀. But if &lt;em&gt;you&lt;/em&gt; have experienced that, or you’d like to &lt;em&gt;avoid&lt;/em&gt; experiencing that, use a &lt;code&gt;lock_timeout&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET lock_timeout TO &#39;2s&#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Now&lt;/em&gt; if your DDL cannot acquire a lock it will throw an error after 2 seconds. That should be an ok delay in running queries, and you can retry the operation later.&lt;/p&gt;
&lt;p&gt;But wait! Are you connected to PgBouncer?! You may want to bring up that landscaping help-wanted listing again&amp;hellip; 🌳&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SET&lt;/code&gt; operations apply at the &lt;a href=&#34;#session-mode&#34;&gt;session level&lt;/a&gt;. This means that on a PgBouncer connection, there is no guarantee our &lt;code&gt;lock_timeout&lt;/code&gt; will still be applied when we run our DDL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Process 1
-- PgBouncer pulls connection 1
SET lock_timeout TO &#39;2s&#39;;
-- connection 1 goes back to the pool

-- Meanwhile, in Process 2:
-- PgBouncer pulls connection 3
SELECT id FROM my_table, pg_sleep(30);

-- Back in Process 1:
-- PgBouncer pulls connection 2
-- This connection has no lock_timeout set, so it will hang 
--    until our pg_sleep query finishes 30 seconds later, and all
--    queries to my_table after it are stuck for those 30 seconds as well
ALTER TABLE my_table...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’d be easy to argue “don’t have slow queries”. And that should be the goal! But we don’t call it “happy path uptime 🌼”, we call it “&lt;em&gt;zero&lt;/em&gt; downtime”. It means even if things go wrong, you don’t go down. There can also be other operations that hold a lock on your table, so you simply can’t rely on successfully acquiring that lock&lt;sup id=&#34;fnref:10&#34;&gt;&lt;a href=&#34;#fn:10&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So what can we do? There are two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bypass PgBouncer and go straight to the database&lt;/li&gt;
&lt;li&gt;Use a transaction level &lt;code&gt;lock_timeout&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;bypassing-pgbouncer&#34;&gt;Bypassing PgBouncer&lt;/h4&gt;
&lt;p&gt;Your safest bet is to go with option (1). You should have some ability to directly connect to your database, so take advantage of it and don’t jump through hoops to run DDL safely.&lt;/p&gt;
&lt;p&gt;The biggest obstacle you hit with (1) is &lt;a href=&#34;#transparency&#34;&gt;transparency&lt;/a&gt;: PgBouncer &lt;em&gt;really&lt;/em&gt; doesn’t want you to know whether you are connected to the real database or not. There’s no &lt;em&gt;easy&lt;/em&gt; answer there, but by validating a setup where you consistently run your DDL process directly against Postgres then you’re set.&lt;/p&gt;
&lt;h4 id=&#34;use-transaction-level-statements&#34;&gt;Use transaction level statements&lt;/h4&gt;
&lt;p&gt;There is a transaction local equivalent to the &lt;code&gt;SET&lt;/code&gt; statement: &lt;code&gt;SET LOCAL&lt;/code&gt;. Using our example from earlier:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Process 1
-- PgBouncer pulls connection 1
BEGIN;
SET LOCAL lock_timeout TO &#39;2s&#39;;
-- connection 1 stays checked out

-- Meanwhile, in Process 2:
-- PgBouncer pulls connection 3
SELECT id FROM my_table, pg_sleep(30);

-- Back in Process 1:
-- Connection 1 is still checked out
ALTER TABLE my_table...
-- lock_timeout raises an error after 2 seconds waiting, and 
--    we avoid our downtime!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DDL in Postgres is transactional, so it’s valid to start our transaction, set our &lt;code&gt;lock_timeout&lt;/code&gt; using &lt;code&gt;SET LOCAL&lt;/code&gt;, then start our DDL operation. Our transaction local setting will stick with us until the transaction commits or rolls back, so we safely keep our timeout and rollback our DDL.&lt;/p&gt;
&lt;p&gt;It’s not a &lt;em&gt;terrible&lt;/em&gt; solution (1 is still better), except for two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Concurrent indexes&lt;/li&gt;
&lt;li&gt;Tooling&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Another zero downtime star is the concurrent index. When you create a new index on a table you run the chance of locking it up long enough to cause downtime. Here’s the answer to that problem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX CONCURRENTLY index_name ON my_table;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Concurrent indexes are created without an exclusive lock, so your normal operations keep going while it builds the index in the background. The &lt;em&gt;problem&lt;/em&gt; is they can’t be run in a transaction, so &lt;code&gt;SET LOCAL&lt;/code&gt; is not an option.&lt;/p&gt;
&lt;p&gt;Because they don’t require an exclusive lock, setting a &lt;code&gt;lock_timeout&lt;/code&gt; is less important. But if there is contention and you just can’t get that index to acquire it’s shared lock, do you really want it to run forever?&lt;/p&gt;
&lt;p&gt;As for (2), popular tooling usually does not handle &lt;code&gt;SET LOCAL&lt;/code&gt; for you. In the Rails/ActiveRecord world there are several libraries that will automatically apply zero downtime policies for you, but they all assume you have an exclusive connection and operate at the &lt;code&gt;SET&lt;/code&gt; session level.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://en.m.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions&#34;&gt;In PgBouncer, the road to downtime is paved with session level statements&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just go with (1), keep your sanity, throw away the diary entries about living out your days breathing in the smell of fresh cut grass, and connect directly to Postgres to run DDL with &lt;code&gt;SET lock_timeout&lt;/code&gt; calls.&lt;/p&gt;
&lt;h3 id=&#34;statement-timeouts&#34;&gt;Statement timeouts (SET/RESET) ⏳&lt;/h3&gt;
&lt;p&gt;Determined not to repeat your experiences from &lt;code&gt;lock_timeout&lt;/code&gt;, you read about this thing called &lt;code&gt;statement_timeout&lt;/code&gt;. This little magic wand makes it so you control how long a statement is allowed to run 🪄.&lt;/p&gt;
&lt;p&gt;So here it is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET statement_timeout TO &#39;2s&#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those greedy queries now don’t stand a chance. You can tame your long running queries and avoid blocking your DDL! You ignore my advice to always use &lt;code&gt;lock_timeout&lt;/code&gt;, say “bye losers” to long running queries, and fire off that DDL again&amp;hellip; oh god. Why are things slowing down. Now they’re timing out. &lt;em&gt;The connections are filling up.&lt;/em&gt; What is &lt;em&gt;happening?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media.tenor.com/MYZgsN2TDJAAAAAC/this-is.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Oh riiiight. You forgot. You’re using PgBouncer. &lt;code&gt;SET&lt;/code&gt; is off the table. Should have set that &lt;code&gt;lock_timeout&lt;/code&gt; 🔐&amp;hellip;&lt;/p&gt;
&lt;p&gt;If I had a nickel for every time someone mentioned &lt;code&gt;SET statement_timeout&lt;/code&gt; and PgBouncer in the same article&amp;hellip;&lt;sup id=&#34;fnref:11&#34;&gt;&lt;a href=&#34;#fn:11&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;11&lt;/a&gt;&lt;/sup&gt; I know no one sharing this content is doing it maliciously, but be aware that these are misleading and incompatible features.&lt;/p&gt;
&lt;h4 id=&#34;with-lock_timeout-why-does-statement_timeout-even-matter&#34;&gt;With lock_timeout, why does statement_timeout even matter?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Statement timeouts are helpful for long running queries so they cancel earlier. If a client disconnects, Postgres will periodically check for the connection and try to cancel the query when it goes away. But a query &lt;a href=&#34;https://dba.stackexchange.com/a/81424/256107&#34;&gt;with runaway cpu&lt;/a&gt; usage will just keep running even if the client dies or disconnects. That means you lose that connection until the query finishes, which can take minutes (or hours)&lt;/li&gt;
&lt;li&gt;The database default is 0, which means there is no limit. In some contexts this is not a problem, but particularly for web requests this is excessive&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first time I used &lt;code&gt;statement_timeout&lt;/code&gt; was from a blog recommendation to limit statements for requests in web applications. In a web request, you usually have an upper limit on how long you allow them to run before they time out - this conserves resources, protects against runaway buggy code and helps with bad actors. It made sense that I’d set it to something conservative on all my web connections to deal with long running queries.&lt;/p&gt;
&lt;p&gt;I deployed the code and for a little while things seemed to work well. Then I saw something odd. This started popping up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;canceling statement due to statement timeout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But in my&amp;hellip; &lt;em&gt;background jobs&lt;/em&gt;? My web requests were tuned to be fast, but the constraints around my background processes were a bit&amp;hellip; looser. Can you guess what I had recently enabled? PgBouncer in transaction mode. My session level statement timeout was being swapped out from my web request, picked up by my job, and caused my job to timeout instead - web request safety was off the rails and longer running jobs were intermittently failing.&lt;/p&gt;
&lt;p&gt;So is there any way we can use it? There’s a couple ways I know of, but nothing great when pooling.&lt;/p&gt;
&lt;h4 id=&#34;our-old-friend-transaction&#34;&gt;Our old friend transaction&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;BEGIN;
SET LOCAL statement_timeout &#39;5s&#39;;
SELECT ...
COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Something about wrapping a SELECT in a transaction feels kind of strange, but it works. If you have targeted concerns, you can wrap particular queries in a transaction and use &lt;code&gt;SET LOCAL&lt;/code&gt; to localize the &lt;code&gt;statement_timeout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is absolutely not a viable solution for a whole request lifecycle. If I wanted to attempt my web request level timeouts again, no way am I wrapping every web request in one giant transaction. Postgres doesn’t have a concept of nested transactions so any code I have that may be operating transactionally is gonna be in for some confusing surprises&lt;sup id=&#34;fnref:12&#34;&gt;&lt;a href=&#34;#fn:12&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;12&lt;/a&gt;&lt;/sup&gt;. And most importantly, wrapping my whole request in a transaction means I’ve completely negated the benefit of proxy pooling - now my request lifecycles are basically 1:1 with my connection sessions.&lt;/p&gt;
&lt;h4 id=&#34;apply-statement-timeouts-per-user&#34;&gt;Apply statement timeouts per user&lt;/h4&gt;
&lt;p&gt;I’ve never tried it, but I’ve seen it recommended to set statement timeouts per user when using PgBouncer. That seems to have a couple problems I can think of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It’s not dynamically configurable.&lt;/li&gt;
&lt;li&gt;It dilutes the pool of available connections per context&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(1) is definitely inconvenient. If you have different contexts where you’d like to apply different timeout constraints, this would be way too cumbersome to maintain.&lt;/p&gt;
&lt;p&gt;But (2) &lt;em&gt;feels&lt;/em&gt; like a deal breaker. If I want to constrain my web requests to a conservative timeout, but give my background processes more wiggle room, my pool size of real connections is now split instead of sharing a pool of total available database connections. I also have to manage making sure each context uses the appropriate user, or things will go badly.&lt;/p&gt;
&lt;p&gt;It’s technically an option, but seems trickier to maintain and monitor.&lt;/p&gt;
&lt;h3 id=&#34;transparency&#34;&gt;Transparency 👻&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://media2.giphy.com/media/xT5LMN0UgalbScp6uI/giphy.gif?cid=6c09b952f7248c90c21812529981462733f1d648a5076839&amp;amp;rid=giphy.gif&amp;amp;ct=g&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don’t understand why my session features aren’t working. I always make sure to use plenty of Postgr&amp;hellip;PgBouncer?!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is very difficult to tell when you are or aren’t using PgBouncer, &lt;a href=&#34;https://github.com/pgbouncer/pgbouncer/issues/249&#34;&gt;which is unfortunately by design&lt;/a&gt;. It considers itself a transparent proxy. In &lt;a href=&#34;#session-mode&#34;&gt;session mode&lt;/a&gt;, that’s pretty much true. But in &lt;a href=&#34;#transaction-mode&#34;&gt;transaction&lt;/a&gt; and &lt;a href=&#34;#statement-mode&#34;&gt;statement&lt;/a&gt; mode you are working with bizarro Postgres. It all works the same except when it doesn’t.&lt;/p&gt;
&lt;p&gt;So if you want a regular connection because you need a feature not available in transaction mode, being sure you did it right is extremely difficult.&lt;/p&gt;
&lt;p&gt;I have had a hell of a time verifying that some servers are or aren’t running with PgBouncer. Server A is using pub sub, I don’t want it. Server B needs the throughput, I want it. How can I make sure someone never makes a mistake and attaches the server to the wrong place? Basically, I can’t.&lt;/p&gt;
&lt;p&gt;When it comes to production code I like to be paranoid. On a large enough codebase, and team, and user base, unusual things are bound to happen, sometimes regularly. I try to write code and configure environments so the right way is easy and the wrong way is hard. PgBouncer does not make that easy.&lt;/p&gt;
&lt;p&gt;On this particular point I’d love to say I have some kind of advice to act on, but it mostly takes testing and validating your setup. If someone out there has better ideas or tips, I am all ears.&lt;/p&gt;
&lt;h3 id=&#34;prepared-statements&#34;&gt;Prepared Statements (PREPARE/DEALLOCATE, Protocol-level prepared plans) ✔️&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;📝 Update as of PgBouncer version 1.21 - protocol-level prepared statements are now supported! See &lt;a href=&#34;https://www.crunchydata.com/blog/prepared-statements-in-transaction-mode-for-pgbouncer&#34;&gt;this crunchy data article&lt;/a&gt; for more specifics. &lt;code&gt;PREPARE&lt;/code&gt; style statements will still never be supported, and there are still some gotchas with protocol-level support you can learn about in that article.&lt;/p&gt;
&lt;p&gt;Everything mentioned here is still relevant for versions &amp;lt; 1.21. As well, it still gives a thorough explanation of why &amp;ldquo;turning off&amp;rdquo; prepared statements while still utilizing &lt;code&gt;libpq&lt;/code&gt; (as most libraries do) is a-ok ✌️&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PgBouncer has a public relations problem when it comes to prepared statements. This is all the &lt;a href=&#34;https://www.pgbouncer.org/features.html&#34;&gt;PgBouncer docs say&lt;/a&gt; about them:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;
				Feature
			&lt;/th&gt;
			&lt;th&gt;
				Session pooling
			&lt;/th&gt;
			&lt;th&gt;
				Transaction pooling
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				`PREPARE` / `DEALLOCATE`
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Protocol-level prepared plans
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				No*
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;* It is possible to add support for that into PgBouncer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Kind of feels&amp;hellip; alarming. No prepared statements in transaction mode?! Aren’t those&amp;hellip; important? Even further when you go to use PgBouncer with Hibernate or ActiveRecord (and I’m sure others) you’ll see the recommendation to configure them to &lt;em&gt;turn off&lt;/em&gt; prepared statements 😱. Does it surprise you a bit to hear that? Make you feel a little queasy maybe?&lt;/p&gt;
&lt;p&gt;I had it drilled into me early in my career that prepared statements are a critical part of protecting against SQL injection. In the &lt;a href=&#34;https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html&#34;&gt;OWASP SQL Injection Prevention Cheatsheet&lt;/a&gt; the very first recommendation is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use of Prepared Statements (with Parameterized Queries)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So PgBouncer tells me I need to &lt;em&gt;turn them off?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://thumbs.gfycat.com/AstonishingEarlyHornet-size_restricted.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The first time I used PgBouncer in an application I spent &lt;em&gt;a lot&lt;/em&gt; of time figuring out how turning off prepared statements was safe to do. It turns out that prepared statements in Postgres mean a few things, but come down to two main options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Named prepared statements&lt;/li&gt;
&lt;li&gt;Unnamed prepared statements&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Named&lt;/em&gt; prepared statements are reusable, and are tied to the connection session.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Unnamed&lt;/em&gt; prepared statements are single use, and have no association to the connection session.&lt;/p&gt;
&lt;p&gt;There are two ways to create a &lt;em&gt;named&lt;/em&gt; prepared statement and one way to create an &lt;em&gt;unnamed&lt;/em&gt; prepared statement:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;PREPARE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Protocol-level Parse/Bind/Execute with a name specified&lt;/li&gt;
&lt;li&gt;Protocol-level Parse/Bind/Execute with no name specified&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;PgBouncer says it doesn’t support prepared statements in either &lt;code&gt;PREPARE&lt;/code&gt; or protocol-level format. What it &lt;em&gt;actually&lt;/em&gt; doesn’t support are &lt;em&gt;named&lt;/em&gt; prepared statements in any form. That’s because named prepared statements live in the session and in &lt;a href=&#34;#transaction-mode&#34;&gt;transaction mode&lt;/a&gt; you can switch sessions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- PgBouncer pulls connection 1
PREPARE bouncer_since (int, timestamp) AS
SELECT * 
FROM bouncers b
INNER JOIN guests g ON g.bouncer_id = b.id
WHERE b.id = $1 AND b.created &amp;gt; $2;
-- connection 1 goes back to the pool

-- PgBouncer pulls connection 2
EXECUTE bouncer_since(1, now() - INTERVAL &#39;2 days&#39;);
-- 💣 ERROR: prepared statement &amp;quot;bouncer_since&amp;quot; does not exist 💣
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But &lt;em&gt;unnamed prepared statements are totally fine&lt;/em&gt;. In fact, I’d be shocked if the current client library you’re using to connect to Postgres does not already switch to them if “prepared statements” (again, so damn misleading) are “turned off”.&lt;/p&gt;
&lt;p&gt;But wait. What the heck is an unnamed statement? &lt;code&gt;PREPARE&lt;/code&gt; &lt;em&gt;requires&lt;/em&gt; a name&amp;hellip; how can I make a prepared statement without a name?&lt;/p&gt;
&lt;h4 id=&#34;protocol-level-prepared-plans&#34;&gt;Protocol-level prepared plans&lt;/h4&gt;
&lt;p&gt;&lt;img src=&#34;https://media0.giphy.com/media/P5wPrhzZDdeJW/giphy.gif?cid=6c09b95256738d3ee35e24f988a790f60659836b97f75ee8&amp;amp;rid=giphy.gif&amp;amp;ct=g&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The alternative to the &lt;code&gt;PREPARE&lt;/code&gt; statement is to directly communicate with Postgres at the protocol level.&lt;/p&gt;
&lt;p&gt;I had to dig a bit to get a handle on this - I started from a common Ruby ORM called ActiveRecord, dug into the Ruby “pg” gem &lt;em&gt;it&lt;/em&gt; uses, then went one layer deeper into &lt;code&gt;libpq&lt;/code&gt;, which is part of Postgres itself.&lt;/p&gt;
&lt;p&gt;If we use active record as an example, &lt;a href=&#34;https://guides.rubyonrails.org/configuring.html#configuring-a-postgresql-database&#34;&gt;when prepared statements are “disabled”&lt;/a&gt;, the postgres adapter internally calls &lt;code&gt;exec_no_cache&lt;/code&gt; in &lt;code&gt;activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def exec_no_cache(sql, name, binds...)
  #...
  conn.exec_params(sql, type_casted_binds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;rsquo;s powered by the ruby “pg” gem, which when calling &lt;code&gt;exec_params&lt;/code&gt; from ruby ultimately calls into the &lt;code&gt;libpq&lt;/code&gt; function &lt;code&gt;PQsendQueryParams&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Ruby &amp;quot;pg&amp;quot; gem
// ext/pg_connection.c
static VALUE
pgconn_async_exec_params(int argc, VALUE *argv, VALUE self) {}

// internally calls...
static VALUE
pgconn_send_query_params(int argc, VALUE *argv, VALUE self) {}

// internally calls this from the libpq c postgres internals:
// src/interfaces/libpq/fe-exec.c
int PQsendQueryParams(PGconn *conn,
  const char *command,
  int nParams,
  const Oid *paramTypes,
  const char *const *paramValues,
  const int *paramLengths,
  const int *paramFormats,
  int resultFormat) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does &lt;code&gt;PQsendQueryParams&lt;/code&gt; do? It calls an internal method named &lt;code&gt;PQsendQueryGuts&lt;/code&gt;. Notice the empty string and &lt;code&gt;use unnamed statement&lt;/code&gt; comment 🤔.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PQsendQueryGuts&lt;/span&gt;(conn,
    command,
    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#75715e&#34;&gt;/* use unnamed statement */&lt;/span&gt;
    nParams,
    paramTypes,
    paramValues,
    paramLengths,
    paramFormats,
    resultFormat);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What does &lt;em&gt;that&lt;/em&gt; function do (aside from making me laugh every time I read the name &lt;code&gt;PQsendQueryGuts&lt;/code&gt; 😆)? Internally &lt;code&gt;PQsendQueryGuts&lt;/code&gt; communicates with Postgres at the protocol level:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* construct the Parse message */
if (pqPutMsgStart(&#39;P&#39;, conn) &amp;lt; 0 ||
  pqPuts(stmtName, conn) &amp;lt; 0 ||
  pqPuts(command, conn) &amp;lt; 0) {}

/* Construct the Bind message */
if (pqPutMsgStart(&#39;B&#39;, conn) &amp;lt; 0 ||
  pqPuts(&amp;quot;&amp;quot;, conn) &amp;lt; 0 ||
  pqPuts(stmtName, conn) &amp;lt; 0) {}

/* construct the Execute message */
if (pqPutMsgStart(&#39;E&#39;, conn) &amp;lt; 0 ||
  pqPuts(&amp;quot;&amp;quot;, conn) &amp;lt; 0 ||
  pqPutInt(0, 4, conn) &amp;lt; 0 ||
  pqPutMsgEnd(conn) &amp;lt; 0) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the Parse/Bind/Execute process I mentioned earlier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The code sends a &lt;strong&gt;P&lt;/strong&gt;arse message with the query and an optional name. In our case the name is empty&lt;/li&gt;
&lt;li&gt;The code then &lt;strong&gt;B&lt;/strong&gt;inds params to that query (if the query is parameterized)&lt;/li&gt;
&lt;li&gt;It then &lt;strong&gt;E&lt;/strong&gt;xecutes using the combination of the parsed query and the bound params&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is perfectly safe to do in transaction mode, and from a SQL safety perspective should behave identically to a named prepared statement.&lt;/p&gt;
&lt;h4 id=&#34;named-protocol-level-statements&#34;&gt;Named protocol-level statements&lt;/h4&gt;
&lt;p&gt;For comparison, when ActiveRecord has prepared statements turned on, things &lt;em&gt;look&lt;/em&gt; a bit different, but by the end we’re in the same place:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def exec_cache(sql, name, binds...)
  #...pseudo coded a bit but importantly
  #   it calls `prepare`
  if !cached
    stmt_key = conn.prepare(sql)
  # then it calls exec_prepared
  conn.exec_prepared(stmt_key, type_casted_binds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It first has to call &lt;code&gt;prepare&lt;/code&gt; with whatever sql we’re going to run. The caller is in charge of keeping track of whether the sql has been prepared before, otherwise Postgres will keep overwriting our previous sql and it might as well just execute an unnamed statement. Then it calls &lt;code&gt;exec_prepared&lt;/code&gt; with only the &lt;code&gt;stmt_key&lt;/code&gt;, which should match the name of a previously prepared query.&lt;/p&gt;
&lt;p&gt;If we skip ahead to what gets called in &lt;code&gt;libpq&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// conn.prepare(sql)
int
PQsendPrepare(PGconn *conn,
    const char *stmtName, 
    const char *query,
    int nParams, 
    const Oid *paramTypes) {
  //...
  if (pqPutMsgStart(&#39;P&#39;, conn) &amp;lt; 0 ||
      pqPuts(stmtName, conn) &amp;lt; 0 ||
      pqPuts(query, conn) &amp;lt; 0) {}
  //...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see something similar to our earlier Parse/Bind/Execute, but now we’re &lt;em&gt;only&lt;/em&gt; calling the &lt;strong&gt;P&lt;/strong&gt;arse portion and this time we have a &lt;code&gt;stmtName&lt;/code&gt;. We then trigger the prepared statement calling &lt;code&gt;exec_prepared&lt;/code&gt;, which ultimately calls &lt;code&gt;PQsendQueryPrepared&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// conn.exec_prepared(stmt_key, type_casted_binds)
int
PQsendQueryPrepared(PGconn *conn,
    const char *stmtName,
    int nParams,
    const char *const *paramValues,
    const int *paramLengths,
    const int *paramFormats,
    int resultFormat) {
  //...
  return PQsendQueryGuts(conn,
      NULL,     // no sql
      stmtName, // named
      nParams,
      NULL,
      paramValues,
      paramLengths,
      paramFormats,
      resultFormat);
  //...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anything look familiar? That’s the same &lt;code&gt;PQsendQueryGuts&lt;/code&gt; function we called for the unnamed statement! This time it doesn’t hand a &lt;code&gt;command&lt;/code&gt; in because we already parsed our SQL in the earlier &lt;code&gt;prepare&lt;/code&gt; call. We also have a &lt;code&gt;stmtName&lt;/code&gt; defined, instead of handing in an empty string. This version goes on to skip the &lt;strong&gt;P&lt;/strong&gt;arse, call the &lt;strong&gt;B&lt;/strong&gt;ind with the &lt;code&gt;stmtName&lt;/code&gt;, then call &lt;strong&gt;E&lt;/strong&gt;xecute - same flow as our unnamed version.&lt;/p&gt;
&lt;p&gt;For SQL injection safety, both named and unnamed versions are equivalent: they separate query structure (Parse) from data values (Bind). Adding query bindings when not in a prepared statement simply makes an unnamed statement.&lt;/p&gt;
&lt;p&gt;Nothing about these calls is specific to the &lt;code&gt;libpq&lt;/code&gt; library, it’s just a rock solid implementation of them&lt;sup id=&#34;fnref:13&#34;&gt;&lt;a href=&#34;#fn:13&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;13&lt;/a&gt;&lt;/sup&gt; - any language could make the same protocol calls. If a library is utilizing this protocol, they are doing the same things when binding to an unnamed prepared statement as they are when binding to a named prepared statement&lt;sup id=&#34;fnref:14&#34;&gt;&lt;a href=&#34;#fn:14&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;14&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;As long as your code uses parameterized queries, “turning off” prepared statements for PgBouncer is safe, even if it seems a bit unnerving. There is a &lt;a href=&#34;https://github.com/pgbouncer/pgbouncer/pull/757&#34;&gt;PR to allow PgBouncer to track prepared statements&lt;/a&gt;, so maybe this won’t cause people like me as much heartburn in the future 🥲.&lt;/p&gt;
&lt;h3 id=&#34;throughput&#34;&gt;Pool throughput / Long running queries 🏃‍♂️&lt;/h3&gt;
&lt;p&gt;We’ve got two types of connections to Postgres: active and idle. Idle connections are the backbone of poolers - having idle connections means we’ve got capacity to swap around transactions for connected clients. What about active connections?&lt;/p&gt;
&lt;p&gt;An active connection means that connection is actively tied up by the database. For that timespan, the connection cannot be swapped out to do anything else until its operation completes. We know that active connections get expensive quickly, and we also know that most managed services range somewhere from 50 to 500 allowed total, non-pooled connections.&lt;/p&gt;
&lt;p&gt;Using a max PgBouncer connection pool of 10k and Render’s managed Postgres service with a max of 397 total connections means we’d have:&lt;/p&gt;
&lt;p&gt;10000 / 397 = ~25 connections per active connection&lt;/p&gt;
&lt;p&gt;Using Supabase’s 50 connections the spread is even higher:&lt;/p&gt;
&lt;p&gt;10000 / 50 = ~&lt;em&gt;200&lt;/em&gt; connections per active connection&lt;/p&gt;
&lt;p&gt;That means that for every long running operation, you are potentially down 200 connections worth of pooling.&lt;/p&gt;
&lt;p&gt;These numbers are very back of the napkin and of course do not represent the true scaling capability and connection handling of a real pooler. But the point is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Active connections are very valuable to a pooler&lt;/li&gt;
&lt;li&gt;Long running queries disproportionally impact concurrency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As an example, you’re using Render Postgres fronted by PgBouncer and you’ve got 10k available connections backed by the max of 397 Postgres connections. Let’s say a new feature is introduced for populating some graph data on your app’s landing page. It’s powered by a new query that looks great, has indexes, and seems well optimized. It’s even run against some load testing and representatively sized data as a QA check. It gets deployed to production and &lt;em&gt;OOF, it’s taking 15 seconds per query&lt;/em&gt; 🐌. Users are logging in or navigating to the landing page all the time so within moments you’ve had thousands of hits to this query. Obviously this is going to get quickly rolled back, but what does it mean for your pool in the meantime?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://media1.giphy.com/media/137TKgM3d2XQjK/giphy.gif?cid=6c09b952ebbb45e59739e8c9dd3ca08d23031a7fe573cd54&amp;amp;rid=giphy.gif&amp;amp;ct=g&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It means you’re maxed out. Your pooler being there means at least you’re less likely to start erroring out right away, but transaction mode can’t fix a stuck query. For each of those 15 second chunks of time your concurrency basically went from 10k back down to 397.&lt;/p&gt;
&lt;p&gt;This is not the general behavior you’ll see when using PgBouncer unless you’ve really got some intermittent trouble with runaway queries. But it does emphasize an important point to remember: these are not real Postgres connections. Your upper bound on long running, active queries is always constrained by your actual pool of real Postgres connections.&lt;/p&gt;
&lt;h4 id=&#34;guarding-against-slow-queries&#34;&gt;Guarding against slow queries&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.crunchydata.com/blog/logging-tips-for-postgres-featuring-your-slow-queries&#34;&gt;Log your slow queries&lt;/a&gt; using &lt;code&gt;log_min_duration_statement&lt;/code&gt;. This option lets you set a threshold and if queries take long than that threshold Postgres will log the offending query. This won’t help the sudden mass slow query situation mentioned above, but it helps to keep an eye on overall app query health&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&#34;https://www.postgresql.org/docs/current/libpq-single-row-mode.html&#34;&gt;streaming queries&lt;/a&gt; sparingly. In most client libraries you can set your query to run in “single row mode”. This means you retrieve your rows one at a time instead of getting one big result set at once. This is helpful for efficiency with very large result sets but is slower than a full result set query, and probably means you are running queries large enough to be slower in the first place&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&#34;#statement-timeouts&#34;&gt;statement timeouts&lt;/a&gt;. This is tricky, especially when pooling, but see that section for ideas on how to approach it&lt;/li&gt;
&lt;li&gt;Spread out reads across read replicas&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;session-level-locks&#34;&gt;Session Level Advisory Locks 🔐&lt;/h3&gt;
&lt;p&gt;Session level advisory locks work fine in PgBouncer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.gifer.com/7DWJ.gif&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Sorry 🙈.&lt;/p&gt;
&lt;p&gt;If you’ve read the previous sections you’ve already picked up on the pattern: “session” anything means it probably doesn’t work in &lt;a href=&#34;#transaction-mode&#34;&gt;transaction mode&lt;/a&gt;. But what does that matter to you?&lt;/p&gt;
&lt;p&gt;Advisory locks are a great option for creating simple, cross process, cross server application mutexes based on a provided integer key. Unlike traditional locks you use/encounter elsewhere in Postgres which are tied to tables or rows, advisory locks can be created independent of tables to control application level concerns. There are plenty of other tools you could use for this job outside of Postgres, but since Postgres is already part of your tech stack it’s a convenient and simple option.&lt;/p&gt;
&lt;p&gt;Across languages a common use case for session level advisory locks is to hold a lock while database migrations (ie, DDL) are being run. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 1234 is arbitrary, it can be any integer
SELECT pg_advisory_lock(1234);
SET lock_timeout TO &#39;1s&#39;;
ALTER TABLE my_table...;
INSERT INTO migrations VALUES (1234567);
-- If we don&#39;t explicitly unlock here, the lock will be held until this 
--    connection is closed
SELECT pg_advisory_unlock(1234);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If another connection went to acquire the same lock, it would be blocked:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- This will block indefinitely until the other connection is closed, 
--    or calls pg_advisory_unlock(1234)
SELECT pg_advisory_lock(1234);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is largely an attempt to improve consistency of migration tracking, and help coordinate multi process deploys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuous deployment with the potential to trigger multiple deployments in succession&lt;/li&gt;
&lt;li&gt;Propagating code changes to multiple servers with deploy scripts automatically triggering migrations in each context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By waiting to acquire a lock at the Postgres level, each process waits for the first lock owner to finish before continuing, coordinating each process based on a shared lock key.&lt;/p&gt;
&lt;h3 id=&#34;once-more-with-strikefeelingstrike-pgbouncer&#34;&gt;Once more, with &lt;strike&gt;feeling&lt;/strike&gt; PgBouncer&lt;/h3&gt;
&lt;p&gt;Now for the obligatory example of trying the same thing when connected to PgBouncer 🫠:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Grab the lock on connection 1
SELECT pg_advisory_lock(1234);
-- Connection 1 goes back into pool
-- ...
-- Try to unlock on connection 2, which does not own the 1234 lock
SELECT pg_advisory_unlock(1234);
-- WARNING: you don&#39;t own a lock of type ExclusiveLock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We try to unlock, but because we’re on a different connection we can’t. The lock stays locked for as long as connection 1 stays alive, which means now no one else can acquire that lock unless that connection naturally closes at some point or is explicitly &lt;code&gt;pg_cancel_backend&lt;/code&gt;ed 😓.&lt;/p&gt;
&lt;h3 id=&#34;more-session-advisory-lock-use-cases&#34;&gt;More session advisory lock use cases&lt;/h3&gt;
&lt;p&gt;Outside of migrations, advisory locks can serve other use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Application mutexes on sensitive operations like &lt;a href=&#34;https://rclayton.silvrback.com/distributed-locking-with-postgres-advisory-locks&#34;&gt;ledger updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jeremydmiller.com/2020/05/05/using-postgresql-advisory-locks-for-leader-election/&#34;&gt;Leader election&lt;/a&gt; for maintaining a single but constant daemon operation across servers&lt;/li&gt;
&lt;li&gt;Exactly once run job controls for Postgres based job systems like &lt;a href=&#34;https://github.com/bensheldon/good_job&#34;&gt;GoodJob&lt;/a&gt; and &lt;a href=&#34;https://github.com/que-rb/que&#34;&gt;Que&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If these things sound interesting or useful, they are! But only if you connect directly to Postgres.&lt;/p&gt;
&lt;h4 id=&#34;transaction-level-locks&#34;&gt;Transaction level locks&lt;/h4&gt;
&lt;p&gt;Advisory locks do have a transaction based companion:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Process 1
BEGIN;
SELECT pg_advisory_xact_lock(1234);

-- Process 2 
-- Blocks while process 1 is in the transaction
SELECT pg_advisory_lock(1234);

-- Back in Process 1
SET LOCAL lock_timeout TO &#39;1s&#39;;
ALTER TABLE my_table...;
INSERT INTO migrations VALUES (1234567);
COMMIT; -- automatically unlocks on commit or rollback
-- Process 2 now can acquire the lock

-- If you need to manually unlock while still in the transaction 
-- SELECT pg_advisory_xact_unlock(1234);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could use it as a replacement for certain scenarios, like the above migration operating transactionally. For custom purposes, it’s a good alternative!&lt;/p&gt;
&lt;p&gt;Unfortunately most migration tooling, things like leader election, and request or job lifetime locks, all use or require a longer lived lock than a single transaction could reasonably provide.&lt;/p&gt;
&lt;h4 id=&#34;turn-off-advisory-migration-locks&#34;&gt;Turn off advisory migration locks&lt;/h4&gt;
&lt;p&gt;If you need to run migrations against PgBouncer, in Rails you can turn them off with an &lt;code&gt;advisory_locks&lt;/code&gt; flag in &lt;code&gt;database.yml&lt;/code&gt;. Other migration tools likely have something similar. Do it at your &lt;em&gt;own&lt;/em&gt; peril 🤷🏻‍♂️&lt;/p&gt;
&lt;h4 id=&#34;maintaining-a-separate-direct-connection-to-postgres&#34;&gt;Maintaining a separate direct connection to Postgres&lt;/h4&gt;
&lt;p&gt;If the lock is critical, but the operations past the lock fan out and acquire multiple connections, you could potentially have two pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A direct connection to Postgres where you acquire a session level advisory lock&lt;/li&gt;
&lt;li&gt;Your normal &lt;a href=&#34;#framework-pool&#34;&gt;code level connection pooling&lt;/a&gt; using your PgBouncer connections so it can capitalize on the scaling opportunities provided there&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s an obvious downside - you’re consuming an extra direct connection and potentially impacting &lt;a href=&#34;#throughput&#34;&gt;throughput&lt;/a&gt; - but it’s an alternative available if needed.&lt;/p&gt;
&lt;h3 id=&#34;listen-notify&#34;&gt;Listen / Notify 📣&lt;/h3&gt;
&lt;p&gt;Postgres comes out of the box with a handy pub/sub feature called &lt;a href=&#34;https://www.postgresql.org/docs/current/sql-listen.html&#34;&gt;LISTEN&lt;/a&gt;/&lt;a href=&#34;https://www.postgresql.org/docs/current/sql-notify.html&#34;&gt;NOTIFY&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You simply call:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LISTEN channel_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that connection will receive &lt;code&gt;NOTIFY&lt;/code&gt; events:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NOTIFY channel_name, &#39;hi there!&#39;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Like session level advisory locks, there are more robust pub/sub solutions out there. But the Postgres implementation works well, and you already have it available in your stack.&lt;/p&gt;
&lt;p&gt;Looking at the example, you’ll notice that the &lt;code&gt;LISTEN&lt;/code&gt; call is just a single statement, and it activates the listener for the current session. What have we said so many times already? Sessions bad. Transactions good&amp;hellip; kind of.&lt;/p&gt;
&lt;h4 id=&#34;kind-of&#34;&gt;kind of?&lt;/h4&gt;
&lt;p&gt;Similar to prepared statements, the docs are misleading when it comes to &lt;code&gt;LISTEN&lt;/code&gt;/&lt;code&gt;NOTIFY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;PgBouncer officially lists &lt;code&gt;LISTEN&lt;/code&gt;/&lt;code&gt;NOTIFY&lt;/code&gt; as an unsupported feature in transaction mode, which is not precisely true. &lt;code&gt;LISTEN&lt;/code&gt; does not work in transaction mode, but &lt;code&gt;NOTIFY&lt;/code&gt; does.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NOTIFY&lt;/code&gt; is a single statement, and doesn’t rely on any session semantics. It’s also transactional&lt;sup id=&#34;fnref:15&#34;&gt;&lt;a href=&#34;#fn:15&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;15&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BEGIN;
NOTIFY channel_name, &#39;hi!&#39;;
ROLLBACK; -- no notification is sent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both &lt;code&gt;NOTIFY&lt;/code&gt; formats (inside and outside a transaction) work fine with transaction mode pooling. If you want to use pub/sub, you just need to make sure your &lt;code&gt;LISTEN&lt;/code&gt;er is connected directly to Postgres. Since &lt;a href=&#34;#transparency&#34;&gt;it can be hard to tell if you’re connected to Postgres or PgBouncer&lt;/a&gt; this is somewhat tricky, unfortunately.&lt;/p&gt;
&lt;p&gt;I’ve built implementations &lt;code&gt;LISTEN&lt;/code&gt;ing on a non-PgBouncer connection and &lt;code&gt;NOTIFY&lt;/code&gt;ing on PgBouncer that work fine. There’s not much writing on this, but I have found this approach to work well.&lt;/p&gt;
&lt;h3 id=&#34;single-threaded&#34;&gt;The single thread 🪡&lt;/h3&gt;
&lt;p&gt;In contrast to the multi process monster that is Postgres, PgBouncer runs on a paltry single process with a single thread.&lt;/p&gt;
&lt;p&gt;This means that no matter how capable a server is, PgBouncer is only going to utilize a max of one CPU core so once &lt;a href=&#34;https://news.ycombinator.com/item?id=17187436&#34;&gt;you’ve maxed out on that core&lt;/a&gt; you can’t scale that single instance anymore.&lt;/p&gt;
&lt;p&gt;A popular option is to &lt;a href=&#34;https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers&#34;&gt;load balance PgBouncer instances&lt;/a&gt;. Otherwise, almost every alternative to PgBouncer (like Odyssey, PgCat and Supavisor) utilize multiple cores.&lt;/p&gt;
&lt;p&gt;If you’re using a managed Postgres service (like Crunchy Data, Supabase, Neon or Heroku), your default option is going with PgBouncer as a connection pooler - so it will be up to those services to offer a load balanced option.&lt;/p&gt;
&lt;h3 id=&#34;pg_dump&#34;&gt;pg_dump 🚮&lt;/h3&gt;
&lt;p&gt;If you’re running &lt;code&gt;pg_dump&lt;/code&gt; against PgBouncer, it’s probably by mistake.&lt;/p&gt;
&lt;p&gt;As far as I can tell, &lt;code&gt;pg_dump&lt;/code&gt; is broken when run against PgBouncer. See &lt;a href=&#34;https://github.com/pgbouncer/pgbouncer/issues/452&#34;&gt;https://github.com/pgbouncer/pgbouncer/issues/452&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The answer here is to make sure you’re using a direct connection to Postgres for utility operations like &lt;code&gt;pg_dump&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;unavailable&#34;&gt;Other unavailable features 🫥&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://media0.giphy.com/media/VCZgfe90H1tMTAW6n4/giphy.gif?cid=6c09b9526e2d6dfd051e5257c6dbce5ac862293219ad6e76&amp;amp;rid=giphy.gif&amp;amp;ct=g&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;There are some remaining features which transaction mode is incompatible with as well&lt;sup id=&#34;fnref:16&#34;&gt;&lt;a href=&#34;#fn:16&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;16&lt;/a&gt;&lt;/sup&gt;. I have less or no experience with these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WITH HOLD CURSOR&lt;/code&gt; - A &lt;code&gt;WITH HOLD&lt;/code&gt; continues to exist outside of a transaction, which seems like it could have handy use cases but I’ve never personally used it in my day to day.&lt;/li&gt;
&lt;li&gt;PRESERVE/DELETE ROWS temp tables - temporary tables are a session level feature so will not work properly, and preserve/delete rows are modifiers on how those temporary tables behave on commit, and are unsupported&lt;/li&gt;
&lt;li&gt;LOAD statement - this is for loading shared libraries into Postgres, so it makes sense this is not something you should be doing through a pooler. I haven’t actually tried, so I’m not sure if PgBouncer would stop you, but it requires super user privileges so it’s very unlikely that’s what your PgBouncer user has&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PgBouncer documents a simple “&lt;a href=&#34;https://www.pgbouncer.org/features.html&#34;&gt;SQL feature map for pooling modes&lt;/a&gt;” where you can see all the features mentioned in this post.&lt;/p&gt;
&lt;h2 id=&#34;linting&#34;&gt;Linting 🧶&lt;/h2&gt;
&lt;p&gt;Aside from having identified potential issues - what can we do to avoid them in an automated way?&lt;/p&gt;
&lt;p&gt;Surprisingly, not much exists. And by not much, I mean i’ve found nothing outside of advice.&lt;/p&gt;
&lt;p&gt;It makes me feel a bit like I’m exaggerating the importance of these issues. Maybe I’m the oddball that has actually encountered many of them in real production usage and had to address them. I’ve had statement timeouts and lock timeouts misapplied. I’ve had to deal with rearranging connections because of code using a session advisory lock and &lt;code&gt;LISTEN&lt;/code&gt;/&lt;code&gt;NOTIFY&lt;/code&gt;, or drop libraries that use them. I’ve had to remember to turn off prepared statements in my ORM to avoid named prepared statement errors.&lt;/p&gt;
&lt;p&gt;The implications can feel small, but they can be surprising and particularly around migrations can cause real serious downtime.&lt;/p&gt;
&lt;p&gt;We lint everywhere. As engineers we try to automate away as many mistakes as possible with linting and specs. As development teams grow, the importance of automation becomes critical to scaling because otherwise someone somewhere is going to do the wrong thing and it won’t get caught.&lt;/p&gt;
&lt;p&gt;Some ideas that would be great to see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PgBouncer optional process that detects bad queries and logs them&lt;/li&gt;
&lt;li&gt;RDS connection pinning behavior&lt;/li&gt;
&lt;li&gt;Static analysis tools for app queries&lt;/li&gt;
&lt;li&gt;Runtime extension to client libraries&lt;/li&gt;
&lt;li&gt;Making sure your development flow runs PgBouncer locally to try and encounter this behavior before running on production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the rails world there are &lt;a href=&#34;https://github.com/braintree/pg_ha_migrations&#34;&gt;several&lt;/a&gt; &lt;a href=&#34;https://github.com/doctolib/safe-pg-migrations&#34;&gt;active&lt;/a&gt; &lt;a href=&#34;https://github.com/ankane/strong_migrations&#34;&gt;gems&lt;/a&gt; devoted to keeping a codebase safe from issues that would cause downtime while migrating tables (ie, zero downtime). But across ecosystems I could not find anything related to protecting against PgBouncer issues.&lt;/p&gt;
&lt;p&gt;As a step in this direction, I’ve published a (currently experimental) gem for use in Rails/ActiveRecord apps called &lt;a href=&#34;https://rubygems.org/gems/pg_pool_safe_query&#34;&gt;pg_pool_safe_query&lt;/a&gt;. It will log warnings if SQL is run that is incompatible with PgBouncer and raise an error if advisory locks and prepared statements are not disabled.&lt;/p&gt;
&lt;h2 id=&#34;future-improvements&#34;&gt;Can we improve connections without a pooler?&lt;/h2&gt;
&lt;p&gt;A more recent development in Postgres 14 was improvements to &lt;a href=&#34;https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/analyzing-the-limits-of-connection-scalability-in-postgres/ba-p/1757266&#34;&gt;snapshot scalability&lt;/a&gt;, which seem to have resulted in big improvements in efficiently &lt;a href=&#34;https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462#first-performance-improvements&#34;&gt;maintaining more idle connections&lt;/a&gt; in Postgres.&lt;/p&gt;
&lt;p&gt;It’s exciting to see effort being applied to increasing connection efficiency in Postgres itself. The author of that snapshot scalability improvement lines up with my own frustrations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ideally Postgres would better handle traffic spikes without requiring a pooler&lt;/li&gt;
&lt;li&gt;Poolers cut out useful database features&lt;/li&gt;
&lt;li&gt;Postgres itself would ideally move towards architecture changes across several key areas, eventually culminating in a larger move towards a lighter weight process/thread/async model which better aligns with the C10k problem out of the box&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of the work in the industry &lt;em&gt;seems&lt;/em&gt; to concentrate on building better poolers, rather than improving the internals of Postgres connection handling itself&lt;sup id=&#34;fnref:17&#34;&gt;&lt;a href=&#34;#fn:17&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;17&lt;/a&gt;&lt;/sup&gt;. Outside of PgBouncer you’ve got RDS Proxy, Odyssey, PgCat, Supavisor, PgPool II and I’m sure others. All have their own benefits but suffer from the same transactional scaling limitations.&lt;/p&gt;
&lt;p&gt;In fairness to the &lt;em&gt;incredible&lt;/em&gt; work that goes into Postgres - every performance improvement they make in every new version is also a connection scalability improvement. If the queries, indexes, plans, and processes are making big performance gains with each version then less connections can do more.&lt;/p&gt;
&lt;h2 id=&#34;alternatives&#34;&gt;PgBouncer alternatives&lt;/h2&gt;
&lt;p&gt;There are alternatives to PgBouncer, but the same transaction limitations apply to all of them: each has a transaction mode (or operate exclusively in transaction mode) that offers the best scaling. Once in transaction mode you can’t support most session level features anymore and you’re working off of the fact that database connections spend more time being idle than active.&lt;/p&gt;
&lt;p&gt;They all have their own unique benefits in comparison, but have the same fundamental transaction limitations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/supabase/supavisor&#34;&gt;Supavisor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/postgresml/pgcat&#34;&gt;PgCat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/yandex/odyssey&#34;&gt;Odyssey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pgpool.net/mediawiki/index.php/Main_Page&#34;&gt;Pg Pool II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/rds/proxy/&#34;&gt;RDS Proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;am-i-finally-done-with-this-post&#34;&gt;Am I finally done with this post?&lt;/h2&gt;
&lt;p&gt;I think I’ve said enough.&lt;/p&gt;
&lt;p&gt;Postgres is great. PgBouncer is important. Know what can go wrong and account for it.&lt;/p&gt;
&lt;p&gt;🐘 ✌🏼 🐘&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This &lt;a href=&#34;https://brandur.org/postgres-connections&#34;&gt;article from Brandur&lt;/a&gt; details some additional nuances of handling connections and pools, but these three are the higher level version of it&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Technically it doesn’t have to be a single instance, it could be a round Robin of multiple PgBouncers, but from a client perspective you connect to a single one&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers&#34;&gt;https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://elements.heroku.com/addons/heroku-postgresql&#34;&gt;It’s even lower&lt;/a&gt; on their lower powered options. It goes from 20, to 120, to 400, then 500 once you’re around their $400/mo plans.&lt;/p&gt;
&lt;p&gt;Supabase has no standard plans with &lt;a href=&#34;https://supabase.com/blog/supabase-pgbouncer&#34;&gt;more than 50 connections&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://render.com/docs/databases#connecting-to-your-database&#34;&gt;Render.com’s managed Postgres offering&lt;/a&gt; is based on memory available on each plan: 6 gigs or less is 97 connections, less than 10 gigs is 197 connections and over 10 gigs is 397 connections.&lt;/p&gt;
&lt;p&gt;This isn’t totally unreasonable - managing more connections requires more cores and more memory in a process based model especially. But at their highest tiers these services don’t exceed 500 available connections.&lt;/p&gt;
&lt;p&gt;More generalized services like Azure and Amazon RDS will let you go as high as you like, but that’ll go badly.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Which is very exciting to see concentrated work on improving this aspect in Postgres internals! 🤘🏼&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This more recent crunchy data article on making sure your Postgres app is production ready implies the old standard of 500 connections is no longer accurate so I’d be curious to know more specifics since most resources still emphasize these numbers &lt;a href=&#34;https://www.crunchydata.com/blog/is-your-postgres-ready-for-production&#34;&gt;https://www.crunchydata.com/blog/is-your-postgres-ready-for-production&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;AFAIK this is largely true of any database and MySQL also has connection pooling solutions, but it does &lt;em&gt;seem&lt;/em&gt; to be particularly necessary with postgres&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;There are a couple caveats to this statement. Just having a dedicated low latency pool is an improvement so may slightly help concurrency. PgBouncer can also proxy multiple databases so you could increase read concurrency at least this way.&lt;/p&gt;
&lt;p&gt;The queueing behavior of poolers can also be a benefit since you can wait for a connection to be available for longer, vs Postgres instantly rejecting the attempt: &lt;a href=&#34;https://www.percona.com/blog/connection-queuing-in-pgbouncer-is-it-a-magical-remedy/&#34;&gt;https://www.percona.com/blog/connection-queuing-in-pgbouncer-is-it-a-magical-remedy/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:8&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;They do mention this in &lt;a href=&#34;https://www.pgbouncer.org/features.html&#34;&gt;their docs&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&amp;gt; “Note that “transaction” pooling breaks client expectations of the server by design and can be used only if the application cooperates by not using non-working features.”&amp;#160;&lt;a href=&#34;#fnref:8&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:9&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/pgbouncer/pgbouncer/issues/653&#34;&gt;https://github.com/pgbouncer/pgbouncer/issues/653&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/pgbouncer/pgbouncer/issues/249&#34;&gt;https://github.com/pgbouncer/pgbouncer/issues/249&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It also just seems to be something they’re not interested in doing&amp;#160;&lt;a href=&#34;#fnref:9&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:10&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The idea is you fail and continue to retry. See this article for some framework agnostic approaches to retries: &lt;a href=&#34;https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries&#34;&gt;https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:10&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:11&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I might have five nickels, but still, it happens. Also again I am grateful for anyone taking the time to write up content and share their expertise.&amp;#160;&lt;a href=&#34;#fnref:11&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:12&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;That’s a topic for another time&amp;hellip;&amp;#160;&lt;a href=&#34;#fnref:12&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:13&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In addition to the Ruby pg gem It&amp;rsquo;s used by the python &lt;a href=&#34;https://github.com/psycopg/psycopg&#34;&gt;psycopg&lt;/a&gt; lib, and node &lt;a href=&#34;https://github.com/brianc/node-libpq&#34;&gt;node-libpq&lt;/a&gt; package (and I’m sure many others). So it seems like most client libraries handle things safely enough at the protocol level to turn off prepared statements&lt;/p&gt;
&lt;p&gt;If you are using Go with the pure Go lib/pq driver &lt;a href=&#34;https://github.com/lib/pq/issues/889&#34;&gt;see this issue&lt;/a&gt; for how to properly handle unnamed statements. The &lt;a href=&#34;https://github.com/launchbadge/sqlx/issues/368&#34;&gt;rust sqlx library&lt;/a&gt; seems to have a similar issue. Seems that if a library does not use libpq they end up in a bit of pain when trying to work with PgBouncer&amp;#160;&lt;a href=&#34;#fnref:13&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:14&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;Named prepared statements can boost performance for repetitious queries because they bypass the Parse call on subsequent runs. That’s their primary benefit in comparison to unnamed statements&amp;#160;&lt;a href=&#34;#fnref:14&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:15&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;code&gt;LISTEN&lt;/code&gt; can be &lt;a href=&#34;https://www.postgresql.org/docs/current/sql-listen.html&#34;&gt;called in a transaction as well&lt;/a&gt;, but all that means is the session level listen won’t be triggered until the transaction commits, and won’t start listening at all if a rollback is triggered&amp;#160;&lt;a href=&#34;#fnref:15&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:16&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I do find myself asking “what is the point of nice features if you can’t use them at scale because of transaction mode pooling”? Not being able to use certain features at scale should never preclude them from being built - but it’s a disappointing reality&amp;#160;&lt;a href=&#34;#fnref:16&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:17&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I’m a &lt;em&gt;little&lt;/em&gt; afraid to have made this statement and the potential for someone to come back at me angry about this being an oversimplification 😅&amp;#160;&lt;a href=&#34;#fnref:17&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2023/e0723a5982.png)

&gt; Updated **2024-09-17** to reflect updated PgBouncer support for protocol-level prepared statements 🐘

To start, I want to say that I’m appreciative that PgBouncer exists and the work its open source maintainers put into it. I also love working with PostgreSQL, and I’m thankful for the incredible amount of work and improvements that go into it as well.

I also think community and industry enthusiasm around Postgres is at an all time high. There are more managed hosting options than ever ([Crunchy Data](https://www.crunchydata.com), [Render](https://render.com/docs/databases), [Fly.io](https://fly.io/docs/postgres/), and on and on), deep extensions like [PostgresML](https://postgresml.org), [Citus](https://www.citusdata.com) and [Timescale](https://www.timescale.com), serverless options like [Neon](https://neon.tech), and real-time services like [Supabase](https://supabase.com) with Postgres at their center. Postgres is a robust, advanced and _fast_ RDBMS capable of handling the needs of most every application.

I just find the current state of recommendations and guidance around scaling Postgres to be confounding. And it feels surprising for new Postgres users to discover that one of the most common scaling options relies on a solution like PgBouncer.

Over the years I’ve read dozens of articles around scaling and maintaining Postgres databases, and they always understate the impact of PgBouncer on your application. They casually mention unusable features without any exploration, or the numerous ways you can silently break expected query behavior. The advice is just to turn it on. **I want it to be clear that as your application scales, PgBouncer is often necessary but isn’t free**.

The following sections provide an overview of what connection pooling is in general, how connection pooling modes work in PgBouncer and similar tools, and then I dig into every Postgres feature that does not work in PgBouncer transaction mode and what the implications are. This is the PgBouncer article I wish existed the first time I used it - let’s get going 🐘!

### Contents
- [What is connection pooling?](#connection-pooling)
- [Why do I need a separate tool from Postgres?](#separate-tool)
	- [Framework pooling](#framework-pool)
	- [Client proxy pooling](#client-pool)
	- [Server proxy pooling](#server-pool)
- [Can I just turn on PgBouncer and get scaling for free?](#turn-it-on)
	- [Session mode](#session-mode)
	- [Statement mode](#statement-mode)
	- [Transaction mode](#transaction-mode)
- [Perils](#perils)
	- [Detecting invalid statements 😑](#invalid-statements)
	- [Lock timeouts (SET/RESET) 🔓](#lock-timeouts)
	- [Statement timeouts (SET/RESET) ⏳](#statement-timeouts)
	- [Transparency 👻](#transparency)
	- [Prepared Statements (PREPARE/DEALLOCATE, Protocol-level prepared plans) ✔️](#prepared-statements)
	- [Pool throughout / Long running queries 🏃‍♂️](#throughput)
	- [Session Level Advisory Locks 🔐 ](#session-level-locks)
	- [Listen/Notify 📣](#listen-notify)
	- [The single thread 🪡 ](#single-threaded)
	- [pg\_dump 🚮](#pg_dump)
	- [Other unavailable features 🫥](#unavailable)
- [Linting 🧶](#linting)
- [Can we improve connections without a pooler?](#future-improvements)
- [PgBouncer alternatives](#alternatives)

&lt;h2 id=&#34;connection-pooling&#34;&gt;What is connection pooling?&lt;/h2&gt;

PgBouncer is a lightweight connection pooler for PostgreSQL. What does that mean exactly? What is connection pooling and why is it needed?

Opening a connection is expensive: a new Postgres client connection involves TCP setup, process creation and backend initialization – all of which are costly in terms of time and system resources. A connection pool keeps a set of connections available for reuse so we can avoid that overhead past initial connection.

There are three main levels of connection pooling[^1]:

&lt;span id=&#34;framework-pool&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/0c4ffd30c0.jpg)

**Framework connection pooling**. This is a common feature of many frameworks/libraries. Within a given process, you maintain a pool of active connections that are shared between code, generally running across threads. Whenever you handle some processing in a server request, a background process, a job, etc, you open a connection and keep that connection open. When that piece of work finishes and a new piece of work starts, you can reuse the connection without the expense of opening a new connection to the database every single time. These connections are usually local to a particular operating system process, so you gain no benefit outside of that process (and if you’re scaling Postgres, you probably have lots of processes)

&lt;span id=&#34;client-pool&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/d281d0294d.jpg)

One level higher, you can have **client level connection pooling** outside of your code. PgBouncer can handle this, and instead of independent unsharable process-isolated connections you proxy all of your connections through PgBouncer. But it runs on your server, so you still cannot share connections between servers (and again, when needing to do it you probably have lots of servers).

&lt;span id=&#34;server-pool&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/cd987b4e04.jpg)

**Server level connection pooling**. Here we host PgBouncer independent of our servers and connect to a single central PgBouncer instance[^2]. This is the most robust form of connection pooling because independent of anything else in your code or server, you are guaranteed that any client connection is coming from the pool.

&lt;h2 id=&#34;separate-tool&#34;&gt;Why do I need a separate tool from Postgres?&lt;/h2&gt;

That’s all great but... why do we need it?

There are two primary layers to this:

1. Maintaining connections is beneficial as a base feature. Less memory and io churn, less latency before running queries. Less pressure on the database constantly opening and closing connections.
2. Postgres connections get expensive very quickly. _Surprisingly_ quickly.

Here are some general community guidelines around allowable Postgres connection counts based on a mixture of community experience and specific benchmarking:

- In terms of what some managed services even offer: [Supabase](https://supabase.com/blog/supabase-pgbouncer) offers a max of _50_ connections, [Neon](https://neon.tech) offers a max of _100_ connections, and [Render](https://render.com/docs/databases#connecting-to-your-database) offers a max of 397 connections.
- The general upper bound recommendation is a _max_ of 500 active connections. Services like [Heroku Postgres](https://elements.heroku.com/addons/heroku-postgresql) even _enforce_ a hard limit of 500 connections[^3]
- Even at 500 connections, your server is going to be strained. [This more recent (as of 2023) enterprisedb article](https://www.enterprisedb.com/postgres-tutorials/why-you-should-use-connection-pooling-when-setting-maxconnections-postgres) analyzed connection performance and found that 300-400 active connections seems optimal. This [article from Brandur](https://brandur.org/postgres-connections) is older (2018) but seems to reinforce this idea as well
- There have been [some more recent connection improvements in Postgres](https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462) (as of version 14) handling idle connections more efficiently[^4], but active connections are still expensive[^5] and idle connections have not reached the scale of a dedicated pooler
- The reality of 500 connections is it sounds extremely low but those connections can handle _a ton of throughput_. The _problem_ is, as a metric of pure concurrency, real connections have a hard upper limit. So if you try to have five thousand clients connect simultaneously, you’re going to start getting loads of connection errors[^6].

To improve the cost of connection overhead, general connection pooling is helpful and a PgBouncer instance in its default session based mode can handle it. But to improve concurrency things have to get a bit... _quirky_.

There are two modes in PgBouncer which give clients access to more connections than Postgres _actually_ has available. They rely on the idea that at any given time many of your connections are idle, so you can free up usage of idle connections to improve concurrency.

&lt;h2 id=&#34;turn-it-on&#34;&gt;Can I just turn on PgBouncer and get scaling for free?&lt;/h2&gt;

Kind of? But not really? It’s complicated.

Internally, PgBouncer will manage a pool of connections for you. The default pooling mode it starts with, session pooling, is conservative, and in most cases will not provide improved concurrency[^7].

I’m going to hand wave a bit past two of the modes and focus on the typical recommendation.

&lt;span id=&#34;session-mode&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/7391257b1c.jpg)

**Session mode** is the default and most conservative mode. This is a 1:1 - your local connection truly holds onto a full connection until you close it. This does little to help you scale connection concurrency, but it helps with latency and connection churn overhead.

&lt;span id=&#34;statement-mode&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/6e486ad1b1.jpg)

**Statement mode** is the most aggressive mode and means your connection goes back into the pool after _every statement_. You lose the ability to use transactions 😰 - that seems wild and unusable for only the most specialized of use cases.

&lt;span id=&#34;transaction-mode&#34;&gt;&lt;/span&gt;

![](https://cdn.uploads.micro.blog/98548/2023/9e0451a965.jpg)

The mode that results in a more sane balance of improved concurrency and retained critical database features is **transaction mode**. Transaction mode means your connection stays consistent as long as you’re in a transaction. Once the transaction finishes, your code _thinks_ it still has real connection but PgBouncer actually releases the connection back into the pool internally. This is _session sharing_, your session is going to be shared with other connections without being reset or closed.

Transaction mode is a powerful concept. Your code in general has lots of database downtime. Most code does not solely operate on the database - it takes CPU cycles, interacts with files, makes network calls, and calls other data stores. During that time, your connection sits idle and unused for what in computing and database terms is an eternity. By releasing that back into the pool outside of transactions you free up your idle connection for use by a client who actually needs it. This way your 500 available connections can services thousands of clients, instead of a 1:1 with the number of available connections.

	-- connection is actually pulled from the pool inside PgBouncer
	BEGIN;
	INSERT INTO...;
	UPDATE;
	COMMIT;
	-- connection goes back to the pool inside PgBouncer

The problem with transaction mode is that this tiny configuration change quietly changes not only your ability to scale, but also the way your connections _behave_. It breaks the expected command semantics between client and database server. And understanding whether you’ve gotten things right in transaction mode is _very difficult_. 

Let’s say you’ve been operating with PgBouncer in session mode (or operating without a proxy at all), and you make the switch to transaction mode. Your perspective on how you can use Postgres needs to change - so now we’re onto the _peril_.

&lt;h2 id=&#34;perils&#34;&gt;Perils&lt;/h2&gt;

Many of the following items are documented shortcomings of PgBouncer in [transaction mode](#transaction-mode). But:

1. They’re treated lightly
2. Their repercussions and downsides are not discussed
3. PgBouncer is often recommended without mentioning them
4. PgBouncer is often recommended at the same time as recommending incompatible transaction mode features like session level advisory locks and session level statement timeouts
5. The non-determinism introduced by using incompatible statements is not discussed (ie, I execute a statement in Process A and suddenly Process B errors out due to it)

Assume anytime I mention PgBouncer after this point I am referring to [transaction mode](#transaction-mode). Here we go!

&lt;h3 id=&#34;invalid-statements&#34;&gt;Detecting invalid statements 😑&lt;/h3&gt;

PgBouncer happily accepts statements that are not supported in transaction mode. The problem is pushed onto the developer, which means they _can_ and _will_ get it wrong[^8].

This is by design. The sense I get is that PgBouncer was specifically architected to not analyze any statements and so it would be a big change for them to handle this[^9]. 

Amazon has a similar tool to PgBouncer called RDS Proxy, and it has a feature called “[connection pinning](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy-managing.html#rds-proxy-pinning)”. If it detects a statement that is incompatible with transaction mode, it will automatically hold that connection for that client for the duration of their session. 

This is both highly useful and simultaneously problematic. It means query behavior is consistent with your expectations (🙌🏼) but also that you can silently kill all concurrency benefits (😑). If enough queries are run that trigger connection pinning, all of a sudden you may throttle your [throughput](#throughput). But it does give you an escape hatch for safely running statements which are not transaction compatible without having to jump through any hoops.

I’d be fine with some logging I could monitor. As far as I can tell there is nothing like this in PgBouncer, and so all the burden lands on you to get it right. As one engineer, or a few engineers, all aware of potential issues, you can probably maintain that. But what about dozens of engineers? Or hundreds? Thousands? All with varying levels of experience with databases and poolers? There’s going to be mistakes.

&lt;h3 id=&#34;lock-timeouts&#34;&gt;Lock Timeouts (SET/RESET) 🔓&lt;/h3&gt;

Unless you _like_ app downtime, you should be using a `lock_timeout` when running [DDL](https://www.postgresql.org/docs/current/ddl.html). It’s a critical aspect of [zero downtime migrations](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680).

The idea is to set it to a limit that would be acceptable for queries in your application to slowdown by - waiting to acquire a lock can cause related queries to queue up behind your DDL operation:

	-- slow select
	SELECT * FROM my_table;
	
	-- DDL starts in separate process, blocked on acquiring the lock by the 
    --    slow query
	ALTER TABLE my_table...
	
	-- Subsequent queries start queuing up...
	SELECT * FROM my_table WHERE id = 123;
	SELECT * FROM my_table WHERE id = 234;
	--- ...

In that scenario, the slow query is running the show. Until it finishes, _all the other queries to that table are stuck_. That goes on long enough and users can’t use the system. A bit longer and your app starts timing out. A bit longer you’re running out of _connections_. Now you’re staring at a total app outage, about ready to kill all of your connections in a desperate attempt to salvage things, contemplating a career change to landscaping where you can at most impact one person at a time, right? That sounds nice, doesn’t it?

I’ve of course never experienced that. I’m just _very_ creative 💀. But if _you_ have experienced that, or you’d like to _avoid_ experiencing that, use a `lock_timeout`:

	SET lock_timeout TO &#39;2s&#39;;

_Now_ if your DDL cannot acquire a lock it will throw an error after 2 seconds. That should be an ok delay in running queries, and you can retry the operation later. 

But wait! Are you connected to PgBouncer?! You may want to bring up that landscaping help-wanted listing again... 🌳

`SET` operations apply at the [session level](#session-mode). This means that on a PgBouncer connection, there is no guarantee our `lock_timeout` will still be applied when we run our DDL:

	-- Process 1
	-- PgBouncer pulls connection 1
	SET lock_timeout TO &#39;2s&#39;;
	-- connection 1 goes back to the pool
	
	-- Meanwhile, in Process 2:
	-- PgBouncer pulls connection 3
	SELECT id FROM my_table, pg_sleep(30);
	
	-- Back in Process 1:
	-- PgBouncer pulls connection 2
	-- This connection has no lock_timeout set, so it will hang 
    --    until our pg_sleep query finishes 30 seconds later, and all
    --    queries to my_table after it are stuck for those 30 seconds as well
	ALTER TABLE my_table...

It’d be easy to argue “don’t have slow queries”. And that should be the goal! But we don’t call it “happy path uptime 🌼”, we call it “_zero_ downtime”. It means even if things go wrong, you don’t go down. There can also be other operations that hold a lock on your table, so you simply can’t rely on successfully acquiring that lock[^10].

So what can we do? There are two options:

1. Bypass PgBouncer and go straight to the database
2. Use a transaction level `lock_timeout`

#### Bypassing PgBouncer
Your safest bet is to go with option (1). You should have some ability to directly connect to your database, so take advantage of it and don’t jump through hoops to run DDL safely. 

The biggest obstacle you hit with (1) is [transparency](#transparency): PgBouncer _really_ doesn’t want you to know whether you are connected to the real database or not. There’s no _easy_ answer there, but by validating a setup where you consistently run your DDL process directly against Postgres then you’re set. 

#### Use transaction level statements
There is a transaction local equivalent to the `SET` statement: `SET LOCAL`. Using our example from earlier:

	-- Process 1
	-- PgBouncer pulls connection 1
	BEGIN;
	SET LOCAL lock_timeout TO &#39;2s&#39;;
	-- connection 1 stays checked out
	
	-- Meanwhile, in Process 2:
	-- PgBouncer pulls connection 3
	SELECT id FROM my_table, pg_sleep(30);
	
	-- Back in Process 1:
	-- Connection 1 is still checked out
	ALTER TABLE my_table...
	-- lock_timeout raises an error after 2 seconds waiting, and 
    --    we avoid our downtime!

DDL in Postgres is transactional, so it’s valid to start our transaction, set our `lock_timeout` using `SET LOCAL`, then start our DDL operation. Our transaction local setting will stick with us until the transaction commits or rolls back, so we safely keep our timeout and rollback our DDL.

It’s not a _terrible_ solution (1 is still better), except for two things:

1. Concurrent indexes
2. Tooling

Another zero downtime star is the concurrent index. When you create a new index on a table you run the chance of locking it up long enough to cause downtime. Here’s the answer to that problem:

	CREATE INDEX CONCURRENTLY index_name ON my_table;

Concurrent indexes are created without an exclusive lock, so your normal operations keep going while it builds the index in the background. The _problem_ is they can’t be run in a transaction, so `SET LOCAL` is not an option.

Because they don’t require an exclusive lock, setting a `lock_timeout` is less important. But if there is contention and you just can’t get that index to acquire it’s shared lock, do you really want it to run forever?

As for (2), popular tooling usually does not handle `SET LOCAL` for you. In the Rails/ActiveRecord world there are several libraries that will automatically apply zero downtime policies for you, but they all assume you have an exclusive connection and operate at the `SET` session level.

[In PgBouncer, the road to downtime is paved with session level statements](https://en.m.wikipedia.org/wiki/The_road_to_hell_is_paved_with_good_intentions).

Just go with (1), keep your sanity, throw away the diary entries about living out your days breathing in the smell of fresh cut grass, and connect directly to Postgres to run DDL with `SET lock_timeout` calls.

&lt;h3 id=&#34;statement-timeouts&#34;&gt;Statement timeouts (SET/RESET) ⏳&lt;/h3&gt;

Determined not to repeat your experiences from `lock_timeout`, you read about this thing called `statement_timeout`. This little magic wand makes it so you control how long a statement is allowed to run 🪄.

So here it is:

	SET statement_timeout TO &#39;2s&#39;;

Those greedy queries now don’t stand a chance. You can tame your long running queries and avoid blocking your DDL! You ignore my advice to always use `lock_timeout`, say “bye losers” to long running queries, and fire off that DDL again... oh god. Why are things slowing down. Now they’re timing out. _The connections are filling up._ What is _happening?_

![](https://media.tenor.com/MYZgsN2TDJAAAAAC/this-is.gif)

Oh riiiight. You forgot. You’re using PgBouncer. `SET` is off the table. Should have set that `lock_timeout` 🔐...

If I had a nickel for every time someone mentioned `SET statement_timeout` and PgBouncer in the same article...[^11] I know no one sharing this content is doing it maliciously, but be aware that these are misleading and incompatible features.

#### With lock\_timeout, why does statement\_timeout even matter?
- Statement timeouts are helpful for long running queries so they cancel earlier. If a client disconnects, Postgres will periodically check for the connection and try to cancel the query when it goes away. But a query [with runaway cpu](https://dba.stackexchange.com/a/81424/256107) usage will just keep running even if the client dies or disconnects. That means you lose that connection until the query finishes, which can take minutes (or hours)
- The database default is 0, which means there is no limit. In some contexts this is not a problem, but particularly for web requests this is excessive

The first time I used `statement_timeout` was from a blog recommendation to limit statements for requests in web applications. In a web request, you usually have an upper limit on how long you allow them to run before they time out - this conserves resources, protects against runaway buggy code and helps with bad actors. It made sense that I’d set it to something conservative on all my web connections to deal with long running queries.

I deployed the code and for a little while things seemed to work well. Then I saw something odd. This started popping up:

	canceling statement due to statement timeout

But in my... _background jobs_? My web requests were tuned to be fast, but the constraints around my background processes were a bit... looser. Can you guess what I had recently enabled? PgBouncer in transaction mode. My session level statement timeout was being swapped out from my web request, picked up by my job, and caused my job to timeout instead - web request safety was off the rails and longer running jobs were intermittently failing.

So is there any way we can use it? There’s a couple ways I know of, but nothing great when pooling.

#### Our old friend transaction
	BEGIN;
	SET LOCAL statement_timeout &#39;5s&#39;;
	SELECT ...
	COMMIT;

Something about wrapping a SELECT in a transaction feels kind of strange, but it works. If you have targeted concerns, you can wrap particular queries in a transaction and use `SET LOCAL` to localize the `statement_timeout`. 

This is absolutely not a viable solution for a whole request lifecycle. If I wanted to attempt my web request level timeouts again, no way am I wrapping every web request in one giant transaction. Postgres doesn’t have a concept of nested transactions so any code I have that may be operating transactionally is gonna be in for some confusing surprises[^12]. And most importantly, wrapping my whole request in a transaction means I’ve completely negated the benefit of proxy pooling - now my request lifecycles are basically 1:1 with my connection sessions.

#### Apply statement timeouts per user
I’ve never tried it, but I’ve seen it recommended to set statement timeouts per user when using PgBouncer. That seems to have a couple problems I can think of:

1. It’s not dynamically configurable.
2. It dilutes the pool of available connections per context

(1) is definitely inconvenient. If you have different contexts where you’d like to apply different timeout constraints, this would be way too cumbersome to maintain.

But (2) _feels_ like a deal breaker. If I want to constrain my web requests to a conservative timeout, but give my background processes more wiggle room, my pool size of real connections is now split instead of sharing a pool of total available database connections. I also have to manage making sure each context uses the appropriate user, or things will go badly.

It’s technically an option, but seems trickier to maintain and monitor.

&lt;h3 id=&#34;transparency&#34;&gt;Transparency 👻&lt;/h3&gt;

![](https://media2.giphy.com/media/xT5LMN0UgalbScp6uI/giphy.gif?cid=6c09b952f7248c90c21812529981462733f1d648a5076839&amp;rid=giphy.gif&amp;ct=g)

&gt; I don’t understand why my session features aren’t working. I always make sure to use plenty of Postgr...PgBouncer?!

It is very difficult to tell when you are or aren’t using PgBouncer, [which is unfortunately by design](https://github.com/pgbouncer/pgbouncer/issues/249). It considers itself a transparent proxy. In [session mode](#session-mode), that’s pretty much true. But in [transaction](#transaction-mode) and [statement](#statement-mode) mode you are working with bizarro Postgres. It all works the same except when it doesn’t.

So if you want a regular connection because you need a feature not available in transaction mode, being sure you did it right is extremely difficult.

I have had a hell of a time verifying that some servers are or aren’t running with PgBouncer. Server A is using pub sub, I don’t want it. Server B needs the throughput, I want it. How can I make sure someone never makes a mistake and attaches the server to the wrong place? Basically, I can’t.

When it comes to production code I like to be paranoid. On a large enough codebase, and team, and user base, unusual things are bound to happen, sometimes regularly. I try to write code and configure environments so the right way is easy and the wrong way is hard. PgBouncer does not make that easy.

On this particular point I’d love to say I have some kind of advice to act on, but it mostly takes testing and validating your setup. If someone out there has better ideas or tips, I am all ears.

&lt;h3 id=&#34;prepared-statements&#34;&gt;Prepared Statements (PREPARE/DEALLOCATE, Protocol-level prepared plans) ✔️&lt;/h3&gt;

&gt; 📝 Update as of PgBouncer version 1.21 - protocol-level prepared statements are now supported! See [this crunchy data article](https://www.crunchydata.com/blog/prepared-statements-in-transaction-mode-for-pgbouncer) for more specifics. `PREPARE` style statements will still never be supported, and there are still some gotchas with protocol-level support you can learn about in that article.
&gt; 
&gt;  Everything mentioned here is still relevant for versions &lt; 1.21. As well, it still gives a thorough explanation of why &#34;turning off&#34; prepared statements while still utilizing `libpq` (as most libraries do) is a-ok ✌️

PgBouncer has a public relations problem when it comes to prepared statements. This is all the [PgBouncer docs say](https://www.pgbouncer.org/features.html) about them:

&lt;table&gt;
	&lt;thead&gt;
		&lt;tr&gt;
			&lt;th&gt;
				Feature
			&lt;/th&gt;
			&lt;th&gt;
				Session pooling
			&lt;/th&gt;
			&lt;th&gt;
				Transaction pooling
			&lt;/th&gt;
		&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;
				`PREPARE` / `DEALLOCATE`
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				Never
			&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;
				Protocol-level prepared plans
			&lt;/td&gt;
			&lt;td&gt;
				Yes
			&lt;/td&gt;
			&lt;td&gt;
				No*
			&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&gt; \* It is possible to add support for that into PgBouncer


Kind of feels... alarming. No prepared statements in transaction mode?! Aren’t those... important? Even further when you go to use PgBouncer with Hibernate or ActiveRecord (and I’m sure others) you’ll see the recommendation to configure them to _turn off_ prepared statements 😱. Does it surprise you a bit to hear that? Make you feel a little queasy maybe?

I had it drilled into me early in my career that prepared statements are a critical part of protecting against SQL injection. In the [OWASP SQL Injection Prevention Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) the very first recommendation is:

- **Use of Prepared Statements (with Parameterized Queries)**

So PgBouncer tells me I need to _turn them off?_

![](https://thumbs.gfycat.com/AstonishingEarlyHornet-size_restricted.gif)

The first time I used PgBouncer in an application I spent _a lot_ of time figuring out how turning off prepared statements was safe to do. It turns out that prepared statements in Postgres mean a few things, but come down to two main options:

1. Named prepared statements
2. Unnamed prepared statements

_Named_ prepared statements are reusable, and are tied to the connection session.

_Unnamed_ prepared statements are single use, and have no association to the connection session.

There are two ways to create a _named_ prepared statement and one way to create an _unnamed_ prepared statement:

1. `PREPARE`
2. Protocol-level Parse/Bind/Execute with a name specified
3. Protocol-level Parse/Bind/Execute with no name specified

PgBouncer says it doesn’t support prepared statements in either `PREPARE` or protocol-level format. What it _actually_ doesn’t support are _named_ prepared statements in any form. That’s because named prepared statements live in the session and in [transaction mode](#transaction-mode) you can switch sessions.

	-- PgBouncer pulls connection 1
	PREPARE bouncer_since (int, timestamp) AS
	SELECT * 
	FROM bouncers b
	INNER JOIN guests g ON g.bouncer_id = b.id
	WHERE b.id = $1 AND b.created &gt; $2;
	-- connection 1 goes back to the pool
	
	-- PgBouncer pulls connection 2
	EXECUTE bouncer_since(1, now() - INTERVAL &#39;2 days&#39;);
	-- 💣 ERROR: prepared statement &#34;bouncer_since&#34; does not exist 💣

But _unnamed prepared statements are totally fine_. In fact, I’d be shocked if the current client library you’re using to connect to Postgres does not already switch to them if “prepared statements” (again, so damn misleading) are “turned off”.

But wait. What the heck is an unnamed statement? `PREPARE` _requires_ a name... how can I make a prepared statement without a name?

#### Protocol-level prepared plans
![](https://media0.giphy.com/media/P5wPrhzZDdeJW/giphy.gif?cid=6c09b95256738d3ee35e24f988a790f60659836b97f75ee8&amp;rid=giphy.gif&amp;ct=g)

The alternative to the `PREPARE` statement is to directly communicate with Postgres at the protocol level.

I had to dig a bit to get a handle on this - I started from a common Ruby ORM called ActiveRecord, dug into the Ruby “pg” gem _it_ uses, then went one layer deeper into `libpq`, which is part of Postgres itself.

If we use active record as an example, [when prepared statements are “disabled”](https://guides.rubyonrails.org/configuring.html#configuring-a-postgresql-database), the postgres adapter internally calls `exec_no_cache` in `activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb`:

    def exec_no_cache(sql, name, binds...)
      #...
      conn.exec_params(sql, type_casted_binds)

That&#39;s powered by the ruby “pg” gem, which when calling `exec_params` from ruby ultimately calls into the `libpq` function `PQsendQueryParams`:

    // Ruby &#34;pg&#34; gem
    // ext/pg_connection.c
    static VALUE
    pgconn_async_exec_params(int argc, VALUE *argv, VALUE self) {}
	
    // internally calls...
    static VALUE
    pgconn_send_query_params(int argc, VALUE *argv, VALUE self) {}
	
    // internally calls this from the libpq c postgres internals:
    // src/interfaces/libpq/fe-exec.c
    int PQsendQueryParams(PGconn *conn,
      const char *command,
      int nParams,
      const Oid *paramTypes,
      const char *const *paramValues,
      const int *paramLengths,
      const int *paramFormats,
      int resultFormat) {}

What does `PQsendQueryParams` do? It calls an internal method named `PQsendQueryGuts`. Notice the empty string and `use unnamed statement` comment 🤔.

```c
return PQsendQueryGuts(conn,
    command,
    &#34;&#34;, /* use unnamed statement */
    nParams,
    paramTypes,
    paramValues,
    paramLengths,
    paramFormats,
    resultFormat);
```
What does _that_ function do (aside from making me laugh every time I read the name `PQsendQueryGuts` 😆)? Internally `PQsendQueryGuts` communicates with Postgres at the protocol level:

	/* construct the Parse message */
	if (pqPutMsgStart(&#39;P&#39;, conn) &lt; 0 ||
	  pqPuts(stmtName, conn) &lt; 0 ||
	  pqPuts(command, conn) &lt; 0) {}
	
	/* Construct the Bind message */
	if (pqPutMsgStart(&#39;B&#39;, conn) &lt; 0 ||
	  pqPuts(&#34;&#34;, conn) &lt; 0 ||
	  pqPuts(stmtName, conn) &lt; 0) {}
	
	/* construct the Execute message */
	if (pqPutMsgStart(&#39;E&#39;, conn) &lt; 0 ||
	  pqPuts(&#34;&#34;, conn) &lt; 0 ||
	  pqPutInt(0, 4, conn) &lt; 0 ||
	  pqPutMsgEnd(conn) &lt; 0) {}

This is the Parse/Bind/Execute process I mentioned earlier.

- The code sends a **P**arse message with the query and an optional name. In our case the name is empty
- The code then **B**inds params to that query (if the query is parameterized)
- It then **E**xecutes using the combination of the parsed query and the bound params

This is perfectly safe to do in transaction mode, and from a SQL safety perspective should behave identically to a named prepared statement.

#### Named protocol-level statements 
For comparison, when ActiveRecord has prepared statements turned on, things _look_ a bit different, but by the end we’re in the same place:

    def exec_cache(sql, name, binds...)
      #...pseudo coded a bit but importantly
      #   it calls `prepare`
      if !cached
        stmt_key = conn.prepare(sql)
      # then it calls exec_prepared
      conn.exec_prepared(stmt_key, type_casted_binds)

It first has to call `prepare` with whatever sql we’re going to run. The caller is in charge of keeping track of whether the sql has been prepared before, otherwise Postgres will keep overwriting our previous sql and it might as well just execute an unnamed statement. Then it calls `exec_prepared` with only the `stmt_key`, which should match the name of a previously prepared query.

If we skip ahead to what gets called in `libpq`:

    // conn.prepare(sql)
    int
    PQsendPrepare(PGconn *conn,
        const char *stmtName, 
        const char *query,
        int nParams, 
        const Oid *paramTypes) {
      //...
      if (pqPutMsgStart(&#39;P&#39;, conn) &lt; 0 ||
          pqPuts(stmtName, conn) &lt; 0 ||
          pqPuts(query, conn) &lt; 0) {}
      //...
    }

We see something similar to our earlier Parse/Bind/Execute, but now we’re _only_ calling the **P**arse portion and this time we have a `stmtName`. We then trigger the prepared statement calling `exec_prepared`, which ultimately calls `PQsendQueryPrepared`:

    // conn.exec_prepared(stmt_key, type_casted_binds)
    int
    PQsendQueryPrepared(PGconn *conn,
        const char *stmtName,
        int nParams,
        const char *const *paramValues,
        const int *paramLengths,
        const int *paramFormats,
        int resultFormat) {
      //...
      return PQsendQueryGuts(conn,
          NULL,     // no sql
          stmtName, // named
          nParams,
          NULL,
          paramValues,
          paramLengths,
          paramFormats,
          resultFormat);
      //...
    }

Anything look familiar? That’s the same `PQsendQueryGuts` function we called for the unnamed statement! This time it doesn’t hand a `command` in because we already parsed our SQL in the earlier `prepare` call. We also have a `stmtName` defined, instead of handing in an empty string. This version goes on to skip the **P**arse, call the **B**ind with the `stmtName`, then call **E**xecute - same flow as our unnamed version.

For SQL injection safety, both named and unnamed versions are equivalent: they separate query structure (Parse) from data values (Bind). Adding query bindings when not in a prepared statement simply makes an unnamed statement.

Nothing about these calls is specific to the `libpq` library, it’s just a rock solid implementation of them[^13] - any language could make the same protocol calls. If a library is utilizing this protocol, they are doing the same things when binding to an unnamed prepared statement as they are when binding to a named prepared statement[^14].

As long as your code uses parameterized queries, “turning off” prepared statements for PgBouncer is safe, even if it seems a bit unnerving. There is a [PR to allow PgBouncer to track prepared statements](https://github.com/pgbouncer/pgbouncer/pull/757), so maybe this won’t cause people like me as much heartburn in the future 🥲.

&lt;h3 id=&#34;throughput&#34;&gt;Pool throughput / Long running queries 🏃‍♂️&lt;/h3&gt;

We’ve got two types of connections to Postgres: active and idle. Idle connections are the backbone of poolers - having idle connections means we’ve got capacity to swap around transactions for connected clients. What about active connections?

An active connection means that connection is actively tied up by the database. For that timespan, the connection cannot be swapped out to do anything else until its operation completes. We know that active connections get expensive quickly, and we also know that most managed services range somewhere from 50 to 500 allowed total, non-pooled connections. 

Using a max PgBouncer connection pool of 10k and Render’s managed Postgres service with a max of 397 total connections means we’d have:

10000 / 397 = ~25 connections per active connection

Using Supabase’s 50 connections the spread is even higher:

10000 / 50 = ~_200_ connections per active connection

That means that for every long running operation, you are potentially down 200 connections worth of pooling.

These numbers are very back of the napkin and of course do not represent the true scaling capability and connection handling of a real pooler. But the point is this:

- Active connections are very valuable to a pooler
- Long running queries disproportionally impact concurrency

As an example, you’re using Render Postgres fronted by PgBouncer and you’ve got 10k available connections backed by the max of 397 Postgres connections. Let’s say a new feature is introduced for populating some graph data on your app’s landing page. It’s powered by a new query that looks great, has indexes, and seems well optimized. It’s even run against some load testing and representatively sized data as a QA check. It gets deployed to production and _OOF, it’s taking 15 seconds per query_ 🐌. Users are logging in or navigating to the landing page all the time so within moments you’ve had thousands of hits to this query. Obviously this is going to get quickly rolled back, but what does it mean for your pool in the meantime?

![](https://media1.giphy.com/media/137TKgM3d2XQjK/giphy.gif?cid=6c09b952ebbb45e59739e8c9dd3ca08d23031a7fe573cd54&amp;rid=giphy.gif&amp;ct=g)

It means you’re maxed out. Your pooler being there means at least you’re less likely to start erroring out right away, but transaction mode can’t fix a stuck query. For each of those 15 second chunks of time your concurrency basically went from 10k back down to 397.

This is not the general behavior you’ll see when using PgBouncer unless you’ve really got some intermittent trouble with runaway queries. But it does emphasize an important point to remember: these are not real Postgres connections. Your upper bound on long running, active queries is always constrained by your actual pool of real Postgres connections.

#### Guarding against slow queries
- [Log your slow queries](https://www.crunchydata.com/blog/logging-tips-for-postgres-featuring-your-slow-queries) using `log_min_duration_statement`. This option lets you set a threshold and if queries take long than that threshold Postgres will log the offending query. This won’t help the sudden mass slow query situation mentioned above, but it helps to keep an eye on overall app query health
- Use [streaming queries](https://www.postgresql.org/docs/current/libpq-single-row-mode.html) sparingly. In most client libraries you can set your query to run in “single row mode”. This means you retrieve your rows one at a time instead of getting one big result set at once. This is helpful for efficiency with very large result sets but is slower than a full result set query, and probably means you are running queries large enough to be slower in the first place
- Use [statement timeouts](#statement-timeouts). This is tricky, especially when pooling, but see that section for ideas on how to approach it
- Spread out reads across read replicas

&lt;h3 id=&#34;session-level-locks&#34;&gt;Session Level Advisory Locks 🔐&lt;/h3&gt;

Session level advisory locks work fine in PgBouncer.

![](https://i.gifer.com/7DWJ.gif)

Sorry 🙈.

If you’ve read the previous sections you’ve already picked up on the pattern: “session” anything means it probably doesn’t work in [transaction mode](#transaction-mode). But what does that matter to you?

Advisory locks are a great option for creating simple, cross process, cross server application mutexes based on a provided integer key. Unlike traditional locks you use/encounter elsewhere in Postgres which are tied to tables or rows, advisory locks can be created independent of tables to control application level concerns. There are plenty of other tools you could use for this job outside of Postgres, but since Postgres is already part of your tech stack it’s a convenient and simple option.

Across languages a common use case for session level advisory locks is to hold a lock while database migrations (ie, DDL) are being run. For example:

	-- 1234 is arbitrary, it can be any integer
	SELECT pg_advisory_lock(1234);
	SET lock_timeout TO &#39;1s&#39;;
	ALTER TABLE my_table...;
	INSERT INTO migrations VALUES (1234567);
	-- If we don&#39;t explicitly unlock here, the lock will be held until this 
    --    connection is closed
	SELECT pg_advisory_unlock(1234);

If another connection went to acquire the same lock, it would be blocked:

	-- This will block indefinitely until the other connection is closed, 
    --    or calls pg_advisory_unlock(1234)
	SELECT pg_advisory_lock(1234);

This is largely an attempt to improve consistency of migration tracking, and help coordinate multi process deploys:

- Continuous deployment with the potential to trigger multiple deployments in succession
- Propagating code changes to multiple servers with deploy scripts automatically triggering migrations in each context

By waiting to acquire a lock at the Postgres level, each process waits for the first lock owner to finish before continuing, coordinating each process based on a shared lock key.

### Once more, with &lt;strike&gt;feeling&lt;/strike&gt; PgBouncer
Now for the obligatory example of trying the same thing when connected to PgBouncer 🫠:

	-- Grab the lock on connection 1
	SELECT pg_advisory_lock(1234);
	-- Connection 1 goes back into pool
	-- ...
	-- Try to unlock on connection 2, which does not own the 1234 lock
	SELECT pg_advisory_unlock(1234);
	-- WARNING: you don&#39;t own a lock of type ExclusiveLock

We try to unlock, but because we’re on a different connection we can’t. The lock stays locked for as long as connection 1 stays alive, which means now no one else can acquire that lock unless that connection naturally closes at some point or is explicitly `pg_cancel_backend`ed 😓.

### More session advisory lock use cases
Outside of migrations, advisory locks can serve other use cases:

- Application mutexes on sensitive operations like [ledger updates](https://rclayton.silvrback.com/distributed-locking-with-postgres-advisory-locks)
- [Leader election](https://jeremydmiller.com/2020/05/05/using-postgresql-advisory-locks-for-leader-election/) for maintaining a single but constant daemon operation across servers
- Exactly once run job controls for Postgres based job systems like [GoodJob](https://github.com/bensheldon/good_job) and [Que](https://github.com/que-rb/que)

If these things sound interesting or useful, they are! But only if you connect directly to Postgres. 

#### Transaction level locks
Advisory locks do have a transaction based companion: 

	-- Process 1
	BEGIN;
	SELECT pg_advisory_xact_lock(1234);
	
	-- Process 2 
	-- Blocks while process 1 is in the transaction
	SELECT pg_advisory_lock(1234);
	
	-- Back in Process 1
	SET LOCAL lock_timeout TO &#39;1s&#39;;
	ALTER TABLE my_table...;
	INSERT INTO migrations VALUES (1234567);
	COMMIT; -- automatically unlocks on commit or rollback
	-- Process 2 now can acquire the lock
	
	-- If you need to manually unlock while still in the transaction 
	-- SELECT pg_advisory_xact_unlock(1234);

You could use it as a replacement for certain scenarios, like the above migration operating transactionally. For custom purposes, it’s a good alternative! 

Unfortunately most migration tooling, things like leader election, and request or job lifetime locks, all use or require a longer lived lock than a single transaction could reasonably provide.

#### Turn off advisory migration locks
If you need to run migrations against PgBouncer, in Rails you can turn them off with an `advisory_locks` flag in `database.yml`. Other migration tools likely have something similar. Do it at your _own_ peril 🤷🏻‍♂️ 

#### Maintaining a separate direct connection to Postgres
If the lock is critical, but the operations past the lock fan out and acquire multiple connections, you could potentially have two pieces:

- A direct connection to Postgres where you acquire a session level advisory lock
- Your normal [code level connection pooling](#framework-pool) using your PgBouncer connections so it can capitalize on the scaling opportunities provided there

There’s an obvious downside - you’re consuming an extra direct connection and potentially impacting [throughput](#throughput) - but it’s an alternative available if needed.

&lt;h3 id=&#34;listen-notify&#34;&gt;Listen / Notify 📣&lt;/h3&gt;

Postgres comes out of the box with a handy pub/sub feature called [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html)/[NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html).

You simply call:

	LISTEN channel_name;

And that connection will receive `NOTIFY` events:

	NOTIFY channel_name, &#39;hi there!&#39;;

Like session level advisory locks, there are more robust pub/sub solutions out there. But the Postgres implementation works well, and you already have it available in your stack.

Looking at the example, you’ll notice that the `LISTEN` call is just a single statement, and it activates the listener for the current session. What have we said so many times already? Sessions bad. Transactions good... kind of.

#### kind of?
Similar to prepared statements, the docs are misleading when it comes to `LISTEN`/`NOTIFY`.

PgBouncer officially lists `LISTEN`/`NOTIFY` as an unsupported feature in transaction mode, which is not precisely true. `LISTEN` does not work in transaction mode, but `NOTIFY` does. 

`NOTIFY` is a single statement, and doesn’t rely on any session semantics. It’s also transactional[^15]:

	BEGIN;
	NOTIFY channel_name, &#39;hi!&#39;;
	ROLLBACK; -- no notification is sent

Both `NOTIFY` formats (inside and outside a transaction) work fine with transaction mode pooling. If you want to use pub/sub, you just need to make sure your `LISTEN`er is connected directly to Postgres. Since [it can be hard to tell if you’re connected to Postgres or PgBouncer](#transparency) this is somewhat tricky, unfortunately. 

I’ve built implementations `LISTEN`ing on a non-PgBouncer connection and `NOTIFY`ing on PgBouncer that work fine. There’s not much writing on this, but I have found this approach to work well.

&lt;h3 id=&#34;single-threaded&#34;&gt;The single thread 🪡&lt;/h3&gt;

In contrast to the multi process monster that is Postgres, PgBouncer runs on a paltry single process with a single thread.

This means that no matter how capable a server is, PgBouncer is only going to utilize a max of one CPU core so once [you’ve maxed out on that core](https://news.ycombinator.com/item?id=17187436) you can’t scale that single instance anymore.

A popular option is to [load balance PgBouncer instances](https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers). Otherwise, almost every alternative to PgBouncer (like Odyssey, PgCat and Supavisor) utilize multiple cores. 

If you’re using a managed Postgres service (like Crunchy Data, Supabase, Neon or Heroku), your default option is going with PgBouncer as a connection pooler - so it will be up to those services to offer a load balanced option.

&lt;h3 id=&#34;pg_dump&#34;&gt;pg_dump 🚮&lt;/h3&gt;

If you’re running `pg_dump` against PgBouncer, it’s probably by mistake.

As far as I can tell, `pg_dump` is broken when run against PgBouncer. See [https://github.com/pgbouncer/pgbouncer/issues/452](https://github.com/pgbouncer/pgbouncer/issues/452). 

The answer here is to make sure you’re using a direct connection to Postgres for utility operations like `pg_dump`.

&lt;h3 id=&#34;unavailable&#34;&gt;Other unavailable features 🫥&lt;/h3&gt;

![](https://media0.giphy.com/media/VCZgfe90H1tMTAW6n4/giphy.gif?cid=6c09b9526e2d6dfd051e5257c6dbce5ac862293219ad6e76&amp;rid=giphy.gif&amp;ct=g)

There are some remaining features which transaction mode is incompatible with as well[^16]. I have less or no experience with these:

- `WITH HOLD CURSOR` - A `WITH HOLD` continues to exist outside of a transaction, which seems like it could have handy use cases but I’ve never personally used it in my day to day.
- PRESERVE/DELETE ROWS temp tables - temporary tables are a session level feature so will not work properly, and preserve/delete rows are modifiers on how those temporary tables behave on commit, and are unsupported
- LOAD statement - this is for loading shared libraries into Postgres, so it makes sense this is not something you should be doing through a pooler. I haven’t actually tried, so I’m not sure if PgBouncer would stop you, but it requires super user privileges so it’s very unlikely that’s what your PgBouncer user has

PgBouncer documents a simple “[SQL feature map for pooling modes](https://www.pgbouncer.org/features.html)” where you can see all the features mentioned in this post.

&lt;h2 id=&#34;linting&#34;&gt;Linting 🧶&lt;/h2&gt;

Aside from having identified potential issues - what can we do to avoid them in an automated way?

Surprisingly, not much exists. And by not much, I mean i’ve found nothing outside of advice.

It makes me feel a bit like I’m exaggerating the importance of these issues. Maybe I’m the oddball that has actually encountered many of them in real production usage and had to address them. I’ve had statement timeouts and lock timeouts misapplied. I’ve had to deal with rearranging connections because of code using a session advisory lock and `LISTEN`/`NOTIFY`, or drop libraries that use them. I’ve had to remember to turn off prepared statements in my ORM to avoid named prepared statement errors.

The implications can feel small, but they can be surprising and particularly around migrations can cause real serious downtime.

We lint everywhere. As engineers we try to automate away as many mistakes as possible with linting and specs. As development teams grow, the importance of automation becomes critical to scaling because otherwise someone somewhere is going to do the wrong thing and it won’t get caught.

Some ideas that would be great to see:

- PgBouncer optional process that detects bad queries and logs them
- RDS connection pinning behavior
- Static analysis tools for app queries
- Runtime extension to client libraries
- Making sure your development flow runs PgBouncer locally to try and encounter this behavior before running on production

In the rails world there are [several](https://github.com/braintree/pg_ha_migrations) [active](https://github.com/doctolib/safe-pg-migrations) [gems](https://github.com/ankane/strong_migrations) devoted to keeping a codebase safe from issues that would cause downtime while migrating tables (ie, zero downtime). But across ecosystems I could not find anything related to protecting against PgBouncer issues.

As a step in this direction, I’ve published a (currently experimental) gem for use in Rails/ActiveRecord apps called [pg\_pool\_safe\_query](https://rubygems.org/gems/pg_pool_safe_query). It will log warnings if SQL is run that is incompatible with PgBouncer and raise an error if advisory locks and prepared statements are not disabled.

&lt;h2 id=&#34;future-improvements&#34;&gt;Can we improve connections without a pooler?&lt;/h2&gt;

A more recent development in Postgres 14 was improvements to [snapshot scalability](https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/analyzing-the-limits-of-connection-scalability-in-postgres/ba-p/1757266), which seem to have resulted in big improvements in efficiently [maintaining more idle connections](https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462#first-performance-improvements) in Postgres. 

It’s exciting to see effort being applied to increasing connection efficiency in Postgres itself. The author of that snapshot scalability improvement lines up with my own frustrations:

- Ideally Postgres would better handle traffic spikes without requiring a pooler
- Poolers cut out useful database features 
- Postgres itself would ideally move towards architecture changes across several key areas, eventually culminating in a larger move towards a lighter weight process/thread/async model which better aligns with the C10k problem out of the box

Most of the work in the industry _seems_ to concentrate on building better poolers, rather than improving the internals of Postgres connection handling itself[^17]. Outside of PgBouncer you’ve got RDS Proxy, Odyssey, PgCat, Supavisor, PgPool II and I’m sure others. All have their own benefits but suffer from the same transactional scaling limitations. 

In fairness to the _incredible_ work that goes into Postgres - every performance improvement they make in every new version is also a connection scalability improvement. If the queries, indexes, plans, and processes are making big performance gains with each version then less connections can do more.

&lt;h2 id=&#34;alternatives&#34;&gt;PgBouncer alternatives&lt;/h2&gt;

There are alternatives to PgBouncer, but the same transaction limitations apply to all of them: each has a transaction mode (or operate exclusively in transaction mode) that offers the best scaling. Once in transaction mode you can’t support most session level features anymore and you’re working off of the fact that database connections spend more time being idle than active.

They all have their own unique benefits in comparison, but have the same fundamental transaction limitations.

- [Supavisor](https://github.com/supabase/supavisor)
- [PgCat](https://github.com/postgresml/pgcat)
- [Odyssey](https://github.com/yandex/odyssey)
- [Pg Pool II](https://pgpool.net/mediawiki/index.php/Main_Page)
- [RDS Proxy](https://aws.amazon.com/rds/proxy/)

## Am I finally done with this post?
I think I’ve said enough.

Postgres is great. PgBouncer is important. Know what can go wrong and account for it.

🐘 ✌🏼 🐘 

[^1]:	This [article from Brandur](https://brandur.org/postgres-connections) details some additional nuances of handling connections and pools, but these three are the higher level version of it

[^2]:	Technically it doesn’t have to be a single instance, it could be a round Robin of multiple PgBouncers, but from a client perspective you connect to a single one

    [https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers](https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers)

[^3]:	[It’s even lower](https://elements.heroku.com/addons/heroku-postgresql) on their lower powered options. It goes from 20, to 120, to 400, then 500 once you’re around their $400/mo plans.

    Supabase has no standard plans with [more than 50 connections](https://supabase.com/blog/supabase-pgbouncer).

    [Render.com’s managed Postgres offering](https://render.com/docs/databases#connecting-to-your-database) is based on memory available on each plan: 6 gigs or less is 97 connections, less than 10 gigs is 197 connections and over 10 gigs is 397 connections.

    This isn’t totally unreasonable - managing more connections requires more cores and more memory in a process based model especially. But at their highest tiers these services don’t exceed 500 available connections.

    More generalized services like Azure and Amazon RDS will let you go as high as you like, but that’ll go badly.

[^4]:	Which is very exciting to see concentrated work on improving this aspect in Postgres internals! 🤘🏼

[^5]:	This more recent crunchy data article on making sure your Postgres app is production ready implies the old standard of 500 connections is no longer accurate so I’d be curious to know more specifics since most resources still emphasize these numbers [https://www.crunchydata.com/blog/is-your-postgres-ready-for-production](https://www.crunchydata.com/blog/is-your-postgres-ready-for-production)

[^6]:	AFAIK this is largely true of any database and MySQL also has connection pooling solutions, but it does _seem_ to be particularly necessary with postgres 

[^7]:	There are a couple caveats to this statement. Just having a dedicated low latency pool is an improvement so may slightly help concurrency. PgBouncer can also proxy multiple databases so you could increase read concurrency at least this way.

    The queueing behavior of poolers can also be a benefit since you can wait for a connection to be available for longer, vs Postgres instantly rejecting the attempt: [https://www.percona.com/blog/connection-queuing-in-pgbouncer-is-it-a-magical-remedy/](https://www.percona.com/blog/connection-queuing-in-pgbouncer-is-it-a-magical-remedy/)

[^8]:	They do mention this in [their docs](https://www.pgbouncer.org/features.html):

    \&gt; “Note that “transaction” pooling breaks client expectations of the server by design and can be used only if the application cooperates by not using non-working features.”

[^9]:	[https://github.com/pgbouncer/pgbouncer/issues/653](https://github.com/pgbouncer/pgbouncer/issues/653)

    [https://github.com/pgbouncer/pgbouncer/issues/249](https://github.com/pgbouncer/pgbouncer/issues/249)

    It also just seems to be something they’re not interested in doing

[^10]:	The idea is you fail and continue to retry. See this article for some framework agnostic approaches to retries: [https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries](https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries)

[^11]:	I might have five nickels, but still, it happens. Also again I am grateful for anyone taking the time to write up content and share their expertise.

[^12]:	That’s a topic for another time...

[^13]:	In addition to the Ruby pg gem It&#39;s used by the python [psycopg](https://github.com/psycopg/psycopg) lib, and node [node-libpq](https://github.com/brianc/node-libpq) package (and I’m sure many others). So it seems like most client libraries handle things safely enough at the protocol level to turn off prepared statements 

    If you are using Go with the pure Go lib/pq driver [see this issue](https://github.com/lib/pq/issues/889) for how to properly handle unnamed statements. The [rust sqlx library](https://github.com/launchbadge/sqlx/issues/368) seems to have a similar issue. Seems that if a library does not use libpq they end up in a bit of pain when trying to work with PgBouncer

[^14]:	Named prepared statements can boost performance for repetitious queries because they bypass the Parse call on subsequent runs. That’s their primary benefit in comparison to unnamed statements

[^15]:	`LISTEN` can be [called in a transaction as well](https://www.postgresql.org/docs/current/sql-listen.html), but all that means is the session level listen won’t be triggered until the transaction commits, and won’t start listening at all if a rollback is triggered

[^16]:	I do find myself asking “what is the point of nice features if you can’t use them at scale because of transaction mode pooling”? Not being able to use certain features at scale should never preclude them from being built - but it’s a disappointing reality

[^17]:	I’m a _little_ afraid to have made this statement and the potential for someone to come back at me angry about this being an oversimplification 😅
</source:markdown>
    </item>
    
    <item>
      <title>Making Tanstack Table 1000x faster with a 1 line change</title>
      <link>https://jpcamara.com/2023/03/07/making-tanstack-table.html</link>
      <pubDate>Tue, 07 Mar 2023 17:10:00 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2023/03/07/making-tanstack-table.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/2ab8d73f1c.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;A few months back I was working on a Javascript frontend for a large dataset using &lt;a href=&#34;https://tanstack.com/table/v8&#34;&gt;Tanstack Table&lt;/a&gt;. The relevant constraints were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Up to 50k rows of content&lt;/li&gt;
&lt;li&gt;Grouped by up to 3 columns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using react and virtualized rendering, showing 50k rows was performing well. But when the Tanstack Table grouping feature was enabled, I was seeing slowdowns on a few thousand rows, and &lt;em&gt;huge&lt;/em&gt; slowdowns on 50k rows.&lt;/p&gt;
&lt;p&gt;It might have gone unnoticed if it was 100ms slower, or even 500ms slower. But in the worst case renders would go from less than a second without grouping up to &lt;em&gt;30-40 seconds&lt;/em&gt; with it.&lt;/p&gt;
&lt;h2 id=&#34;tracking-down-the-issue&#34;&gt;Tracking down the issue&lt;/h2&gt;
&lt;p&gt;Initially I tried using the chrome Javascript profiler, but it can be tough to use when performance is so slow. The profiler adds noticeable overhead to your code, and since the code took 30-40 seconds already, it was basically unusable&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Unable to use the profiler, I reached for an old, simple standby: &lt;code&gt;console.time&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. This is a convenient way to see how long a section of code takes, logged to your console:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;expensive code&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#a6e22e&#34;&gt;thisIsExpensive&lt;/span&gt;();
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;expensive code&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#75715e&#34;&gt;// console.time
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   expensive code: 1000ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A side note about optimizing code&lt;/strong&gt;: as programmers we are &lt;em&gt;full&lt;/em&gt; of ideas about what needs to be optimized and are &lt;em&gt;terrible&lt;/em&gt; at getting it right.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We make educated guesses at what is important to optimize and where the problem is, and until we measure we’re usually wrong. I try now to wrap everything possible in a benchmark to make sure I’m even in the right place, then start narrowing the benchmark inside of the code from there.&lt;/p&gt;
&lt;p&gt;When tracking down a performance issue this would be a general outline:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#a6e22e&#34;&gt;elements&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;methodCall&amp;#39;&lt;/span&gt;);
  &lt;span style=&#34;color:#a6e22e&#34;&gt;methodCall&lt;/span&gt;(() &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;build&amp;#39;&lt;/span&gt;);
	&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();
	&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;build&amp;#39;&lt;/span&gt;);
  });
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;methodCall&amp;#39;&lt;/span&gt;);
});
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#75715e&#34;&gt;// build      49ms
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// methodCall 50ms
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// build      51ms
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// methodCall 52ms
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// everything 102ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Back to the table rendering&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As usual, before measuring, all of my guesses at the potential performance problem were wrong.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My own code was fine, so this was a case where it was actually a library bug, which was a surprise. There was no code path I could find that was performing poorly - all of the time was spent in the library. When using Tanstack table in React all of the logic happens in a pretty centralized place - the &lt;code&gt;useReactTable&lt;/code&gt; hook - so it was easy to see the time was all there&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#a6e22e&#34;&gt;customCode&lt;/span&gt;();
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;useReactTable&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#a6e22e&#34;&gt;useReactTable&lt;/span&gt;(...);
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;useReactTable&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
&lt;span style=&#34;color:#75715e&#34;&gt;// useReactTable 31500 ms
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// everything    31537 ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;One of the &lt;em&gt;nicest&lt;/em&gt; things about developing Javascript with packages is that at any time I can open up the &lt;code&gt;node_modules&lt;/code&gt; folder and play around with third party code. In this case I was able to modify the Tanstack Table source code directly to add some timing information.&lt;/li&gt;
&lt;li&gt;Turning on grouping was when everything slowed down, so it made the most sense to start timing that code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is an abbreviated version of the grouped row source code, with my first pass at timing what I thought were the likeliest culprits. Pay attention primarily to &lt;code&gt;console.time&lt;/code&gt; statements - you don’t have to understand everything going on.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getGroupedRowModel&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extends&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RowData&lt;/span&gt;&amp;gt;() {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
  &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;	
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;grouping filter&amp;#39;&lt;/span&gt;)
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;existing&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;grouping&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt;
    &lt;span style=&#34;color:#a6e22e&#34;&gt;table&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getColumn&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;)
  )
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;grouping filter&amp;#39;&lt;/span&gt;)
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupUpRecursively&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (
    &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Row&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt;&amp;gt;[],
    &lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
    &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId?&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
  ) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;existing&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;) {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
      &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt;
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;subRows&lt;/span&gt;) {
        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;subRows&amp;#39;&lt;/span&gt;)
        &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;subRows&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupUpRecursively&lt;/span&gt;(
          &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;subRows&lt;/span&gt;, 
          &lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
        )
        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;subRows&amp;#39;&lt;/span&gt;)
      }
      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;
    });
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;existingGrouping&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt;]&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rowGroupsMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupBy&lt;/span&gt;(
      &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;, 
      &lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;
    )
	
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;aggregatedGroupedRows&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array.&lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rowGroupsMap&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;entries&lt;/span&gt;()).&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(([&lt;span style=&#34;color:#a6e22e&#34;&gt;groupingValue&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;groupedRows&lt;/span&gt;], &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
      &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;groupingValue&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
      &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;parentId&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;
      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(
        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;aggregatedGroupedRows groupUpRecursively&amp;#39;&lt;/span&gt;
      )
      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;subRows&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupUpRecursively&lt;/span&gt;(
        &lt;span style=&#34;color:#a6e22e&#34;&gt;groupedRows&lt;/span&gt;, 
        &lt;span style=&#34;color:#a6e22e&#34;&gt;depth&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
        &lt;span style=&#34;color:#a6e22e&#34;&gt;id&lt;/span&gt;
      )
      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(
        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;aggregatedGroupedRows groupUpRecursively&amp;#39;&lt;/span&gt;
      )
      &lt;span style=&#34;color:#75715e&#34;&gt;//...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;      }
    }
  }
  &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;everything&amp;#39;&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My hunch was that the &lt;code&gt;groupUpRecursively&lt;/code&gt; function was to blame - it made logical sense that tens of thousands of recursive calls could cause a slowdown (&lt;strong&gt;spoiler&lt;/strong&gt;: as usual, I was wrong 😑):&lt;/p&gt;
&lt;p&gt;First pass was a bust - it logged thousands of the &lt;code&gt;subRows&lt;/code&gt; timers - every iteration was fast and there were too many of them to be useful so I cut it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.time
 subRows: 0 ms
   at map (packages/table-core/src/utils/getGroupedRowModel.ts:48:25)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)

console.time
 subRows: 0 ms
   at map (packages/table-core/src/utils/getGroupedRowModel.ts:48:25)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)
       at Array.map (&amp;lt;anonymous&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Removing that, I started to get closer. I was accounting for &lt;em&gt;most&lt;/em&gt; of the time but I had two problems: I wasn’t accounting for &lt;em&gt;all&lt;/em&gt; of the time (&lt;code&gt;everything&lt;/code&gt; was 33 seconds and &lt;code&gt;groupUpRecursively&lt;/code&gt; was only 23 seconds) and the chunk of time I was logging was too large to usefully identify the problem code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.time
  grouping filter: 1 ms
    at fn (packages/table-core/src/utils/getGroupedRowModel.ts:22:17)

console.time
  aggregatedGroupedRows groupUpRecursively: 23248 ms
    at map (packages/table-core/src/utils/getGroupedRowModel.ts:71:23)
        at Array.map (&amp;lt;anonymous&amp;gt;)
        at Array.map (&amp;lt;anonymous&amp;gt;)
        at Array.map (&amp;lt;anonymous&amp;gt;)

console.time
  everything: 33509 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I realized I had missed a function call - a little unassuming function called &lt;code&gt;groupBy&lt;/code&gt; - so I added a &lt;code&gt;console.time&lt;/code&gt; block there next:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;groupBy&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rowGroupsMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;)
&lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;timeEnd&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;groupBy&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Got it! Almost the entirety of the 31 seconds was concentrated into 3 calls to &lt;code&gt;groupBy&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.time
  grouping filter: 2 ms
    at fn (packages/table-core/src/utils/getGroupedRowModel.ts:22:17)

console.time
  groupBy: 10279 ms
    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)

console.time
  groupBy: 10868 ms
    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)
        at Array.map (&amp;lt;anonymous&amp;gt;)

console.time
  groupBy: 10244 ms
    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)
        at Array.map (&amp;lt;anonymous&amp;gt;)
        at Array.map (&amp;lt;anonymous&amp;gt;)

console.time
  aggregatedGroupedRows groupUpRecursively: 21159 ms
    at map (packages/table-core/src/utils/getGroupedRowModel.ts:71:23)
        at Array.map (&amp;lt;anonymous&amp;gt;)
        at Array.map (&amp;lt;anonymous&amp;gt;)

console.time
  everything: 31537 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each grouped column, it was calling &lt;code&gt;groupBy&lt;/code&gt; and each call took roughly 10 seconds.&lt;/p&gt;
&lt;p&gt;So what the heck was going on in the &lt;code&gt;groupBy&lt;/code&gt; function that was causing such a massive slowdown. Can you pick it out?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupBy&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extends&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RowData&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Row&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt;&amp;gt;[], &lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;any&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Row&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;TData&lt;/span&gt;&amp;gt;[]&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;()
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;)
	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;) {
	  &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
	} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
	  &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
	}
	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;
  }, &lt;span style=&#34;color:#a6e22e&#34;&gt;groupMap&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I started chopping up the function. I switched the &lt;code&gt;Map&lt;/code&gt; to be an object literal in case that was causing some kind of memory overhead and tried changing to a &lt;code&gt;for&lt;/code&gt; loop instead of &lt;code&gt;reduce&lt;/code&gt; in case the amount of iterations plus closures was causing an issue&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Those had no effect so next I started commenting out lines of the function. When I commented out this line, all of a sudden everything started finishing instantly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// map.set(resKey, [...previous, row])
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;what-was-the-purpose-of-that-line-and-why-was-it-so-slow&#34;&gt;What was the purpose of that line and why was it so slow?&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;)
&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On each iteration of the &lt;code&gt;reduce&lt;/code&gt; call, the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Used the value of that column cell as a map key. Let’s say the value is the string “New York”&lt;/li&gt;
&lt;li&gt;If there was no value associated with “New York”, it would set a value of the current &lt;code&gt;row&lt;/code&gt; wrapped in an array&lt;/li&gt;
&lt;li&gt;If there was already a value, it would use the Javascript &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_array_literals&#34;&gt;spread operator&lt;/a&gt; to concatenate the current &lt;code&gt;row&lt;/code&gt; onto the end of the previous array value&lt;/li&gt;
&lt;li&gt;This means that on each iteration of &lt;code&gt;reduce&lt;/code&gt;, the spread operator was creating a new, incrementally larger array, getting slower with each iteration&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 1st iteration
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;[...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;span style=&#34;color:#75715e&#34;&gt;// 2nd iteration
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;[...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]
&lt;span style=&#34;color:#75715e&#34;&gt;// 3rd iteration
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;[...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;]
&lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 50,000th iteration
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;[...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,...&lt;span style=&#34;color:#ae81ff&#34;&gt;49998&lt;/span&gt;,&lt;span style=&#34;color:#ae81ff&#34;&gt;49999&lt;/span&gt;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;what-does-spread-do-behind-the-scenes&#34;&gt;What does spread do behind the scenes?&lt;/h2&gt;
&lt;p&gt;In our case the spread operator takes an &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#built-in_iterables&#34;&gt;iterable object&lt;/a&gt; and loops over it to create new array. It’s probably more nuanced than this, but the spread operator is likely &lt;a href=&#34;https://frontendmasters.com/courses/algorithms/&#34;&gt;O(n)&lt;/a&gt;. That means that as the size of the array grows, the longer it takes to spread the values into a new array.&lt;/p&gt;
&lt;p&gt;I’m &lt;em&gt;certain&lt;/em&gt; there are additional efficiencies provided in the language internals but the following code is essentially equivalent&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;];
	
&lt;span style=&#34;color:#75715e&#34;&gt;// spread
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [...&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;];
	
&lt;span style=&#34;color:#75715e&#34;&gt;// manual loop
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
	
&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;a&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;]);
}
&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which means that the original version of that &lt;code&gt;groupBy&lt;/code&gt; code was equivalent to this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// original code
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [...&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;]
	
&lt;span style=&#34;color:#75715e&#34;&gt;// manual spread
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tempPrevious&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [];
  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;j&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;j&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;j&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
    &lt;span style=&#34;color:#a6e22e&#34;&gt;tempPrevious&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;j&lt;/span&gt;]);
  }
  &lt;span style=&#34;color:#a6e22e&#34;&gt;tempPrevious&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;]);
  &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tempPrevious&lt;/span&gt;;
  &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;tempPrevious&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Seeing the manual spread, one thing sticks out immediately: we have a nested loop.&lt;/p&gt;
&lt;p&gt;A nested loop is a good shorthand for identifying one of the slower forms of algorithmic complexity: a “Big O” complexity of O(n^2)&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;. This means that for an array size of 50,000, the number of iterations required would be 50,000^2, or 2.5 &lt;em&gt;billion&lt;/em&gt; iterations 😱.&lt;/p&gt;
&lt;p&gt;In our specific case the array started out empty and grew to a size of 50,000, so it’s wasn’t &lt;em&gt;quite&lt;/em&gt; as bad. Technically it was still a complexity of O(n^2), but &lt;em&gt;practically&lt;/em&gt; it was O(n^2/2)&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;. Either way it still meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1,249,975,000, or 1 &lt;em&gt;billion&lt;/em&gt; 249 &lt;em&gt;million&lt;/em&gt; iterations&lt;/li&gt;
&lt;li&gt;&lt;em&gt;49,999&lt;/em&gt; discarded arrays allocated for &lt;em&gt;1,249,925,001&lt;/em&gt; entries&lt;/li&gt;
&lt;li&gt;Times three &lt;code&gt;groupBy&lt;/code&gt; calls that means &lt;em&gt;3,749,925,000&lt;/em&gt; iterations, &lt;em&gt;149,997&lt;/em&gt; discarded arrays and &lt;em&gt;3,749,775,003&lt;/em&gt; allocated entries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour one out for the garbage collector and Javascript runtime 🙅🏻‍♂️☠️🙅🏻‍♂️. Honestly, for how many iterations and how much garbage we accumulated, it was operating pretty well 🫠.&lt;/p&gt;
&lt;p&gt;So the question was - what could be done to improve it?&lt;/p&gt;
&lt;h3 id=&#34;spread-is-an-immutable-pattern-so-should-the-code-be-kept-immutable&#34;&gt;Spread is an immutable pattern, so should the code be kept immutable?&lt;/h3&gt;
&lt;p&gt;Aside from providing a convenient syntax for combining iterables, the spread operator also allows us to keep code immutable. This can be beneficial because immutable patterns mean we avoid mutations, which means we don’t change data from underneath other code that is using it.&lt;/p&gt;
&lt;p&gt;If this code was written to be immutable, there were some other options we could try to improve performance which would behave the same as the spread operator:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// Array.from()
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~10 seconds, just as slow 😒
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Array.&lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;)
&lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;)
	
&lt;span style=&#34;color:#75715e&#34;&gt;// slice()
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~10 seconds, just as slow 🙃
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;slice&lt;/span&gt;()
&lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;)
	
&lt;span style=&#34;color:#75715e&#34;&gt;// concat(row)
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~10 seconds, just as slow 🧐
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;concat&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;))
	
&lt;span style=&#34;color:#75715e&#34;&gt;// hand written for loop
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~14 seconds, slower than spread! 😱 
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; []
&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
  &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;])
}
&lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;arr&lt;/span&gt;)
	
&lt;span style=&#34;color:#75715e&#34;&gt;// concat([row])
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~4 seconds 🏃‍♂️ 
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;concat&lt;/span&gt;([&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;]))
	
&lt;span style=&#34;color:#75715e&#34;&gt;// using the `immer` node package
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//   ~100ms 🚀 
&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;produce&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;groupMap&lt;/span&gt;, (&lt;span style=&#34;color:#a6e22e&#34;&gt;draft&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`fixedKey`&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;)
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;) {
      &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
      &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;
  }, &lt;span style=&#34;color:#a6e22e&#34;&gt;draft&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Array.from&lt;/code&gt;, &lt;code&gt;slice&lt;/code&gt;, and &lt;code&gt;concat&lt;/code&gt; all had the same performance characteristics as the spread operator. There were a few surprises however:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A manual &lt;code&gt;for&lt;/code&gt; loop was the slowest option, coming in at around 14 seconds. The native code versions of these methods are clearly much more optimized than the manual loop we can write&lt;/li&gt;
&lt;li&gt;&lt;code&gt;concat&lt;/code&gt;, when handed an array as the argument instead of a plain object, was consistently faster than spread on any v8 platform (node/chrome). It took half the time! Either there was a hard to find mistake in my code or the runtime had a special optimization for this use case. Still too slow however&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/immerjs/immer&#34;&gt;Using &lt;code&gt;immer&lt;/code&gt;&lt;/a&gt; performed significantly faster than all other options. &lt;code&gt;immer&lt;/code&gt; provides regular Javascript interfaces on immutable data using &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&#34;&gt;proxies&lt;/a&gt; and &lt;a href=&#34;https://blog.klipse.tech/javascript/2021/02/26/structural-sharing-in-javascript.html&#34;&gt;structural sharing&lt;/a&gt; to make efficient changes to data structures without changing the original source object. However in this case the main benefit of the &lt;code&gt;immer&lt;/code&gt; approach is that we were not copying any arrays - just &lt;code&gt;push&lt;/code&gt;ing directly to them because they were created inline in our function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But did we even need immutability at all? Was the &lt;code&gt;immer&lt;/code&gt; approach good enough? Could we get even faster?&lt;/p&gt;
&lt;h3 id=&#34;using-push-directly&#34;&gt;Using push directly&lt;/h3&gt;
&lt;p&gt;For performance, you can’t beat mutations.&lt;/p&gt;
&lt;p&gt;Mutations modify data in place, without copying and creating duplicated data. Looking over our &lt;code&gt;groupBy&lt;/code&gt; function, there wasn’t any benefit to using immutable operations. Everything was created in the function so we could safely mutate it before we returned it. I don’t think the original code was written with immutability in mind - using the spread syntax was just convenient.&lt;/p&gt;
&lt;p&gt;When using array &lt;code&gt;push&lt;/code&gt;, we are effectively running with an algorithmic complexity of O(1). That means as our array grows, the cost of inserting new elements does not. It’s constant time: it’s as fast for 1 item as it is for 50,000.&lt;/p&gt;
&lt;p&gt;Here’s the final version of the fix, now 1000x faster for my use case:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupBy&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;extends&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RowData&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;Row&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;TData&lt;/span&gt;&amp;gt;[], &lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) {
  &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;groupMap&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Map&lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;any&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Row&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;TData&lt;/span&gt;&amp;gt;[]&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt;()
	
  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;rows&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;reduce&lt;/span&gt;((&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;=&amp;gt;&lt;/span&gt; {
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getValue&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;columnId&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;
    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;)
    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;) {
      &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;resKey&lt;/span&gt;, [&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;])
    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
      &lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
    }
    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;
  }, &lt;span style=&#34;color:#a6e22e&#34;&gt;groupMap&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Can you spot the difference? It’s this one simple change:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;previous&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;push&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;row&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;previous.push(row)&lt;/code&gt; mutates the existing array and brings our time down from 10 seconds per &lt;code&gt;groupBy&lt;/code&gt; down to around 10ms, 1000x faster than the original code 🚀.&lt;/p&gt;
&lt;p&gt;The takeaway here is that while spread is an expressive and convenient language feature, it’s important to see it for what it is at its core: a &lt;code&gt;for&lt;/code&gt; loop. If you see a spread inside of a loop, you’ll want to rewrite it if there is any chance it will operate on a large set of values.&lt;/p&gt;
&lt;h3 id=&#34;source-code&#34;&gt;Source code&lt;/h3&gt;
&lt;p&gt;You can see the full PR here &lt;a href=&#34;https://github.com/TanStack/table/pull/4495&#34;&gt;https://github.com/TanStack/table/pull/4495&lt;/a&gt;. Because it was such a small fix, the majority of the PR is just a spec to guard against a future performance regression.&lt;/p&gt;
&lt;p&gt;And here’s the &lt;a href=&#34;https://replit.com/@JPCamara/Tanstack-GroupBy-Performance?s=app&#34;&gt;source code&lt;/a&gt; for each attempt, runnable on the Replit platform. If you run it there the timings are much slower because it is running a lower powered CPU than my local machine, but as a relative measure the results are consistent with the numbers in this post.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I also find it can be hard to get a great sense for a profile when profiling react code since certain internal react code paths are hit so often the results are tough to decipher&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;It’s non standard, but in the future I need to remember to reach for &lt;code&gt;console.profile&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;That could have been helpful here for profiling smaller blocks of code rather than deciphering the whole React render stack.&lt;/p&gt;
&lt;p&gt;Considering how slow the code was it still may not have been usable anyways.&lt;/p&gt;
&lt;p&gt;You can learn more about it here: &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/console/profile&#34;&gt;https://developer.mozilla.org/en-US/docs/Web/API/console/profile&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;What I’ve found is that Javascript engines have gotten so optimized that a &lt;code&gt;forEach&lt;/code&gt;/&lt;code&gt;map&lt;/code&gt;/&lt;code&gt;reduce&lt;/code&gt; call can be as efficient as a hand written &lt;code&gt;for&lt;/code&gt; loop. They’re almost never the performance problem in Javascript, least of all in a large set of iterations where the JIT can aggressively optimize.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I should note that the performance was at its worst for poorly distributed groupings. By that I mean if you had a grouped column with only one value then the grouping is concentrated into one key with all 50,000 values. If there were lots of unique values in the column the arrays were smaller and built more quickly&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The v8 internals are, unsurprisingly, very complicated. Logic for spread seems to exist in a variety of places because it’s such a core feature so I wasn’t able to track down a specific implementation&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If “Big O” complexity doesn’t mean anything to you, there’s lots of great resources online for it and it’s a great concept to be familiar with. The basic idea is it helps you identify how costly a particular piece of code will be to run.&lt;/p&gt;
&lt;p&gt;For a deep dive into it, &lt;a href=&#34;https://frontendmasters.com/courses/algorithms/&#34;&gt;https://frontendmasters.com/courses/algorithms/&lt;/a&gt; is a great free resource&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;In terms of algorithmic complexity, O(n^2) and O(n^2/2) are considered equivalent, since you generally drop the constant (in this case, 1/2).&lt;/p&gt;
&lt;p&gt;But from a practical perspective, if you’re going to have a slow algorithm you’d still rather have it be half as slow as it’s full potential slowness 😅&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2023/2ab8d73f1c.png)

A few months back I was working on a Javascript frontend for a large dataset using [Tanstack Table](https://tanstack.com/table/v8). The relevant constraints were:

- Up to 50k rows of content
- Grouped by up to 3 columns

Using react and virtualized rendering, showing 50k rows was performing well. But when the Tanstack Table grouping feature was enabled, I was seeing slowdowns on a few thousand rows, and _huge_ slowdowns on 50k rows.

It might have gone unnoticed if it was 100ms slower, or even 500ms slower. But in the worst case renders would go from less than a second without grouping up to _30-40 seconds_ with it.

## Tracking down the issue
Initially I tried using the chrome Javascript profiler, but it can be tough to use when performance is so slow. The profiler adds noticeable overhead to your code, and since the code took 30-40 seconds already, it was basically unusable[^1].

Unable to use the profiler, I reached for an old, simple standby: `console.time`[^2]. This is a convenient way to see how long a section of code takes, logged to your console: 

```ts
console.time(&#39;expensive code&#39;);
thisIsExpensive();
console.timeEnd(&#39;expensive code&#39;);
// console.time
//   expensive code: 1000ms
```

&gt; **A side note about optimizing code**: as programmers we are _full_ of ideas about what needs to be optimized and are _terrible_ at getting it right.

We make educated guesses at what is important to optimize and where the problem is, and until we measure we’re usually wrong. I try now to wrap everything possible in a benchmark to make sure I’m even in the right place, then start narrowing the benchmark inside of the code from there. 

When tracking down a performance issue this would be a general outline:

```ts
console.time(&#39;everything&#39;);
elements.forEach(() =&gt; {
  console.time(&#39;methodCall&#39;);
  methodCall(() =&gt; {
    console.time(&#39;build&#39;);
	build();
	console.timeEnd(&#39;build&#39;);
  });
  console.timeEnd(&#39;methodCall&#39;);
});
console.timeEnd(&#39;everything&#39;);
// build      49ms
// methodCall 50ms
// build      51ms
// methodCall 52ms
// everything 102ms
```

&gt; **Back to the table rendering**

As usual, before measuring, all of my guesses at the potential performance problem were wrong.

- My own code was fine, so this was a case where it was actually a library bug, which was a surprise. There was no code path I could find that was performing poorly - all of the time was spent in the library. When using Tanstack table in React all of the logic happens in a pretty centralized place - the `useReactTable` hook - so it was easy to see the time was all there 

```ts
console.time(&#39;everything&#39;);
customCode();
console.time(&#39;useReactTable&#39;);
useReactTable(...);
console.timeEnd(&#39;useReactTable&#39;);
console.timeEnd(&#39;everything&#39;);
// useReactTable 31500 ms
// everything    31537 ms
```

- One of the _nicest_ things about developing Javascript with packages is that at any time I can open up the `node_modules` folder and play around with third party code. In this case I was able to modify the Tanstack Table source code directly to add some timing information.
- Turning on grouping was when everything slowed down, so it made the most sense to start timing that code 

This is an abbreviated version of the grouped row source code, with my first pass at timing what I thought were the likeliest culprits. Pay attention primarily to `console.time` statements - you don’t have to understand everything going on.

```ts
function getGroupedRowModel&lt;TData extends RowData&gt;() {
  console.time(&#39;everything&#39;);
  //...
	
  console.time(&#39;grouping filter&#39;)
  const existing = grouping.filter(columnId =&gt;
    table.getColumn(columnId)
  )
  console.timeEnd(&#39;grouping filter&#39;)
	
  const groupUpRecursively = (
    rows: Row&lt;TData&gt;[],
    depth = 0,
    parentId?: string
  ) =&gt; {
    if (depth &gt;= existing.length) {
      return rows.map(row =&gt; {
      row.depth = depth
      //...
      if (row.subRows) {
        console.time(&#39;subRows&#39;)
        row.subRows = groupUpRecursively(
          row.subRows, 
          depth + 1
        )
        console.timeEnd(&#39;subRows&#39;)
      }
      return row
    });
	
    const columnId: string = existingGrouping[depth]!
    const rowGroupsMap = groupBy(
      rows, 
      columnId
    )
	
    const aggregatedGroupedRows = Array.from(rowGroupsMap.entries()).map(([groupingValue, groupedRows], index) =&gt; {
      let id = `${columnId}:${groupingValue}`
      id = parentId ? `${parentId}&gt;${id}` : id
      console.time(
        &#39;aggregatedGroupedRows groupUpRecursively&#39;
      )
      const subRows = groupUpRecursively(
        groupedRows, 
        depth + 1,
        id
      )
      console.timeEnd(
        &#39;aggregatedGroupedRows groupUpRecursively&#39;
      )
      //...
      }
    }
  }
  console.timeEnd(&#39;everything&#39;);
}
```

My hunch was that the `groupUpRecursively` function was to blame - it made logical sense that tens of thousands of recursive calls could cause a slowdown (**spoiler**: as usual, I was wrong 😑):

First pass was a bust - it logged thousands of the `subRows` timers - every iteration was fast and there were too many of them to be useful so I cut it.

	console.time
	 subRows: 0 ms
	   at map (packages/table-core/src/utils/getGroupedRowModel.ts:48:25)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)
	
	console.time
	 subRows: 0 ms
	   at map (packages/table-core/src/utils/getGroupedRowModel.ts:48:25)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)
	       at Array.map (&lt;anonymous&gt;)

Removing that, I started to get closer. I was accounting for _most_ of the time but I had two problems: I wasn’t accounting for _all_ of the time (`everything` was 33 seconds and `groupUpRecursively` was only 23 seconds) and the chunk of time I was logging was too large to usefully identify the problem code:

	console.time
	  grouping filter: 1 ms
	    at fn (packages/table-core/src/utils/getGroupedRowModel.ts:22:17)
	
	console.time
	  aggregatedGroupedRows groupUpRecursively: 23248 ms
	    at map (packages/table-core/src/utils/getGroupedRowModel.ts:71:23)
	        at Array.map (&lt;anonymous&gt;)
	        at Array.map (&lt;anonymous&gt;)
	        at Array.map (&lt;anonymous&gt;)
	
	console.time
	  everything: 33509 ms

I realized I had missed a function call - a little unassuming function called `groupBy` - so I added a `console.time` block there next:

```ts
console.time(&#39;groupBy&#39;)
const rowGroupsMap = groupBy(rows, columnId)
console.timeEnd(&#39;groupBy&#39;)
```

Got it! Almost the entirety of the 31 seconds was concentrated into 3 calls to `groupBy`.

	console.time
	  grouping filter: 2 ms
	    at fn (packages/table-core/src/utils/getGroupedRowModel.ts:22:17)
	
	console.time
	  groupBy: 10279 ms
	    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)
	
	console.time
	  groupBy: 10868 ms
	    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)
	        at Array.map (&lt;anonymous&gt;)
	
	console.time
	  groupBy: 10244 ms
	    at groupUpRecursively (packages/table-core/src/utils/getGroupedRowModel.ts:59:19)
	        at Array.map (&lt;anonymous&gt;)
	        at Array.map (&lt;anonymous&gt;)
	
	console.time
	  aggregatedGroupedRows groupUpRecursively: 21159 ms
	    at map (packages/table-core/src/utils/getGroupedRowModel.ts:71:23)
	        at Array.map (&lt;anonymous&gt;)
	        at Array.map (&lt;anonymous&gt;)
	
	console.time
	  everything: 31537 ms

For each grouped column, it was calling `groupBy` and each call took roughly 10 seconds.

So what the heck was going on in the `groupBy` function that was causing such a massive slowdown. Can you pick it out?

```ts
function groupBy&lt;TData extends RowData&gt;(rows: Row&lt;TData&gt;[], columnId: string) {
  const groupMap = new Map&lt;any, Row&lt;TData&gt;[]&gt;()
	
  return rows.reduce((map, row) =&gt; {
    const resKey = `${row.getValue(columnId)}`
	const previous = map.get(resKey)
	if (!previous) {
	  map.set(resKey, [row])
	} else {
	  map.set(resKey, [...previous, row])
	}
	return map
  }, groupMap)
}
```

I started chopping up the function. I switched the `Map` to be an object literal in case that was causing some kind of memory overhead and tried changing to a `for` loop instead of `reduce` in case the amount of iterations plus closures was causing an issue[^3]. 

Those had no effect so next I started commenting out lines of the function. When I commented out this line, all of a sudden everything started finishing instantly:

```ts
// map.set(resKey, [...previous, row])
```

### What was the purpose of that line and why was it so slow?

```ts
const resKey = `${row.getValue(columnId)}`
const previous = map.get(resKey)
if (!previous) {
  map.set(resKey, [row])
} else {
  map.set(resKey, [...previous, row])
}
```

On each iteration of the `reduce` call, the code:

- Used the value of that column cell as a map key. Let’s say the value is the string “New York”
- If there was no value associated with “New York”, it would set a value of the current `row` wrapped in an array 
- If there was already a value, it would use the Javascript [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_array_literals) to concatenate the current `row` onto the end of the previous array value
- This means that on each iteration of `reduce`, the spread operator was creating a new, incrementally larger array, getting slower with each iteration[^4] 

```ts
// 1st iteration
[...previous, row] =&gt; [1,2]
// 2nd iteration
[...previous, row] =&gt; [1,2,3]
// 3rd iteration
[...previous, row] =&gt; [1,2,3,4]
// ...
// 50,000th iteration
[...previous, row] =&gt; [1,...49998,49999]
```

## What does spread do behind the scenes?
In our case the spread operator takes an [iterable object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#built-in_iterables) and loops over it to create new array. It’s probably more nuanced than this, but the spread operator is likely [O(n)](https://frontendmasters.com/courses/algorithms/). That means that as the size of the array grows, the longer it takes to spread the values into a new array.

I’m _certain_ there are additional efficiencies provided in the language internals but the following code is essentially equivalent[^5]:

```ts
const a = [1, 2, 3, 4, 5, 6, 7];
	
// spread
const b = [...a, 8];
	
// manual loop
let c = [];
	
for (let i = 0; i &lt; a.length; i++) {
  c.push(a[i]);
}
c.push(8);
```

Which means that the original version of that `groupBy` code was equivalent to this:

```ts
// original code
map.set(resKey, [...previous, row]
	
// manual spread
for (let i = 0; i &lt; rows.length; i++) {
  const tempPrevious = [];
  for (let j = 0; j &lt; previous.length; j++) {
    tempPrevious.push(previous[j]);
  }
  tempPrevious.push(rows[i]);
  previous = tempPrevious;
  map.set(resKey, tempPrevious);
}
```

Seeing the manual spread, one thing sticks out immediately: we have a nested loop.

A nested loop is a good shorthand for identifying one of the slower forms of algorithmic complexity: a “Big O” complexity of O(n^2)[^6]. This means that for an array size of 50,000, the number of iterations required would be 50,000^2, or 2.5 _billion_ iterations 😱.

In our specific case the array started out empty and grew to a size of 50,000, so it’s wasn’t _quite_ as bad. Technically it was still a complexity of O(n^2), but _practically_ it was O(n^2/2)[^7]. Either way it still meant:

- 1,249,975,000, or 1 _billion_ 249 _million_ iterations
- _49,999_ discarded arrays allocated for _1,249,925,001_ entries
- Times three `groupBy` calls that means _3,749,925,000_ iterations, _149,997_ discarded arrays and _3,749,775,003_ allocated entries

Pour one out for the garbage collector and Javascript runtime 🙅🏻‍♂️☠️🙅🏻‍♂️. Honestly, for how many iterations and how much garbage we accumulated, it was operating pretty well 🫠.

So the question was - what could be done to improve it?

### Spread is an immutable pattern, so should the code be kept immutable?
Aside from providing a convenient syntax for combining iterables, the spread operator also allows us to keep code immutable. This can be beneficial because immutable patterns mean we avoid mutations, which means we don’t change data from underneath other code that is using it.

If this code was written to be immutable, there were some other options we could try to improve performance which would behave the same as the spread operator:

```ts
// Array.from()
//   ~10 seconds, just as slow 😒
const arr = Array.from(previous)
arr.push(row)
map.set(resKey, arr)
	
// slice()
//   ~10 seconds, just as slow 🙃
const arr = previous.slice()
arr.push(row)
map.set(resKey, arr)
	
// concat(row)
//   ~10 seconds, just as slow 🧐
map.set(resKey, previous.concat(row))
	
// hand written for loop
//   ~14 seconds, slower than spread! 😱 
let arr = []
for (let i = 0; i &lt; previous.length; i++) {
  arr.push(previous[i])
}
arr.push(row)
map.set(resKey, arr)
	
// concat([row])
//   ~4 seconds 🏃‍♂️ 
map.set(resKey, previous.concat([row]))
	
// using the `immer` node package
//   ~100ms 🚀 
return produce(groupMap, (draft) =&gt; {
  return rows.reduce((map, row) =&gt; {
    const resKey = `fixedKey`
    let previous = map.get(resKey)
    if (!previous) {
      map.set(resKey, [row])
    } else {
      previous.push(row)
    }
    return map
  }, draft)
})
```

`Array.from`, `slice`, and `concat` all had the same performance characteristics as the spread operator. There were a few surprises however:

- A manual `for` loop was the slowest option, coming in at around 14 seconds. The native code versions of these methods are clearly much more optimized than the manual loop we can write
- `concat`, when handed an array as the argument instead of a plain object, was consistently faster than spread on any v8 platform (node/chrome). It took half the time! Either there was a hard to find mistake in my code or the runtime had a special optimization for this use case. Still too slow however
- [Using `immer`](https://github.com/immerjs/immer) performed significantly faster than all other options. `immer` provides regular Javascript interfaces on immutable data using [proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and [structural sharing](https://blog.klipse.tech/javascript/2021/02/26/structural-sharing-in-javascript.html) to make efficient changes to data structures without changing the original source object. However in this case the main benefit of the `immer` approach is that we were not copying any arrays - just `push`ing directly to them because they were created inline in our function

But did we even need immutability at all? Was the `immer` approach good enough? Could we get even faster?

### Using push directly
For performance, you can’t beat mutations. 

Mutations modify data in place, without copying and creating duplicated data. Looking over our `groupBy` function, there wasn’t any benefit to using immutable operations. Everything was created in the function so we could safely mutate it before we returned it. I don’t think the original code was written with immutability in mind - using the spread syntax was just convenient.

When using array `push`, we are effectively running with an algorithmic complexity of O(1). That means as our array grows, the cost of inserting new elements does not. It’s constant time: it’s as fast for 1 item as it is for 50,000.

Here’s the final version of the fix, now 1000x faster for my use case:

```ts
function groupBy&lt;TData extends RowData&gt;(rows: Row&lt;TData&gt;[], columnId: string) {
  const groupMap = new Map&lt;any, Row&lt;TData&gt;[]&gt;()
	
  return rows.reduce((map, row) =&gt; {
    const resKey = `${row.getValue(columnId)}`
    const previous = map.get(resKey)
    if (!previous) {
      map.set(resKey, [row])
    } else {
      previous.push(row)
    }
    return map
  }, groupMap)
}
```

Can you spot the difference? It’s this one simple change:

```ts
previous.push(row)
```

`previous.push(row)` mutates the existing array and brings our time down from 10 seconds per `groupBy` down to around 10ms, 1000x faster than the original code 🚀.

The takeaway here is that while spread is an expressive and convenient language feature, it’s important to see it for what it is at its core: a `for` loop. If you see a spread inside of a loop, you’ll want to rewrite it if there is any chance it will operate on a large set of values.

### Source code
You can see the full PR here [https://github.com/TanStack/table/pull/4495](https://github.com/TanStack/table/pull/4495). Because it was such a small fix, the majority of the PR is just a spec to guard against a future performance regression.

And here’s the [source code](https://replit.com/@JPCamara/Tanstack-GroupBy-Performance?s=app) for each attempt, runnable on the Replit platform. If you run it there the timings are much slower because it is running a lower powered CPU than my local machine, but as a relative measure the results are consistent with the numbers in this post.

[^1]:	I also find it can be hard to get a great sense for a profile when profiling react code since certain internal react code paths are hit so often the results are tough to decipher

[^2]:	It’s non standard, but in the future I need to remember to reach for `console.profile` as well.

    That could have been helpful here for profiling smaller blocks of code rather than deciphering the whole React render stack.

    Considering how slow the code was it still may not have been usable anyways.

    You can learn more about it here: [https://developer.mozilla.org/en-US/docs/Web/API/console/profile](https://developer.mozilla.org/en-US/docs/Web/API/console/profile)

[^3]:	What I’ve found is that Javascript engines have gotten so optimized that a `forEach`/`map`/`reduce` call can be as efficient as a hand written `for` loop. They’re almost never the performance problem in Javascript, least of all in a large set of iterations where the JIT can aggressively optimize.

[^4]:	I should note that the performance was at its worst for poorly distributed groupings. By that I mean if you had a grouped column with only one value then the grouping is concentrated into one key with all 50,000 values. If there were lots of unique values in the column the arrays were smaller and built more quickly

[^5]:	The v8 internals are, unsurprisingly, very complicated. Logic for spread seems to exist in a variety of places because it’s such a core feature so I wasn’t able to track down a specific implementation

[^6]:	If “Big O” complexity doesn’t mean anything to you, there’s lots of great resources online for it and it’s a great concept to be familiar with. The basic idea is it helps you identify how costly a particular piece of code will be to run.

    For a deep dive into it, [https://frontendmasters.com/courses/algorithms/](https://frontendmasters.com/courses/algorithms/) is a great free resource 

[^7]:	In terms of algorithmic complexity, O(n^2) and O(n^2/2) are considered equivalent, since you generally drop the constant (in this case, 1/2).

    But from a practical perspective, if you’re going to have a slow algorithm you’d still rather have it be half as slow as it’s full potential slowness 😅
</source:markdown>
    </item>
    
    <item>
      <title>Kicking the social media habit with “one sec”</title>
      <link>https://jpcamara.com/2023/01/31/kicking-the-social.html</link>
      <pubDate>Thu, 09 Feb 2023 17:30:54 -0500</pubDate>
      
      <guid>http://jpcamara.micro.blog/2023/01/31/kicking-the-social.html</guid>
      <description>&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/fc557bc79e.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Twitter. Oh how I could stroll that infinite corridor of information.&lt;/p&gt;
&lt;p&gt;Ever since having kids my personal time has been squeezed into a tiny ball. Still, I’d finish putting the kids to bed, open my phone, and where would I end up? On Twitter for 45 minutes. An hour. Hour 30. I’d sit there feeding whatever meager morsels of free time I had left to that tiny blue bird, it gobbling them up like some kind of attention soaked black oil sunflower seeds&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Twitter was my go to. I could certainly get lost on Instagram, or TikTok. But something about those platforms felt more wasteful. I wasn’t doing anything “worthwhile” there. Being on Twitter I could create warped justifications that I was actually learning things from people. Many times I was. But it was just as easy to get lost in pointless wanderings, sucked into controversy I didn’t care about 5 minutes earlier. And even when I learned something, was it best use of my time?&lt;/p&gt;
&lt;p&gt;During one of these info dump excursions, I saw a tweet about an app called “&lt;a href=&#34;https://one-sec.app&#34;&gt;one sec&lt;/a&gt;”. It got me curious - taking a deep breathe, delaying distracting apps&amp;hellip; adding friction?&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/1757e85a0d.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;-isnt-that-basically-screen-time&#34;&gt;&amp;hellip; isn’t that basically Screen Time?&lt;/h2&gt;
&lt;p&gt;It sounded interesting, but was it really any different from screen time limits?&lt;/p&gt;
&lt;p&gt;I have had screen time limits on social media since that feature first released on iOS. At worst it is a minor annoyance that I hardly notice myself dismissing. At best it occasionally saves me after already wandering aimlessly for ages.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/74c1e8b95c.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;My rock solid screen time workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You’ve reached your limit!&lt;/li&gt;
&lt;li&gt;tap “Remind me in 15 minutes”&lt;/li&gt;
&lt;li&gt;You’ve reached your limit!&lt;/li&gt;
&lt;li&gt;tap for 15 more&lt;/li&gt;
&lt;li&gt;You’ve reached your limit!&lt;/li&gt;
&lt;li&gt;Ugh, I just want to finish reading one thing - I can probably do it in 1 minute?&lt;/li&gt;
&lt;li&gt;You’ve reached your limit!&lt;/li&gt;
&lt;li&gt;&amp;hellip;give up and tap “Ignore limit for today”&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what about “one sec” was going to make that any different?&lt;/p&gt;
&lt;p&gt;The difference I found with “one sec” is specifically related to what they advertise: &lt;strong&gt;&lt;em&gt;added friction&lt;/em&gt;&lt;/strong&gt;. In full they describe it as “added friction makes distracting apps less appealing.”&lt;/p&gt;
&lt;p&gt;Screen time does not make apps less appealing. Screen time is almost completely frictionless. How much friction does quickly tapping a screen cause? I’ve extended my screen time so many times over the years I probably could tap each option by muscle memory.&lt;/p&gt;
&lt;p&gt;“One sec” is &lt;em&gt;all&lt;/em&gt; friction. Here’s an example of what it’s Iike when you try to open an app connected to “one sec”:&lt;/p&gt;
&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/1f0f8c7e10.gif&#34; width=&#34;323&#34; height=&#34;700&#34; alt=&#34;&#34; /&gt;
&lt;p&gt;That’s actually sped up a bit - the default is a 6-10 second animation. See how you are forced to sit and wait? That waiting is the key. It’s the friction they describe. I literally &lt;em&gt;cannot&lt;/em&gt; get into that app any faster, and then I still have to decide whether I move forward once I finish waiting.&lt;/p&gt;
&lt;p&gt;You sit there for 10 seconds - it’s intentionally aggravating, and it forces you to question how committed you are to opening the app. Does it really matter to me?&lt;/p&gt;
&lt;p&gt;Here’s my new workflow with “one sec”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open app&lt;/li&gt;
&lt;li&gt;“One sec” pops open&lt;/li&gt;
&lt;li&gt;Take a deep breath. Wait for the screen to reveal my options&lt;/li&gt;
&lt;li&gt;Phew, still waiting&lt;/li&gt;
&lt;li&gt;This is only 10 seconds? Do I really want to open the app this badly?&lt;/li&gt;
&lt;li&gt;Ok, moment of truth. Close or continue on?&lt;/li&gt;
&lt;li&gt;Here I usually just drop out, but sometimes I “Continue to ‘app’”. Now I still have to make one more decision. What’s the reason I’m using it?&lt;/li&gt;
&lt;li&gt;Not worth it, I’m out 👋🏼&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/89d2edbf86.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;For the rare times I actually stick around, I love the intention prompt. If the initial friction of waiting isn’t enough, here is one more layer of confronting my habit. You have a base set of intentions, and I’ve created a custom prompt for the one thing I ever do anymore on Instagram.&lt;/p&gt;
&lt;p&gt;I don’t remember if this was on by default, but if it isn’t you should absolutely enable it through “Intention Tracking”&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/505cbf1b28.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;is-there-anything-more-to-it-than-friction&#34;&gt;Is there anything more to it than friction?&lt;/h2&gt;
&lt;p&gt;Why would friction be so much more effective than screen time limits? Why would it be more powerful than just willing yourself to leave social media?&lt;/p&gt;
&lt;p&gt;This excerpt from James Clear’s “Atomic Habits” sums it up really well:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://jamesclear.com/power-of-environment&#34;&gt;Your environment is more important than your willpower&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is important to remember that the environment drives our good behaviors as well as our bad ones. People who seem to stick to good habits with ease are often benefitting from an environment that makes those behaviors easier.&lt;/p&gt;
&lt;p&gt;Atomic Habits - Chapter 12&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;“One sec” &lt;em&gt;changes&lt;/em&gt; your environment. Your typical phone environment is every app instantly available with a tap. With “one sec”, you are forced to evaluate that tap instead of mindlessly hopping around.&lt;/p&gt;
&lt;p&gt;“One sec” has also been analyzed in two different academic studies about its impact on social media usage:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://one-sec.app/max-planck-study/&#34;&gt;Social media usage cut in half through “one sec” intervention&lt;/a&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3 id=&#34;friction-causes-behavioral-change&#34;&gt;Friction causes behavioral change&lt;/h3&gt;
&lt;p&gt;By taking out the instant gratification, the urge for unintentional social media usage fades away over time: user’s brain now connects opening Instagram with something unpleasant, having to wait 10 seconds during the breathing intervention.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The takeaway: instant gratification (tap in and space out) is replaced with an unpleasant experience (waiting and being forced to acknowledge your own motivators). You are actually &lt;em&gt;re-wiring&lt;/em&gt; your brain through this change. I can attest to that - the thought of opening apps and waiting through the “one sec” interstitial is pretty unpleasant, and stops me before I even consider opening them.&lt;/p&gt;
&lt;h2 id=&#34;how-do-i-use-it&#34;&gt;How do I use it?&lt;/h2&gt;
&lt;p&gt;For such a powerful app, the setup is a bit clunky.&lt;/p&gt;
&lt;p&gt;As far as I can tell this isn’t “one sec”s fault - it’s likely not possible to block one app with another app just by having it installed. If it was, you could completely block a persons phone by convincing them to download your app.&lt;/p&gt;
&lt;p&gt;To enable “one sec” you need the &lt;a href=&#34;https://apps.apple.com/us/app/one-sec-delay-distracting-apps/id1532875441&#34;&gt;one sec&lt;/a&gt; app and the &lt;a href=&#34;https://apps.apple.com/us/app/shortcuts/id1462947752&#34;&gt;Shortcuts&lt;/a&gt; app. You create a personal automation that triggers “one sec” any time you go to open a specific application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/062785fb14.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;As of writing this I’m up to 8 automations connecting to “one sec”:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/06763c912f.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The biggest benefit you’ll get out of it is by using the “pro” version, which currently costs me about $16/yr. It’s absolutely worth it, but you can also use the free version to try out the behavior against a single app and see how you like it.&lt;/p&gt;
&lt;p&gt;“One sec” displays the number of times it’s prevented you from opening each app, and estimates time saved. It’s interesting to analyze preventions, though it’s saved me way more time than it takes credit for.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/4957b9d917.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://one-sec.app&#34;&gt;one sec site&lt;/a&gt; has great documentation, and the app offers videos on setup, so you can read and watch those once you download it.&lt;/p&gt;
&lt;h2 id=&#34;what-ive-been-doing-instead&#34;&gt;What I’ve been doing instead&lt;/h2&gt;
&lt;p&gt;The clearest change for me is that I’m even writing this. It’s been years since I’ve written anything, and I’ve invested some of my time into a new writing workflow.&lt;/p&gt;
&lt;p&gt;As well I’ve been learning a new programming language, reading books, exercising, and just generally formulating uses of my time that align with my values.&lt;/p&gt;
&lt;p&gt;Social media, even when not all consuming, is a time suck. You &lt;em&gt;probably&lt;/em&gt; have alternatives that would make you happier if only you left these platforms&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Oh and I’ve been &lt;strong&gt;sleeping&lt;/strong&gt; better. I have less options to distract me if I wake up at night - it’s a beautiful thing.&lt;/p&gt;
&lt;h2 id=&#34;throwing-the-baby-out-with-the-bath-water5&#34;&gt;Throwing the baby out with the bath water&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/h2&gt;
&lt;p&gt;Social media may be a huge time suck, but it’s not 100% useless. I think most people find some slices of learning, inspiration and connection on it somewhere.&lt;/p&gt;
&lt;p&gt;For me, Twitter was often a valuable learning resource. It was a useful tool for keeping up with technology trends that matter to me personally and professionally. Unfortunately, I can’t disentangle the good (learning) from the bad (aimless wandering). Social media is designed to suck us in and keep us there as long as possible - I can’t fight that programming, I can only avoid it.&lt;/p&gt;
&lt;p&gt;As a way to keep up with folks I follow without using Twitter directly, I’ve been using &lt;a href=&#34;https://mailbrew.com&#34;&gt;Mailbrew&lt;/a&gt;. It allows me to setup a digest based on my timeline and I can also choose specific Twitter handles to keep up with. It doesn’t recreate the experience of spontaneous discovery, but that means it also doesn’t trigger the accompanying &lt;a href=&#34;https://rationalwiki.org/wiki/Variable_ratio&#34;&gt;variable ratio schedule&lt;/a&gt;&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt; with it.&lt;/p&gt;
&lt;h2 id=&#34;going-even-deeper-with-one-sec&#34;&gt;Going even deeper with “one sec”&lt;/h2&gt;
&lt;p&gt;In writing about “one sec”, I’ve dug into the app quite a bit. I’ve found that “one sec” is deeply configurable and filled with additional features. Blocking apps was my main draw but it has a variety of other features including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blocking websites with a safari extension&lt;/li&gt;
&lt;li&gt;Hooking into focus modes to completely block apps during those times&lt;/li&gt;
&lt;li&gt;Starting a “block session” in the app which blocks every app and website you have configured&lt;/li&gt;
&lt;li&gt;Triggering “don’t get lost” notifications if you’ve gone through to an app and have been there for awhile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;“One sec” can do even more and I’ve found myself expanding not only what it enables but also what apps I apply it to. I now have periods where I apply “one sec” to email, slack and web browsers. I have my &lt;a href=&#34;https://tutorials.one-sec.app/focus-filters&#34;&gt;sleep focus&lt;/a&gt; setup to completely block them so I can’t wake up in the middle of the night and just hop over to bad habits.&lt;/p&gt;
&lt;p&gt;It’s starting to feel like a distraction reducing superpower.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;Eventually, having subjected all apps on my phone to triggering “one sec”, I will finally setup “one sec” to trigger “one sec” whenever it opens. This will likely initiate an infinite loop that causes my phone to melt completely&lt;sup id=&#34;fnref:7&#34;&gt;&lt;a href=&#34;#fn:7&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;In this highly probable scenario, maybe I’ll just switch back to a flip phone?&lt;/p&gt;
&lt;p&gt;More likely I’ll buy a smartphone again&amp;hellip; but I know the first app I install will be “one sec”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/cddf478f3a.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I suppose I’m being presumptuous in assuming the Twitter bird likes black oil sunflower seeds. The birds in my yard go wild for the stuff, so seems like a strong likelihood&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I do wonder if screen limits are even meant to_actually_ stop you, or just check off a box on device usability for Apple. All screen time tracking usually does is make you think “wow.. I use this phone too much”. There’s no behavior change. Information in isolation does not change behavior.&lt;/p&gt;
&lt;p&gt;This is a bit like mandates to make it clearer to consumers what the nutrition facts are for different foods so people can make “informed decisions”. This helps some people do that, but for most people it’s meaningless information that they completely ignore (or notice, but eat/drink it anyways and just feel bad about it later).&lt;/p&gt;
&lt;p&gt;It’s highly anecdotal but I’ve never met &lt;em&gt;anyone&lt;/em&gt; who found screen limits to be helpful.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;This is from the “one sec” blog, which is a good read in general.&lt;/p&gt;
&lt;p&gt;Honestly some of their blog post illustrations are fantastic - much nicer than I expected such a utilitarian app to have. The referenced post in particular looks great, so here it is again &lt;a href=&#34;https://one-sec.app/max-planck-study/&#34;&gt;https://one-sec.app/max-planck-study/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;If not, then get your social media on! Who am I to judge if it makes you genuinely happy?&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:5&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;The sentiment is right, but figures of speech are so weird. Also I like to think no one has ever actually done this 😰&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:6&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://rationalwiki.org/wiki/Variable_ratio&#34;&gt;https://rationalwiki.org/wiki/Variable_ratio&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; this tracks. Essentially using Twitter for me is like using a slot machine. I get hits of value, but on a variable schedule. Because I don’t know when the next hit will occur, but I know it probably &lt;em&gt;will&lt;/em&gt;, I keep going indefinitely.&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:7&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;I am genuinely curious whether it’s possible and if apple or the “one sec” developer have handled it but I’m too scared to try it 😅 I know my phone won’t &lt;em&gt;melt&lt;/em&gt;, but could I crash it?&amp;#160;&lt;a href=&#34;#fnref:7&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
      <source:markdown>![](https://cdn.uploads.micro.blog/98548/2023/fc557bc79e.jpg)

Twitter. Oh how I could stroll that infinite corridor of information.

Ever since having kids my personal time has been squeezed into a tiny ball. Still, I’d finish putting the kids to bed, open my phone, and where would I end up? On Twitter for 45 minutes. An hour. Hour 30. I’d sit there feeding whatever meager morsels of free time I had left to that tiny blue bird, it gobbling them up like some kind of attention soaked black oil sunflower seeds[^1].

Twitter was my go to. I could certainly get lost on Instagram, or TikTok. But something about those platforms felt more wasteful. I wasn’t doing anything “worthwhile” there. Being on Twitter I could create warped justifications that I was actually learning things from people. Many times I was. But it was just as easy to get lost in pointless wanderings, sucked into controversy I didn’t care about 5 minutes earlier. And even when I learned something, was it best use of my time?

During one of these info dump excursions, I saw a tweet about an app called “[one sec](https://one-sec.app)”. It got me curious - taking a deep breathe, delaying distracting apps... adding friction? 

![](https://cdn.uploads.micro.blog/98548/2023/1757e85a0d.jpg)

## ... isn’t that basically Screen Time?
It sounded interesting, but was it really any different from screen time limits?

I have had screen time limits on social media since that feature first released on iOS. At worst it is a minor annoyance that I hardly notice myself dismissing. At best it occasionally saves me after already wandering aimlessly for ages.

![](https://cdn.uploads.micro.blog/98548/2023/74c1e8b95c.jpg)

My rock solid screen time workflow:

- You’ve reached your limit!
- tap “Remind me in 15 minutes”
- You’ve reached your limit!
- tap for 15 more
- You’ve reached your limit!
- Ugh, I just want to finish reading one thing - I can probably do it in 1 minute?
- You’ve reached your limit!
- ...give up and tap “Ignore limit for today”[^2]

So what about “one sec” was going to make that any different?

The difference I found with “one sec” is specifically related to what they advertise: **_added friction_**. In full they describe it as “added friction makes distracting apps less appealing.” 

Screen time does not make apps less appealing. Screen time is almost completely frictionless. How much friction does quickly tapping a screen cause? I’ve extended my screen time so many times over the years I probably could tap each option by muscle memory.

“One sec” is _all_ friction. Here’s an example of what it’s Iike when you try to open an app connected to “one sec”:

&lt;img src=&#34;https://cdn.uploads.micro.blog/98548/2023/1f0f8c7e10.gif&#34; width=&#34;323&#34; height=&#34;700&#34; alt=&#34;&#34; /&gt;

That’s actually sped up a bit - the default is a 6-10 second animation. See how you are forced to sit and wait? That waiting is the key. It’s the friction they describe. I literally _cannot_ get into that app any faster, and then I still have to decide whether I move forward once I finish waiting. 

You sit there for 10 seconds - it’s intentionally aggravating, and it forces you to question how committed you are to opening the app. Does it really matter to me?

Here’s my new workflow with “one sec”:

- Open app
- “One sec” pops open
- Take a deep breath. Wait for the screen to reveal my options
- Phew, still waiting
- This is only 10 seconds? Do I really want to open the app this badly?
- Ok, moment of truth. Close or continue on?
- Here I usually just drop out, but sometimes I “Continue to ‘app’”. Now I still have to make one more decision. What’s the reason I’m using it?
- Not worth it, I’m out 👋🏼 

![](https://cdn.uploads.micro.blog/98548/2023/89d2edbf86.jpg)

For the rare times I actually stick around, I love the intention prompt. If the initial friction of waiting isn’t enough, here is one more layer of confronting my habit. You have a base set of intentions, and I’ve created a custom prompt for the one thing I ever do anymore on Instagram.

I don’t remember if this was on by default, but if it isn’t you should absolutely enable it through “Intention Tracking”

![](https://cdn.uploads.micro.blog/98548/2023/505cbf1b28.jpg)

## Is there anything more to it than friction?
Why would friction be so much more effective than screen time limits? Why would it be more powerful than just willing yourself to leave social media?

This excerpt from James Clear’s “Atomic Habits” sums it up really well:

[Your environment is more important than your willpower](https://jamesclear.com/power-of-environment)

&gt; It is important to remember that the environment drives our good behaviors as well as our bad ones. People who seem to stick to good habits with ease are often benefitting from an environment that makes those behaviors easier.
&gt; 
&gt; Atomic Habits - Chapter 12

“One sec” _changes_ your environment. Your typical phone environment is every app instantly available with a tap. With “one sec”, you are forced to evaluate that tap instead of mindlessly hopping around.

“One sec” has also been analyzed in two different academic studies about its impact on social media usage:

[Social media usage cut in half through “one sec” intervention](https://one-sec.app/max-planck-study/)[^3]

&gt; ### Friction causes behavioral change
&gt; By taking out the instant gratification, the urge for unintentional social media usage fades away over time: user’s brain now connects opening Instagram with something unpleasant, having to wait 10 seconds during the breathing intervention.

The takeaway: instant gratification (tap in and space out) is replaced with an unpleasant experience (waiting and being forced to acknowledge your own motivators). You are actually _re-wiring_ your brain through this change. I can attest to that - the thought of opening apps and waiting through the “one sec” interstitial is pretty unpleasant, and stops me before I even consider opening them.

## How do I use it?
For such a powerful app, the setup is a bit clunky.

As far as I can tell this isn’t “one sec”s fault - it’s likely not possible to block one app with another app just by having it installed. If it was, you could completely block a persons phone by convincing them to download your app.

To enable “one sec” you need the [one sec](https://apps.apple.com/us/app/one-sec-delay-distracting-apps/id1532875441) app and the [Shortcuts](https://apps.apple.com/us/app/shortcuts/id1462947752) app. You create a personal automation that triggers “one sec” any time you go to open a specific application. 

![](https://cdn.uploads.micro.blog/98548/2023/062785fb14.jpg)

As of writing this I’m up to 8 automations connecting to “one sec”:

![](https://cdn.uploads.micro.blog/98548/2023/06763c912f.jpg)

The biggest benefit you’ll get out of it is by using the “pro” version, which currently costs me about $16/yr. It’s absolutely worth it, but you can also use the free version to try out the behavior against a single app and see how you like it.

“One sec” displays the number of times it’s prevented you from opening each app, and estimates time saved. It’s interesting to analyze preventions, though it’s saved me way more time than it takes credit for.

![](https://cdn.uploads.micro.blog/98548/2023/4957b9d917.jpg)

The [one sec site](https://one-sec.app) has great documentation, and the app offers videos on setup, so you can read and watch those once you download it.

## What I’ve been doing instead 
The clearest change for me is that I’m even writing this. It’s been years since I’ve written anything, and I’ve invested some of my time into a new writing workflow.

As well I’ve been learning a new programming language, reading books, exercising, and just generally formulating uses of my time that align with my values. 

Social media, even when not all consuming, is a time suck. You _probably_ have alternatives that would make you happier if only you left these platforms[^4]. 

Oh and I’ve been **sleeping** better. I have less options to distract me if I wake up at night - it’s a beautiful thing.

## Throwing the baby out with the bath water[^5]
Social media may be a huge time suck, but it’s not 100% useless. I think most people find some slices of learning, inspiration and connection on it somewhere.

For me, Twitter was often a valuable learning resource. It was a useful tool for keeping up with technology trends that matter to me personally and professionally. Unfortunately, I can’t disentangle the good (learning) from the bad (aimless wandering). Social media is designed to suck us in and keep us there as long as possible - I can’t fight that programming, I can only avoid it.

As a way to keep up with folks I follow without using Twitter directly, I’ve been using [Mailbrew](https://mailbrew.com). It allows me to setup a digest based on my timeline and I can also choose specific Twitter handles to keep up with. It doesn’t recreate the experience of spontaneous discovery, but that means it also doesn’t trigger the accompanying [variable ratio schedule](https://rationalwiki.org/wiki/Variable_ratio)[^6] with it.

## Going even deeper with “one sec”
In writing about “one sec”, I’ve dug into the app quite a bit. I’ve found that “one sec” is deeply configurable and filled with additional features. Blocking apps was my main draw but it has a variety of other features including:

- Blocking websites with a safari extension
- Hooking into focus modes to completely block apps during those times
- Starting a “block session” in the app which blocks every app and website you have configured
- Triggering “don’t get lost” notifications if you’ve gone through to an app and have been there for awhile

“One sec” can do even more and I’ve found myself expanding not only what it enables but also what apps I apply it to. I now have periods where I apply “one sec” to email, slack and web browsers. I have my [sleep focus](https://tutorials.one-sec.app/focus-filters) setup to completely block them so I can’t wake up in the middle of the night and just hop over to bad habits.

It’s starting to feel like a distraction reducing superpower.

## Next steps
Eventually, having subjected all apps on my phone to triggering “one sec”, I will finally setup “one sec” to trigger “one sec” whenever it opens. This will likely initiate an infinite loop that causes my phone to melt completely[^7]. 

In this highly probable scenario, maybe I’ll just switch back to a flip phone? 

More likely I’ll buy a smartphone again... but I know the first app I install will be “one sec”.

![](https://cdn.uploads.micro.blog/98548/2023/cddf478f3a.png)


[^1]:	I suppose I’m being presumptuous in assuming the Twitter bird likes black oil sunflower seeds. The birds in my yard go wild for the stuff, so seems like a strong likelihood

[^2]:	I do wonder if screen limits are even meant to_actually_ stop you, or just check off a box on device usability for Apple. All screen time tracking usually does is make you think “wow.. I use this phone too much”. There’s no behavior change. Information in isolation does not change behavior.

    This is a bit like mandates to make it clearer to consumers what the nutrition facts are for different foods so people can make “informed decisions”. This helps some people do that, but for most people it’s meaningless information that they completely ignore (or notice, but eat/drink it anyways and just feel bad about it later).

    It’s highly anecdotal but I’ve never met _anyone_ who found screen limits to be helpful.

[^3]:	This is from the “one sec” blog, which is a good read in general.

    Honestly some of their blog post illustrations are fantastic - much nicer than I expected such a utilitarian app to have. The referenced post in particular looks great, so here it is again [https://one-sec.app/max-planck-study/](https://one-sec.app/max-planck-study/)

[^4]:	If not, then get your social media on! Who am I to judge if it makes you genuinely happy?

[^5]:	The sentiment is right, but figures of speech are so weird. Also I like to think no one has ever actually done this 😰

[^6]:	[https://rationalwiki.org/wiki/Variable\_ratio](https://rationalwiki.org/wiki/Variable_ratio)

    I _think_ this tracks. Essentially using Twitter for me is like using a slot machine. I get hits of value, but on a variable schedule. Because I don’t know when the next hit will occur, but I know it probably _will_, I keep going indefinitely.

[^7]:	I am genuinely curious whether it’s possible and if apple or the “one sec” developer have handled it but I’m too scared to try it 😅 I know my phone won’t _melt_, but could I crash it?
</source:markdown>
    </item>
    
  </channel>
</rss>
