<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Steven Harman</title>
    <description>It Depends™, as a Service. Maker &amp; breaker of things. Software spelunker. Good at naps. he/him</description>
    <link>https://stevenharman.net/</link>
    <atom:link href="https://stevenharman.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 12 Feb 2026 17:22:01 +0000</pubDate>
    <lastBuildDate>Thu, 12 Feb 2026 17:22:01 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Configuring SSH Keys for Multiple GitHub Accounts</title>
        <description>&lt;p&gt;Managing different &lt;abbr title=&quot;Secure Shell&quot;&gt;SSH&lt;/abbr&gt; Keys for different Hosts is well-understood.
Different keys for the same host (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt;), based on which GitHub Organization we’re working with, that’s… a bit trickier.
But, we have the technology!&lt;/p&gt;

&lt;p&gt;We’re going to configure our SSH and Git clients to seamlessly switch SSH keys between our personal GitHub Account and our GitHub Enterprise Cloud, &lt;abbr title=&quot;Enterprise Managed User&quot;&gt;EMU&lt;/abbr&gt; Account.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/futurama-403-blue-key.webp&quot; alt=&quot;Futurama character Fry and a soldier standing outside a &apos;War Room&apos; door. A screen on the door has the words &apos;YOU NEED A BLUE KEY&apos;. The soldier is holding up a comically large blue key.&quot; title=&quot;You need the right key.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;You need the right key.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;!-- more --&gt;
&lt;h2 id=&quot;-typical-multi-key-setup&quot;&gt;🔐 Typical Multi-Key Setup&lt;/h2&gt;

&lt;p&gt;Most guides and information we’ll find on the Internet about managing multiple SSH keys focus on multiple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt; values.
As in, use key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;A&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.com&lt;/code&gt; and key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;B&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stevenharman.net&lt;/code&gt;.
A simplified &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ssh/config&lt;/code&gt; for that might look something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host example.com
  IdentityFile ~/.ssh/id_a_ed25519

Host stevenharman.net
  IdentityFile ~/.ssh/id_b_ed25519
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;-different-hostnames-different-keys&quot;&gt;🤹 Different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HostName&lt;/code&gt;s, Different Keys&lt;/h2&gt;

&lt;p&gt;But what happens when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HostName&lt;/code&gt;, i.e., the real host name that will be used for the SSH connection, for both is the same?
For example, when we have a personal GitHub Account (for Open Source work, some private repositories, and maybe even some repos belonging to other GitHub Organizations), and second &lt;a href=&quot;https://docs.github.com/en/enterprise-cloud@latest/admin/overview/about-github-enterprise-cloud&quot; title=&quot;About GitHub Enterprse Cloud&quot;&gt;GitHub Enterprise Cloud&lt;/a&gt; Account provisioned by our employer.&lt;/p&gt;

&lt;p&gt;For brevity, let’s say our personal GitHub Account is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry&lt;/code&gt;.
And our work for &lt;em&gt;Planet Express, Inc.&lt;/em&gt;, has a GitHub Enterprise Cloud instance, and provisioned us a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry_plnx&lt;/code&gt; &lt;a href=&quot;https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/about-enterprise-managed-users&quot; title=&quot;About Enterprise managed Users&quot;&gt;Enterprise Managed Users&lt;/a&gt; Account.&lt;/p&gt;

&lt;p&gt;In both cases, the SSH &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HostName&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt;.
We might try a configuration like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_ed25519

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_plnx_ed25519
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This configuration will work for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; talking to GitHub for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry&lt;/code&gt; Account, because it is matched first.
But when working with a repository for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry_plnx&lt;/code&gt; Account, the first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host github.com&lt;/code&gt; entry matches, and uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id_fry_ed25519&lt;/code&gt; SSH Key.
However, that key is not associated with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry_plnx&lt;/code&gt; Account on GitHub and so… we see errors.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;git clone git@github.com:planet-express/delivery_service.git
&lt;span class=&quot;go&quot;&gt;  ERROR: Repository not found.
  fatal: Could not read from remote repository.
  
  Please make sure you have the correct access rights
  and the repository exists.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;-use-different-host-values&quot;&gt;🤡 Use Different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt; values&lt;/h2&gt;

&lt;p&gt;At this point, most articles on the Internet will tell us to &lt;a href=&quot;https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-personal-account/managing-multiple-accounts#contributing-to-multiple-accounts-using-ssh-and-git_ssh_command&quot; title=&quot;Contributing to multiple accounts using SSH and GIT_SSH_COMMAND&quot;&gt;override the SSH command used&lt;/a&gt; to connect, or (&lt;abbr title=&quot;In My Opinion&quot;&gt;IMO&lt;/abbr&gt;, slightly better) &lt;a href=&quot;https://developer.1password.com/docs/ssh/agent/advanced#use-multiple-github-accounts&quot; title=&quot;SSH Agent: Use multiple GitHub accounts&quot;&gt;use different &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt; values&lt;/a&gt; for each.
The later would look something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_ed25519

Host github-plnx
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_fry_plnx_ed25519
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A big downside to this technique is that any time we’re cloning a new repository for our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fry_plnx&lt;/code&gt; Account, we’ll need to adjust the &lt;abbr title=&quot;Uniform Resource Locator&quot;&gt;URL&lt;/abbr&gt; used.
Meaning we can no longer copy/pasta the command from the GitHub &lt;abbr title=&quot;User Interface&quot;&gt;UI&lt;/abbr&gt;.
Nor rely on muscle memory we’ve built up over years of using our personal Account.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;Instead of the actual URL
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;git clone git@github.com:planet-express/delivery_service.git
&lt;span class=&quot;go&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;Substitue &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;our custom Host value &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;the &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;github.com&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; part
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;git clone git@github-plnx:planet-express/delivery_service.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And this will work!
But we have to remember to manually substitute those values for each repository we clone.
It’s toil.
And I detest toil.&lt;/p&gt;

&lt;h2 id=&quot;-automate-substituting-the-host&quot;&gt;🤖 Automate Substituting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;I happened &lt;a href=&quot;https://www.kenmuse.com/blog/ssh-and-multiple-git-credentials/#git&quot; title=&quot;SSH and Multiple Git Credentials&quot;&gt;across an article&lt;/a&gt; using &lt;a href=&quot;https://www.git-scm.com/docs/git-config#Documentation/git-config.txt-urlltbasegtinsteadOf&quot;&gt;Git’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url.&amp;lt;base&amp;gt;.insteadOf&lt;/code&gt; variable&lt;/a&gt; to swap URLs.
We can use that to teach Git about our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host github-plnx&lt;/code&gt; entry, and have it auto-magically swap &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt; back in when talking over SSH to GitHub.
We’ll add this to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# See custom `Host github-plnx` in ~/.ssh/config
[url &quot;github-plnx:planet-express&quot;]
  insteadOf = git@github.com:planet-express
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;planet-express&lt;/code&gt; GitHub Organization name in the match for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; variable, Git will alter the URL &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git@github.com:planet-express/delivery_service.git&lt;/code&gt; by matching &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com:planet-express&lt;/code&gt; and swapping in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-plnx:planet-express&lt;/code&gt;.
Then the SSH config matches the host &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-plnx&lt;/code&gt;, and using that matched config, replaces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-plnx&lt;/code&gt; with the actual user (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt;) and host name (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com&lt;/code&gt;).
At which point we’re back to the original URL.
🎉 NEAT!&lt;/p&gt;

&lt;p&gt;With that in place, Git commands for repositories under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github.com/planet-express&lt;/code&gt; will use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id_fry_plnx_ed25519&lt;/code&gt; SSH key.
Any other repositories will fail to match the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-plnx&lt;/code&gt; host, thus using the regular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Host github.com&lt;/code&gt; SSH Config, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id_fry_ed25519&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE: We’d need to add a similar entry for other Organizations in the GitHub Enterprise Cloud instance that we need to work with.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;-keeping-gitconfig-clean&quot;&gt;🧹 Keeping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitconfig&lt;/code&gt; Clean&lt;/h3&gt;

&lt;p&gt;If you’ve &lt;a href=&quot;https://github.com/stevenharman/config?tab=readme-ov-file#my-config-files&quot; title=&quot;Steven Harman&apos;s dotfiles&quot;&gt;got your dotfiles in Git&lt;/a&gt;, you might want to keep employer and/or machine-specific configuration out of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; file.
For example, our work machine needs to know about the custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url &quot;github-plnx:planet-express&quot;&lt;/code&gt; Git config, but our personal MacBook likely doesn’t!
Or maybe we don’t want to, or cannot, expose every GitHub Enterprise Cloud Organizations we’re a part of in our dotfiles repo.&lt;/p&gt;

&lt;p&gt;Luckily, we can leverage &lt;a href=&quot;https://www.git-scm.com/docs/git-config#_includes&quot; title=&quot;Git Config: include and includeIf&quot;&gt;Git’s ability to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include&lt;/code&gt; other config files&lt;/a&gt; to keep such customizations out of Git.
We can adjust the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; file, which is in our dotfiles Git repo, to include another config file which is &lt;em&gt;NOT&lt;/em&gt; in Git.
Add the following to the bottom of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[include]
  path = ~/.gitconfig_custom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig_custom&lt;/code&gt; file isn’t present, Git just ignores the include directive.
As is the case on my personal MacBook.
😅 Phew!&lt;/p&gt;

&lt;p&gt;When it is present, Git will load the file, allowing us to override or add additional configuration.
Now, move the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[url …]&lt;/code&gt; configuration from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; into the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig_custom&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[url &quot;github-plnx:planet-express&quot;]
  insteadOf = git@github.com:planet-express
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And &lt;em&gt;voila!&lt;/em&gt;
We are switching SSH keys automatically, and our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.gitconfig&lt;/code&gt; is clean of any knowledge about the specific GitHub Organizations, etc…&lt;/p&gt;

</description>
        <pubDate>Wed, 21 Aug 2024 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/configure-ssh-keys-for-multiple-github-accounts</link>
        <guid isPermaLink="true">https://stevenharman.net/configure-ssh-keys-for-multiple-github-accounts</guid>
        
        <category>git</category>
        
        <category>github</category>
        
        <category>ssh</category>
        
        
      </item>
    
      <item>
        <title>On Flaky Tests, Time Precision, and Order Dependence</title>
        <description>&lt;p&gt;A flaky test is one that fails unpredictably, without a corresponding change in the code under test.
These often show up in &lt;abbr title=&quot;Continuous Integration&quot;&gt;CI&lt;/abbr&gt; runs where a test unrelated to any change made suddenly fails, and then mysteriously passes when re-run.
There are many type and causes of “flakes”.
Today I want to talk about flaky tests caused by time precision, and time-order dependence.&lt;/p&gt;

&lt;p&gt;The specific tools mentioned here will be Ruby, Rails, and PostgreSQL specific.
But analogs exist in nearly all other languages and frameworks, so the techniques are broadly applicable.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/not-sure-if-flaky-test.jpg&quot; alt=&quot;Frye meme: Not sure if I broke something, or it&apos;s a flaky test&quot; title=&quot;Not sure if I broke something, or it&apos;s a flaky test…&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Not sure if I broke something, or it&apos;s a flaky test…&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;!-- more --&gt;
&lt;h2 id=&quot;-freeze-time&quot;&gt;🥶 Freeze Time&lt;/h2&gt;

&lt;p&gt;Sometimes a test assumes that time will elapse (or &lt;em&gt;not&lt;/em&gt; elapse) during the execution of the test.
In such tests, when comparing time/timestamps, try to work with entirely predictable time.
Ideally by making time… umm… more static.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;https://rspec.info&quot; title=&quot;Behaviour Driven Development for Ruby. Making TDD Productive and Fun.&quot;&gt;RSpec&lt;/a&gt;, you can ensure that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; and friends always return the same value during a single test execution by &lt;em&gt;freezing&lt;/em&gt; time with &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-freeze_time&quot; title=&quot;ActiveSupport&apos;s #freeze_time helper&quot;&gt;Rails’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;freeze_time&lt;/code&gt; helper&lt;/a&gt;.
From the docs:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Sun, 09 Jul 2017 15:34:49 EST -05:00&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;freeze_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Sun, 09 Jul 2017 15:34:49 EST -05:00&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Sun, 09 Jul 2017 15:34:50 EST -05:00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I prefer to add a bit of RSpec metadata to do the magic. 🪄&lt;/p&gt;

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

&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;around&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_frozen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;freeze_time&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that, you can add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:time_frozen&lt;/code&gt; metadata to a wrapping &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; block:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;RSpec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_frozen&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Time is frozen here… 🥶&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now all references to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; and friends are frozen.
Time will not move forward inside that block.&lt;/p&gt;

&lt;p&gt;It’s important to know that any setup (like RSpec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;around&lt;/code&gt; blocks) happening before the time-frozen block did not have time frozen, and so can have slightly different values &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; than inside the block.
We’ll come back to this later. 😉&lt;/p&gt;

&lt;h2 id=&quot;️-platform-specific-time-precision&quot;&gt;⏱️ Platform-specific Time Precision&lt;/h2&gt;

&lt;p&gt;Ruby’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; objects have up to nanosecond precision.
That is, up to 9 significant digits after the decimal point.
e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.utc(2024, 5, 20, 0, 0, 0.123456789)&lt;/code&gt; would result in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time&lt;/code&gt; object with 123,456,789 nanoseconds of precision.&lt;/p&gt;

&lt;p&gt;However, the precision of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time.now&lt;/code&gt; (and friends) varies by platform.
On macOS, it’s limited to microseconds - 6 significant digits after the decimal point.
Linux tends to be nanoseconds.&lt;/p&gt;

&lt;p&gt;This usually doesn’t matter because any single test run happens on the same machine.
However, there’s a wrinkle: PostgreSQL.
Or, more generally, the precision of time-like data in your database.&lt;/p&gt;

&lt;p&gt;PostgreSQL’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;time&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;interval&lt;/code&gt; data types have a &lt;a href=&quot;https://www.postgresql.org/docs/current/datatype-datetime.html&quot; title=&quot;PostgreSQL Date/Time Types&quot;&gt;resolution of microseconds&lt;/a&gt;.
And MySQL has a similar &lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/date-and-time-type-syntax.html&quot; title=&quot;MySQL Date/Time Types&quot;&gt;limitation on precision&lt;/a&gt;.
So during a CI run, for example, it’s possible to do something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 2024-05-20 19:58:45.660142789 +0000&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;BlogPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BlogPost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;published_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Flakes b/c post.published_at lost resolution&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;published_at&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 2024-05-20 19:58:45.660142000 +0000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In my experience, the need for directly comparing to a time field from the database is rare.
But in those rare cases, I prefer to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#round&lt;/code&gt; the in-memory time to the same precision as the database column.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;published_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Another option is more fuzzy expectations/matching.
RSpec has &lt;a href=&quot;https://www.rubydoc.info/github/rspec/rspec-expectations/RSpec%2FMatchers%3Abe_within&quot; title=&quot;RSpec::Matchers#be_within&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;be_within&lt;/code&gt; matcher&lt;/a&gt;, for example.
Though I find this more confusing than helpful, and tend to avoid it.&lt;/p&gt;

&lt;h2 id=&quot;-time-order-dependent-tests&quot;&gt;🤹 Time-order Dependent Tests&lt;/h2&gt;

&lt;p&gt;In that contrived example it’s easy to see how we end up with mismatched precision.
A more tricky variation occurs when we create a bunch of test data and then make assertions on that data, based on the order of various time fields.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:published&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Generate Post&apos;s with #published_at = Time.now at time of creation&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;sorted_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sort_by&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:published_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reverse&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; sort the in-memory list, &quot;newest&quot; first&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RecentPostQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sorted_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Flakes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this case, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;posts&lt;/code&gt; collection was held in memory, with each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post#published_at&lt;/code&gt; value having full nanosecond precision.
When saving to PostgreSQL, the precision was reduced to microsecond.
Meaning it’s possible two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt;s were created fast enough that when sub-microsecond precision is lost, they have the “same” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#published_at&lt;/code&gt; in the database.
Sorting equal values is non-deterministic, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt; instances with the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#published_at&lt;/code&gt; can have different ordering within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;recent_posts&lt;/code&gt; collection.
And so… flaky test failures.&lt;/p&gt;

&lt;p&gt;A naive way to handle this would be to &lt;a href=&quot;https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-reload&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#reload&lt;/code&gt; the posts collection&lt;/a&gt; before sorting it in-memory.
Though that can result in the same problem of loss of precision, with the in-memory collection having non-deterministic ordering.&lt;/p&gt;

&lt;p&gt;A robust way to solve this is to use explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DateTime&lt;/code&gt; values when creating the test data.
After all, the ordering-by-time is actually the thing we’re trying to test.
Being more explicit about that in test setup helps our future selves understand which parts of setup are important, vs incidental.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;oldest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;middle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;day&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;newest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;published_at: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RecentPostQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;middle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oldest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; works every time!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;️-precision-of-frozen-time&quot;&gt;🥶⏱️ Precision of Frozen Time&lt;/h2&gt;

&lt;p&gt;Using Rails’ built in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#freeze_time&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#travel_to&lt;/code&gt;, etc… will result in all sub-second precision being lost, by default.
This can be surprising, or at least unexpected.
I know it surprised me!&lt;/p&gt;

&lt;p&gt;As with freezing time in general, this loss of precision usually doesn’t matter.
However, it can cause issues similar to those above where a loss of precision leads to non-deterministic ordering.
Consider this terribly made up example:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:published&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;with a draft post&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;before&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;freeze_time&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;includes a published draft as the newest Post&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish!&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; sets published_at = Time.now&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RecentPostsQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Flakes&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This can be tricky to spot.&lt;/p&gt;

&lt;p&gt;Here the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!(:post)&lt;/code&gt; happened before we froze time, so it might have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#published_at = 2024-05-20 0:0:0.123456000 +0000&lt;/code&gt;.
Then, a fraction of a second later (because computers are fast), we freeze time, create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draft&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#publish!&lt;/code&gt; it.
Since we know the creation and publication of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draft&lt;/code&gt; happens after the creation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post&lt;/code&gt;, this setup seems to make sense.&lt;/p&gt;

&lt;p&gt;Except frozen time has no sub-second precision.
So it’s possible that the&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draft&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;published_at = 2024-05-20 0:0:0.000000 +0000&lt;/code&gt;.
Which is a fraction of a second &lt;em&gt;before&lt;/em&gt; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;post&lt;/code&gt;’s, rather than after it.&lt;/p&gt;

&lt;p&gt;We might try reaching for our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:time_frozen&lt;/code&gt; RSpec helper here, relying on RSpec’s behavior to ensure all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; blocks (which include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let!&lt;/code&gt;) run before the actual test code (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; block).
And indeed that is the case.
But this is also subtly broken as now all of the times lack sub-second precision.
Meaning we’re back in the non-deterministic ordering problem.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;let!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:published&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;with a draft post&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:time_frozen&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FactoryBot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;includes a published draft as the newest Post&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;publish!&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; sets published_at = Time.now&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;RecentPostsQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;recent_posts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; Flakes b/c time&apos;s can be equal&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The solution, like before, is to be explicit about time values when ordering based on them is important to the test.&lt;/p&gt;

&lt;h3 id=&quot;preserving-sub-second-precision&quot;&gt;Preserving sub-second precision&lt;/h3&gt;

&lt;p&gt;With newer versions of Rails, sub-second precision can optionally be &lt;a href=&quot;https://github.com/rails/rails/blob/747a03ba7722b6f0a7ce42e86cea83cf07a2e6ef/activesupport/lib/active_support/testing/time_helpers.rb#L133&quot;&gt;preserved using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;with_usec&lt;/code&gt; option&lt;/a&gt;.
You need to opt into this at each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;freeze_time&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;travel&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;travel_to&lt;/code&gt; call site, but at least it’s possible!&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;freeze_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;with_usec: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Time here is frozen, and includes sub-second precision.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;-wrapping-up&quot;&gt;🎁 Wrapping up&lt;/h2&gt;

&lt;p&gt;In summary, take care when directly comparing or sorting on time (or time-like) values.
And when setting up test data where time is important, be explicit about the exact time values being used.&lt;/p&gt;

&lt;p&gt;Good luck.
And happy testing!&lt;/p&gt;

&lt;h3 id=&quot;-thanks&quot;&gt;🙏 Thanks&lt;/h3&gt;

&lt;p&gt;Thank you to &lt;a href=&quot;https://hibachrach.com&quot;&gt;Hazel&lt;/a&gt; for feedback and proofing!&lt;/p&gt;
</description>
        <pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/on-flaky-tests-time-precision-and-order-dependence</link>
        <guid isPermaLink="true">https://stevenharman.net/on-flaky-tests-time-precision-and-order-dependence</guid>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>testing</category>
        
        
      </item>
    
      <item>
        <title>So We&apos;ve Got a Memory Leak…</title>
        <description>&lt;p&gt;Memory leaks happen.
And if you’re here, reading this, I’d bet you’re dealing with one.
First things first - are you &lt;a href=&quot;https://medium.com/klaxit-techblog/tracking-a-ruby-memory-leak-in-2021-9eb56575f731#5051&quot; title=&quot;Tracking a Ruby memory leak in 2021&quot;&gt;sure it’s a leak, and not bloat&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Okay, so it’s a leak.
Much has been written about various tools for &lt;a href=&quot;https://blog.appsignal.com/2022/08/10/a-deep-dive-into-memory-leaks-in-ruby.html&quot; title=&quot;A Deep Dive into Memory Leaks in Ruby&quot;&gt;profiling a leak&lt;/a&gt;, &lt;a href=&quot;https://www.cloudbees.com/blog/the-definitive-guide-to-ruby-heap-dumps-part-i&quot; title=&quot;The Definitive Guide to Ruby Heap Dumps, Part I&quot;&gt;understanding heap dumps&lt;/a&gt;, &lt;a href=&quot;https://blog.devops.dev/understanding-memory-leaks-in-ruby-on-rails-applications-43a84222f49c&quot; title=&quot;Understanding Memory Leaks in Ruby on Rails Applications&quot;&gt;common causes of leaks&lt;/a&gt;, &lt;a href=&quot;https://shopify.engineering/ruby-variable-width-allocation&quot; title=&quot;Optimizing Ruby&apos;s Memory Layout: Variable Width Allocation&quot;&gt;work being done to improve Ruby’s memory layout&lt;/a&gt;, and so much more.
Ben Sheldon’s recent “&lt;a href=&quot;https://island94.org/2024/01/the-answer-is-in-your-heap-debugging-big-rails-memory&quot; title=&quot;The answer is in your heap: debugging a big memory increase in Ruby on Rails&quot;&gt;The answer is in your heap: debugging a big memory increase in Ruby on Rails&lt;/a&gt;” is a great example.
Ben mentions and shows how he and some teammates used several different tools to generate heap dumps, analyze and interrogate them, and ultimately find and fix the source of a leak in Rails itself.&lt;/p&gt;

&lt;p&gt;These are all great resources, and can be crucial knowledge in our hunt for a leak.
But they all suppose we already have an idea of where to start looking, or use stripped down examples to showcase the tools.
The &lt;a href=&quot;https://github.com/SamSaffron/memory_profiler&quot; title=&quot;MemoryProfiler - A memory profiler for Ruby&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memory_profiler&lt;/code&gt; Gem&lt;/a&gt; can profile memory allocations around some suspect code, if we already know that code is suspect.
&lt;a href=&quot;https://github.com/zombocom/derailed_benchmarks/blob/main/README.md#i-want-more-heap-dumps&quot; title=&quot;Derailed Benchmarks - A series of things you can use to benchmark a Rails or Ruby app.&quot;&gt;Derailed Benchmarks&lt;/a&gt; can get us
started on tracking down a leak in the overall stack, or a known problematic resource, and generate heap dumps for us.
Comparing those dumps with &lt;a href=&quot;https://github.com/zombocom/heapy&quot; title=&quot;Heapy (Ruby Heap Dump Inspector) - A CLI for analyzing Ruby Heap dumps.&quot;&gt;heapy&lt;/a&gt; can point us in the right direction by revealing memory being unexpectedly retained over time.
We can use &lt;a href=&quot;https://github.com/jhawthorn/sheap&quot; title=&quot;sheap - A library for interactively exploring Ruby Heap dumps. Sheap contains contains a command-line tool and a library for use in IRB.&quot;&gt;sheap&lt;/a&gt; to track down exactly where problematic objects allocations are happening, once we’ve identified those problematic objects.&lt;/p&gt;

&lt;p&gt;But what if we’ve reviewed all recent code changes, and nothing stands out?
Or if the leaks don’t happen consistently, across all instances of the running app?
Or memory starts leaking different times?
Where do we even start?&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;That’s what I was asking myself recently with a decade old Rails app I work on.
This is a real production app, driving substantial revenue.
It serves a sustained load of 400-500 requests/second, with peaks into several thousands/second.&lt;/p&gt;

&lt;p&gt;Then one, nondescript day, amongst the usual flow of deploys, memory started spiking. 📈
The pager started yelling. 📟
And we had what looked like a memory leak. 💧&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/initial-memory-spikes.png&quot; alt=&quot;Graph of memory starting to spike over and over mid-way across the view&quot; title=&quot;We have a memory leak! So. Many. Spikeys!&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;We have a memory leak! So. Many. Spikeys!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;put-out-the-fire&quot;&gt;🔥 First put out the fire&lt;/h2&gt;

&lt;p&gt;It certainly looked like a leak, rather than bloat, meaning we had one sure-fire way to temporarily fix the problem.
Restart things!
We run on &lt;a href=&quot;https://heroku.com&quot; title=&quot;Heroku is a container-based cloud Platform as a Service (PaaS).&quot;&gt;Heroku&lt;/a&gt;, and what we’re seeing above are per-&lt;a href=&quot;https://www.heroku.com/dynos&quot; title=&quot;Dynos: the heart of the Heroku platform&quot;&gt;Dyno&lt;/a&gt; memory numbers.
In the short term, we’d either let our normal cadence of many-deploys-a-day restart our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; instances, or we’d manually restart individual Dynos approaching their memory limit.&lt;/p&gt;

&lt;h2 id=&quot;look-for-suspect-changes&quot;&gt;🔎 Look for suspect changes&lt;/h2&gt;

&lt;p&gt;With the fire under control we turned our attention to changes that happened in the hours before the first big spike.
The graph above doesn’t show our deploy markers (they were too distracting at this granularity) but starting with the deploy just inside of the first spike and working backward, we audited all code changes going back three days.&lt;/p&gt;

&lt;p&gt;We found a few changes that seemed like they &lt;em&gt;could&lt;/em&gt; be related.
One of them introduced a memory leak in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;development&lt;/code&gt; mode due to Rails’ code reloading.
Another caused us to hit Redis more than we intended during certain request filtering.
The third looked like we were making more database calls, loading more &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; instances than we wanted (a classic N+1).
We fixed the first two and rolled back the third, deploying them one at a time.
The memory leak continued. 📈&lt;/p&gt;

&lt;p&gt;No other code changes &lt;em&gt;looked&lt;/em&gt; like they could cause a leak.
But, we had made a few tooling changes to start collecting Ruby language and Puma pool usage metrics, so we backed those out and deployed.
The memory leak continued. 📈&lt;/p&gt;

&lt;h2 id=&quot;look-for-patterns&quot;&gt;🧩 Look for patterns&lt;/h2&gt;

&lt;p&gt;Next we went looking for patterns to the memory growth.
Of note, it was only our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; Dynos that were leaking.
All of our &lt;a href=&quot;https://sidekiq.org/products/enterprise.html&quot; title=&quot;Simple, efficient background jobs for Ruby.&quot;&gt;Sidekiq&lt;/a&gt; and &lt;a href=&quot;https://github.com/collectiveidea/delayed_job&quot; title=&quot;Delayed::Job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background.&quot;&gt;Delayed::Job&lt;/a&gt; (yes, both, for… reasons lost to history) Dynos looked just fine.
Also, not all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; Dynos were always leaking memory.
Sometimes they’d go hours with relatively flat-ish memory usage consistent with a long-running web process.
Then one, or a few, or all of them would start to leak memory.
This made us suspicious that perhaps the leak was specific to a certain &lt;em&gt;kind&lt;/em&gt; of traffic, rather than a problem in the stack, or with volume of traffic.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/memory-spikes-delayed.png&quot; alt=&quot;Graph of memory flat for 4 hours, and then starting to rise&quot; title=&quot;Low-traffic period, just post deploy. Hours pass before 2 of 4 Dynos start to leak memory.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Low-traffic period, just post deploy. Hours pass before 2 of 4 Dynos start to leak memory.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When we looked at our Puma workers, we also saw that not all workers on a single Dyno were leaking.
We run Puma in clustered mode, with 12 worker processes for the 8 &lt;abbr title=&quot;Virtual CPU&quot;&gt;vCPU&lt;/abbr&gt; on each Dyno.
It wasn’t unusual to see just a couple of the 12 processes were using up nearly all of the memory on the Dyno.
i.e., a couple workers were leaking, while the rest were fine.&lt;/p&gt;

&lt;p&gt;Due to aggressive sampling we were unable to use our &lt;a href=&quot;https://opentelemetry.io/docs/concepts/signals/traces/&quot; title=&quot;OpenTelemetry Traces&quot;&gt;OpenTelmetry Traces&lt;/a&gt; to match specific kinds of traffic to specific Dynos at times that correlated to the start of the leaks.
But we still had a strong suspicion the leaks had &lt;em&gt;something&lt;/em&gt; to do with particular traffic.
One of the smart folks I work with had the idea to try correlating via our logs - which are not sampled.
But the tooling there didn’t make that particularly easy.
So we put that idea in our back pocket and moved on to another.&lt;/p&gt;

&lt;h2 id=&quot;go-to-the-dumps&quot;&gt;🚛 Go to the dumps!&lt;/h2&gt;

&lt;p&gt;If memory is being leaked, looking at dumps of Ruby’s memory heap is probably a good idea.
After all, all of those very smart Internet Folks wrote lots of words about it.&lt;/p&gt;

&lt;h3 id=&quot;step-0-tooling&quot;&gt;Step 0: Get the tooling in place&lt;/h3&gt;

&lt;p&gt;We use &lt;a href=&quot;https://github.com/tmm1/rbtrace&quot; title=&quot;rbtrace: like strace, but for ruby code&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbtrace&lt;/code&gt;&lt;/a&gt; to attach to a running Ruby process, meaning it needs to be loaded into the running process.
Basically, make sure it’s in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;.
It’s generally fine to have this loaded in production.
But we’ve made it somewhat toggle-able; we can opt in/out of loading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbtrace&lt;/code&gt; via an Environment Variable:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rbtrace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;require: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FEATURE_ENABLE_MEMORY_DUMPS&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;true&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-1-find-leaking-process&quot;&gt;Step 1: Find a leaking process&lt;/h3&gt;

&lt;p&gt;Running on Heroku meant we needed to dump the heap of a live, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; process, from within a Dyno itself.
Luckily, Herkou and the Ruby community have already built all of the tooling necessary.&lt;/p&gt;

&lt;p&gt;Using &lt;a href=&quot;https://devcenter.heroku.com/articles/exec&quot; title=&quot;Heroku Exec (SSH Tunneling)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku ps:exec&lt;/code&gt;&lt;/a&gt; to open an SSH tunnel into a leaking Dyno, we needed to find a particular Puma worker process to dump.
We went to our old *nix friend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ps&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ps &lt;span class=&quot;nt&quot;&gt;-eo&lt;/span&gt; pid,ppid,comm,rss,vsz &lt;span class=&quot;nt&quot;&gt;--sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rss&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;ruby
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This gave us a list of all of the Ruby processes running on the Dyno, sorted by their &lt;a href=&quot;https://stackoverflow.com/questions/7880784/what-is-rss-and-vsz-in-linux-memory-management&quot; title=&quot;What is RSS and VSZ in Linux memory management&quot;&gt;RSS&lt;/a&gt; (amount of memory being used by the process).
In the case of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt; Dyno, most of them have the same &lt;abbr title=&quot;Parent Process IDentifier&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PPID&lt;/code&gt;&lt;/abbr&gt;.
These are the Puma workers.
The process with a matching &lt;abbr title=&quot;Process IDentifier&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PID&lt;/code&gt;&lt;/abbr&gt; is the Puma primary (master) process.
It will look something similar for Sidekiq.
Other Ruby tools that run only a single process would look a little different.
Picking the process with the most memory (i.e., the leakiest of them) we use its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PID&lt;/code&gt; to and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbtrace&lt;/code&gt; to start tracing memory allocations.&lt;/p&gt;

&lt;h3 id=&quot;step-2-enable-tracing&quot;&gt;Step 2: Enable memory allocation tracing&lt;/h3&gt;

&lt;p&gt;We set that worker process ID as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$DUMP_PID&lt;/code&gt; to be used as a variable in later steps.
This helps prevent silly typos and much face-into-keyboard-ing later on. 🤦&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;pid&amp;gt;
&lt;span class=&quot;c&quot;&gt;# Turn on allocation tracking in the Ruby process.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# This will impact performance; it can use a lot of memory and CPU&lt;/span&gt;
rbtrace &lt;span class=&quot;nt&quot;&gt;--pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--eval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Thread.new{require &apos;objspace&apos;;ObjectSpace.trace_object_allocations_start}.join&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-3-dump-the-heap&quot;&gt;Step 3: Dump the heap&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Generate the first heap dump&lt;/span&gt;
rbtrace &lt;span class=&quot;nt&quot;&gt;--pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--eval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Thread.new{require &apos;objspace&apos;; GC.start(); io=File.open(&apos;/tmp/heap-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.json&apos;, &apos;w&apos;); ObjectSpace.dump_all(output: io); io.close}.join&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--timeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;600
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; file can be quite large.
On a leaking process that had been running for a few hours, it was 5-6&lt;abbr title=&quot;Gibibyte&quot;&gt;GiB&lt;/abbr&gt; in size.
I’d recommend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gzip&lt;/code&gt;-ing it.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;gzip&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/tmp/heap-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.json&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That will replace the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; dump file with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json.gz&lt;/code&gt; file which could be up to an order of magnitude smaller.&lt;/p&gt;

&lt;h3 id=&quot;step-4-fetch-the-dump&quot;&gt;Step 4: Fetch the dump&lt;/h3&gt;

&lt;p&gt;This will differ depending on where the application is deployed, but on Heroku we run the following (adjusting for exact file and Dyno names) from a shell on our local machine.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;heroku ps:copy /tmp/heap-&amp;lt;pid&amp;gt;.json.gz &lt;span class=&quot;nt&quot;&gt;--dyno&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;web.1&amp;gt; &lt;span class=&quot;nt&quot;&gt;--app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;app name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Decompress and move the file somewhere organized.
I’d recommend renaming it based on which dump this is; we’ll grab at least three if we’re going to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heapy&lt;/code&gt; to try to find retained memory, for example.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mv &lt;/span&gt;heap-&amp;lt;pid&amp;gt;.json.gz ~/dumps/.
&lt;span class=&quot;nb&quot;&gt;gzip&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--keep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--decompress&lt;/span&gt; ~/dumps/heap-&amp;lt;pid&amp;gt;.json.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Repeat steps 3 and 4 to grab as many dumps as needed.&lt;/p&gt;

&lt;h3 id=&quot;step-5-clean-up&quot;&gt;Step 5: Clean up after ourselves&lt;/h3&gt;

&lt;p&gt;Be sure to turn off the allocation tracking, and probably remove those dumps from the Dyno.
Or just restart the Dyno.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rbtrace &lt;span class=&quot;nt&quot;&gt;--pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DUMP_PID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--eval&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Thread.new{GC.start;require &apos;objspace&apos;;ObjectSpace.trace_object_allocations_stop;ObjectSpace.trace_object_allocations_clear}.join&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;analyze-the-heap-dumps&quot;&gt;🧐 Analyze the heap dumps&lt;/h2&gt;

&lt;p&gt;We tried looking at the retained memory reports via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heapy&lt;/code&gt;, and meandering the diffs via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheap&lt;/code&gt;, but with no particular spot to start looking, it felt like looking for a specific needle in a stack of needles.
(Thanks for that line, Tom Hanks.)&lt;/p&gt;

&lt;p&gt;In hopes of &lt;em&gt;seeing&lt;/em&gt; something that looked off, &lt;a href=&quot;https://ruby.social/@stevenharman/111791414166558864&quot; title=&quot;Me, asking questions on ruby.social&quot;&gt;I went in search&lt;/a&gt; of a way to visualize the heap dumps.
And The Internet™ did not disappoint!
&lt;a href=&quot;https://github.com/oxidize-rb/reap&quot; title=&quot;A tool for parsing Ruby heap dumps by analyzing the reference graph.&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reap&lt;/code&gt; is a tool for analyzing a Ruby heap dump’s reference graph&lt;/a&gt;, and generating visualizations.
Including flame graphs!
Here’s a graphs of the third dump we took off of a leaking process, per the steps above.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;reap path/to/heap-&amp;lt;pid&amp;gt;-2.json &lt;span class=&quot;nt&quot;&gt;--count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;15 &lt;span class=&quot;nt&quot;&gt;--flamegraph&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dump-2.svg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The way to read this is starting at the top and moving downward are references from a “root” (in the view of the Ruby Garbage Collector) to other objects.
That is, objects further down are references by objects above them.
And the more memory an object (or an object it references) is holding onto, the wider the cell in the graph.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/flamegraph-2.png&quot; alt=&quot;Flame graph showing a Thread holding 1.9GiB of memory.&quot; title=&quot;Flame graph showing a Thread holding 1.9GiB of memory.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Flame graph showing a Thread holding 1.9GiB of memory.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Notice anything odd?
What’s up with that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Thread&lt;/code&gt; holding 1.9GiB of memory?
Or really, it’s the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; at the bottom, holding onto references to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32,067&lt;/code&gt; other objects, in turn holding 1.9GiB of memory.
So what the heck is that array?
Where did it come from?
Why is it referencing so much memory?&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/flamegraph-2-annotated.png&quot; alt=&quot;Flame graph calling out an Array of 32k things, referencing 1.9GiB of memory.&quot; title=&quot;Flame graph calling out an Array of 32k things, referencing 1.9GiB of memory.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Flame graph calling out an Array of 32k things, referencing 1.9GiB of memory.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;go-spelunking-with-sheap&quot;&gt;🧗 Go spelunking with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheap&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Using the latest &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; branch from &lt;a href=&quot;https://github.com/jhawthorn/sheap&quot; title=&quot;sheap - A library for interactively exploring Ruby Heap dumps. Sheap contains contains a command-line tool and a library for use in IRB.&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheap&lt;/code&gt;&lt;/a&gt; (because some of the features like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;find_path&lt;/code&gt; weren’t yet released to RubyGems), we compared the second and third dumps.
It will take a while to parse these dumps, which were pushing 6GiB in our case. ☕️&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/code/jhawthorn/sheap/bin/sheap heap-&amp;lt;pid&amp;gt;-1.json heap-&amp;lt;pid&amp;gt;-2.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That opens an &lt;abbr title=&quot;Interactive Ruby&quot;&gt;IRB&lt;/abbr&gt; console where we can explore the dumps and the diff between them.
See the &lt;a href=&quot;https://github.com/jhawthorn/sheap/blob/main/README.md&quot; title=&quot;Sheap is a library for interactively exploring Ruby Heap dumps&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;README&lt;/code&gt;&lt;/a&gt; (and the source code) for all that it can do.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0x55adecb68498&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DATA&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adecb68498&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thread&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;137&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;vg&quot;&gt;$after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ROOT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vm&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4014&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DATA&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55addb8deff8&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ractor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DATA&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adecb68498&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;VM&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;thread&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;137&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We first investigated that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Thread&lt;/code&gt; to understand where it was coming from.
Our suspicion was that it was a background thread from one of our telemetry or metrics tools - maybe the &lt;abbr title=&quot;OpenTelemetry&quot;&gt;OTel&lt;/abbr&gt; tracer, or error reporter, or something like that?
But, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheap&lt;/code&gt; told us it was a Puma thread - the one actually working requests, meaning it’s “the Rails thread.”
So that question is answered.&lt;/p&gt;

&lt;p&gt;Next we wanted to know more about that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::SubscriberQueueRegistry&lt;/code&gt; object.
We went looking at the stable branch for the version of Rails we’re currently on (6.1) and found that &lt;a href=&quot;https://github.com/rails/rails/blob/517ff4b7c6c6aeaa588993475277d816b0ba038e/activesupport/lib/active_support/subscriber.rb#L155&quot;&gt;it’s basically a per-&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Thread&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; used to store lists of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Subscriber&lt;/code&gt; instances&lt;/a&gt;, based on the event name.
And &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications::Event&lt;/code&gt; objects are pushed onto and popped off that per-thread, per-event-name list (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;) as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications.instrument&lt;/code&gt; blocks are run.
Going back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sheap&lt;/code&gt; we can see that in action - the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SubscriberQueueRegistry&lt;/code&gt; instance holds a reference to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt;, which has references to some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; objects.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$after&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0x55adeeb31798&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adeeb31798&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SubscriberQueueRegistry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adeeb31748&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adeb01afb0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ActiveRecord::LogSubscriber-9300&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adeeb314f0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf09ffaf8&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Lograge::LogSubscribers::ActionController-276520&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf09ffb20&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4a029e8&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Lograge::LogSubscribers::GRPCWeb-276540&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4a02a10&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; with anything in it was in the middle of the flame graph with addresses ending &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ffb20&lt;/code&gt;.
It held a reference to a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications::Event&lt;/code&gt; instance.
And that instance then held a reference to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; of 32k+ other objects.
If we look at the code for that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; class, we &lt;a href=&quot;https://github.com/rails/rails/blob/517ff4b7c6c6aeaa588993475277d816b0ba038e/activesupport/lib/active_support/notifications/instrumenter.rb#L64&quot;&gt;see it contains a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@children&lt;/code&gt; ivar &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;&lt;/a&gt;.
So, it’s an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; of what then?
&lt;em&gt;Child&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; instances, presumably.
We grabbed a couple to confirm.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4fdda70&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children_ary&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4fdda20&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32067&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children_ary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf523e9e0&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;children_ary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55addbe19ae0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;redirect_to.action_controller&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf523e9b8&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adedec2188&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;19b24a9862f0e692859e&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf523e990&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sure enough, the huge &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; is a list of “children” &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; objects.
This first child &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; was for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;redirect_to.action_controller&lt;/code&gt; event name.
And it would have been pushed onto this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event#children&lt;/code&gt; list because some &lt;a href=&quot;https://github.com/rails/rails/blob/517ff4b7c6c6aeaa588993475277d816b0ba038e/activesupport/lib/active_support/subscriber.rb#L138-L139&quot;&gt;other &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; was already on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;event_stack&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At this point our intuition was telling us something was wrong here - these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt;s were incorrectly being put on this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#children&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt;.
We wanted to know what else that child &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; had to tell us, like what is in that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; it’s holding a reference to?&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_child&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;children_ary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf523e9e0&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4e54e38&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55ade7965bf0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PresentationController&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4250150&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;view&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f6d4f0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;507&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f5c998&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Headers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4cccca0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f6e8a0&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf5378c70&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARRAY&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf5378ae0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf5382310&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mh&quot;&gt;0x55adf5400058&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whoa, an entire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionDispatch::Request&lt;/code&gt; object. 😱
The request that was being redirected.
What did that look like?
Maybe we could reproduce this locally?&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4e54e38&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ss&quot;&gt;irb: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first_child&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;references&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;===&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4ccced0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;222&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HASH&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f6d4f0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;507&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f6e8f0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/view/abc123def456?parked=junk&amp;amp;parking=junk&amp;amp;parseSchema=junk&amp;amp;… eliding more param tampering … &amp;amp;password=[FILTERED]&amp;amp; … &amp;amp;portbl=junk&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55addc723e60&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;STRING&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4cccca0&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;GET&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# eliding more stuff&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;OBJECT&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x55adf4f5c998&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionDispatch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Headers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Well look at that.
It‘s a mostly valid request, to an actual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Route&lt;/code&gt; in the app, with a valid public ID of a resource (changed to protect the innocent, of course).
What’s not legit is the parameter tampering - the work of a scanner very likely.
What especially stood out was the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;password=[FILTERED]&lt;/code&gt; , which means something was catching the request and cleaning sensitive info we’d not want in logs and such.&lt;/p&gt;

&lt;h2 id=&quot;reproduce-it&quot;&gt;🔄 Reproduce it&lt;/h2&gt;

&lt;p&gt;With the questionable looking query path and params, we opened the production app in an incognito browser window and made a request.
We were met with a &lt;abbr title=&quot;Internal Server Error&quot;&gt;500&lt;/abbr&gt; server error.&lt;/p&gt;

&lt;p&gt;🎉 Success! ✨&lt;/p&gt;

&lt;p&gt;Logs confirmed that it was an error.
Specifically a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI::InvalidURIError&lt;/code&gt;.
The logs also recorded which Dyno the request had hit.
We popped open our memory graphs and by sheer luck, that Dyno was currently showing normal memory usage.
We put a temporary deploy freeze in place so we could monitor memory usage for a few minutes and before long the trend line was clear.
We had a leak.&lt;/p&gt;

&lt;p&gt;With some confidence we were leaking &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; objects as part of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications&lt;/code&gt; tooling, we still wanted to reproduce it locally so we could step through the code and see what was going on.
Leaning on a little bit of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binding.pry&lt;/code&gt; and even more &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;puts&lt;/code&gt; debugging in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;activesupport&lt;/code&gt; Gem (if you don’t know, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle open &amp;lt;gem name&amp;gt;&lt;/code&gt;, &lt;a href=&quot;https://bundler.io/v2.5/man/bundle-open.1.html&quot;&gt;check it out&lt;/a&gt;), we eventually reproduced the situation locally.
And with a backtrace!&lt;/p&gt;

&lt;p&gt;The error’s backtrace pointed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uri&lt;/code&gt; Gem (part of Ruby’s standard library), which was being &lt;a href=&quot;https://github.com/bugsnag/bugsnag-ruby/blob/v6.26.1/lib/bugsnag/integrations/railtie.rb#L45&quot;&gt;used by Bugsnag’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bugsnag.cleaner.clean_url&lt;/code&gt; method&lt;/a&gt;, as part of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications.subscribe&lt;/code&gt; block.
Which sounded awfully familiar.
A little more debugging and stepping through the Bugsnag and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport&lt;/code&gt; code and We’d narrowed in on the problem(s).&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/puts-debugging-before.png&quot; alt=&quot;Logs showing a Event object left on the stack after the error is raised.&quot; title=&quot;Logs showing a Event object left on the stack after the error is raised.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Logs showing a Event object left on the stack after the error is raised.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;understand-the-cause&quot;&gt;💡Understand the cause&lt;/h2&gt;

&lt;p&gt;There were two related problems, right next to each other.
The first was to do with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Subscriber&lt;/code&gt;’s notion of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#children&lt;/code&gt; and how it was tracking them.
&lt;a href=&quot;https://jhawthorn.com/&quot;&gt;John Hawthorn&lt;/a&gt; explains it, and had &lt;a href=&quot;https://github.com/rails/rails/pull/43390&quot;&gt;already fixed it in Rails 7.1&lt;/a&gt; (we were on 6.1 at the time).&lt;/p&gt;

&lt;p&gt;This alone wasn’t the cause our memory leak.
But in combination with a change to the Bugsnag Gem to &lt;a href=&quot;https://github.com/bugsnag/bugsnag-ruby/pull/806&quot;&gt;start cleaning URLs Rails breadcrumbs&lt;/a&gt;, it manifested in a leak.
The Bugsnag change relied on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI&lt;/code&gt; to parse and then redact certain sensitive query strings.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URI&lt;/code&gt; is rightfully pretty strict about what it considers a valid &lt;abbr title=&quot;Uniform Resource Identifier&quot;&gt;URI&lt;/abbr&gt;, raising an error when given an invalid URI.
Meaning the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications.subscribe&lt;/code&gt; block in the Bugsnag Gem could now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise&lt;/code&gt; an error while processing an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::Notifications::Event&lt;/code&gt;.
And raising that error meant that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; &lt;a href=&quot;https://github.com/rails/rails/blob/517ff4b7c6c6aeaa588993475277d816b0ba038e/activesupport/lib/active_support/subscriber.rb#L145&quot;&gt;wasn’t getting popped off the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Subscriber#event_stack&lt;/code&gt;&lt;/a&gt;.
So the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parent&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; was sticking around (leaking memory), and it was still holding a reference to the child &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event&lt;/code&gt; via its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#children&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; (leaking more memory).&lt;/p&gt;

&lt;p&gt;John’s change not only removed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Event#children&lt;/code&gt; concept, but completely removed the shared-&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; used to track &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Events&lt;/code&gt;.
The source of both leaks, gone in a single PR. 👏&lt;/p&gt;

&lt;h2 id=&quot;fix-the-problem&quot;&gt;🛠️ Fix the problem&lt;/h2&gt;

&lt;p&gt;John’s fix for newer Rails means this is no longer a problem… on newer Rails versions.
But we weren’t there yet.
As luck would have it, just days prior &lt;a href=&quot;https://github.com/bugsnag/bugsnag-ruby/pull/811&quot;&gt;Bugsnag had fixed&lt;/a&gt; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bugsnag.cleaner.clean_url&lt;/code&gt; method to not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;raise&lt;/code&gt; an error on invalid URIs.&lt;/p&gt;

&lt;p&gt;Our fix, in the short term, was to upgrade the Bugsnag Gem to a version that included the “don’t raise an error while trying to clean an invalid URI” change.
Longer term we’re upgrading Rails versions.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/images/posts/memory_leak/puts-debugging-after.png&quot; alt=&quot;Upgraded dependency no longer raises, and now the stack is empty.&quot; title=&quot;Upgraded dependency no longer raises, and now the stack is empty.&quot; /&gt;
  &lt;figcaption class=&quot;text-center&quot;&gt;Upgraded dependency no longer raises, and now the stack is empty.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;its-me-hi-im-the-problem-its-me&quot;&gt;🤡 It’s me, hi, I’m the problem, it’s me.&lt;/h2&gt;

&lt;p&gt;&lt;cite&gt;(with apologies to Taylor Swift) 🙏&lt;/cite&gt;&lt;/p&gt;

&lt;p&gt;Funny story.
It turns out that Bugsnag change was introduced to our code base right around when the first memory spikes occurred.
We’d upgraded from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v6.26.0&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v6.26.1&lt;/code&gt; to fix a deprecation warning for a different dependency.
We (by which I mean me, I, myself) audited this change too.
And dismissed it as not being related because… why the heck would redacting part of a URI, something the Bugsnag Gem already did in other places, cause such a bad and sudden memory leak!?!?
Well… &lt;em&gt;something-something-something&lt;/em&gt; assumptions. 🤦&lt;/p&gt;
</description>
        <pubDate>Thu, 25 Jan 2024 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/so-we-have-a-memory-leak</link>
        <guid isPermaLink="true">https://stevenharman.net/so-we-have-a-memory-leak</guid>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>heroku</category>
        
        
      </item>
    
      <item>
        <title>Cherry-picking Specific Active Support Behavior</title>
        <description>&lt;p&gt;The &lt;a href=&quot;as-lib&quot;&gt;Active Support library&lt;/a&gt; has always (or, &lt;em&gt;close enough to always&lt;/em&gt;) allowed us to cherry-pick specific extensions/behaviors, to load only strictly needed dependencies.
That is, rather than loading the entirety of the library, we can load just the bits and pieces we need.
This helps keep the amount of things loaded in memory smaller, and faster by doing less work.
It can also aide in understanding by making more explicit the dependencies some code relies on.&lt;/p&gt;

&lt;p&gt;I’ve been using this technique for years, and it’s been solid.
Until today, while working on a Rails 7 code base, when I started seeing an error:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;go&quot;&gt;uninitialized constant ActiveSupport::IsolatedExecutionState (NameError)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As it happens, what I’d been doing for years &lt;em&gt;worked&lt;/em&gt;, but more by accident than design.
Active Support 7 has &lt;em&gt;fixed the glitch&lt;/em&gt;.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;how-it-used-to-work&quot;&gt;How it used to “work”&lt;/h2&gt;

&lt;p&gt;Let’s say we want to use Active Support’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Time&lt;/code&gt; extensions for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Numeric&lt;/code&gt; types.
For example, that’s what that allows us to express some past date/time like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 2022-09-30 09:08:47.983955 -0400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We could cherry-pick in just those extensions, right in the file where we’re using them, like so:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/core_ext/numeric/time&quot;&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 2022-09-30 09:08:47.983955 -0400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But as of Active Support 7, that doesn’t quite work.
It raises a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NameError&lt;/code&gt; because the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::IsolatedExecutionState&lt;/code&gt; constant cannot be found.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/core_ext/numeric/time&quot;&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# NameError: uninitialized constant ActiveSupport::IsolatedExecutionState&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#       ::ActiveSupport::IsolatedExecutionState[:time_zone] || zone_default&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#                      ^^^^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# from .../ruby/gems/3.1.0/gems/activesupport-7.0.4/lib/active_support/core_ext/time/zones.rb:15:in `zone&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-changed&quot;&gt;What changed?&lt;/h2&gt;

&lt;p&gt;Active Support 7 added a &lt;a href=&quot;as-pr-concurrency&quot;&gt;new abstraction to better handle different concurrency mechanisms&lt;/a&gt; (think, Threads vs Fibers).
In doing so, everywhere in Active Support that used to directly reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Thread.current&lt;/code&gt;, was changed to leverage &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::ActiveSupport::IsolatedExecutionState&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means attempting to directly cherry-pick a bit of Active Support, as had been possible for years, we might also need the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IsolatedExecutionState&lt;/code&gt; constant loaded.
So, the direct cherry-picking feature is… broken?&lt;/p&gt;

&lt;p&gt;Not exactly.
It turns out, Active Support never intended us to directly cherry-pick by &lt;em&gt;only&lt;/em&gt; requiring the one file we need.
The intent, and &lt;a href=&quot;https://guides.rubyonrails.org/active_support_core_extensions.html#how-to-load-core-extensions&quot; title=&quot;Stand-Alone Active Support&quot;&gt;guides&lt;/a&gt;, have long told us to first load the library itself, and then cherry-pick any specific bits we want.
That is, we should have been doing this all along:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/core_ext/numeric/time&quot;&lt;/span&gt;

&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#=&amp;gt; 2022-09-30 09:08:47.983955 -0400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So really, nothing changed.
Before, things just kinda worked by accident.&lt;/p&gt;

&lt;h2 id=&quot;does-that-mean-all-of-active-support-is-loaded&quot;&gt;Does that mean all of Active Support is loaded?&lt;/h2&gt;

&lt;p&gt;I’d always &lt;em&gt;assumed&lt;/em&gt; that requiring the library would also &lt;em&gt;load&lt;/em&gt; the whole library.
Though, I should have known better.
&lt;em&gt;mea culpa&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead, requiring the library via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require &quot;active_support&quot;&lt;/code&gt; &lt;a href=&quot;as-source&quot;&gt;only loads the bare minimum&lt;/a&gt;, while setting up all of the &lt;a href=&quot;ruby-autoload&quot;&gt;Ruby &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoload&lt;/code&gt;-ing&lt;/a&gt; needed to reference other bits of the library later.
Huzzah!&lt;/p&gt;

&lt;p&gt;If we do want or need to eagerly load the whole library, that’s done like so:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;active_support/all&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Phew, glad to have gotten to the bottom of that one! 🎉&lt;/p&gt;

&lt;p&gt;And a shout-out to &lt;a href=&quot;https://github.com/gektin&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@gektin&lt;/code&gt;&lt;/a&gt; for helping me dive into this one.&lt;/p&gt;

</description>
        <pubDate>Wed, 05 Oct 2022 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/cherry-picking-specific-active-support-behavior</link>
        <guid isPermaLink="true">https://stevenharman.net/cherry-picking-specific-active-support-behavior</guid>
        
        <category>rails</category>
        
        
      </item>
    
      <item>
        <title>A Rails 7 compatible bin/dev for heroku local</title>
        <description>&lt;p&gt;Rails 7 introduced a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt; wrapper to launch and manage your Rails server, CSS watcher, and JS bundler into a single process, managed by &lt;a href=&quot;https://github.com/ddollar/foreman&quot; title=&quot;Manage Procfile-based applications&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foreman&lt;/code&gt;&lt;/a&gt;.
This is quite handy for running everything with a single command.
But what if you’re deploying to Heroku and using the Heroku CLI’s &lt;a href=&quot;https://devcenter.heroku.com/articles/heroku-local&quot; title=&quot;Use the Heroku CLI to run Apps locally&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt;&lt;/a&gt; to run things locally?
Or if you’re a fan of one of the other tools that manage processes based on your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Procfile&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I’ve got you covered!&lt;/p&gt;

&lt;h2 id=&quot;bindev-for-heroku-local&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;The biggest, and most obvious change is swapping out the executable.
Mostly this is to do with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt; using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:&amp;lt;subcommand&amp;gt;&lt;/code&gt; style CLI interface.
Let’s compare &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foreman&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt; doing similar things to see how their CLIs differ.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;start the web and css processes
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;foreman start web,css
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;heroku &lt;span class=&quot;nb&quot;&gt;local&lt;/span&gt;:start web,css
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;bin/dev web,css
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It turns out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt; explicitly calls the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foreman start&lt;/code&gt; command.
So a quick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt; version of the same would be:&lt;/p&gt;

&lt;!-- more --&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env sh&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; heroku &amp;amp;&amp;gt; /dev/null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You must install the Heroku CLI on your system before setup can continue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;☁️  https://devcenter.heroku.com/articles/heroku-cli&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;heroku &lt;span class=&quot;nb&quot;&gt;local&lt;/span&gt;:start &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And this works… fine-ish.
I mean, it totally works.
But it’s only using a small part of the underlying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt; CLI.
And the same goes for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foreman&lt;/code&gt; version.
If we want to leverage the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;run&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;version&lt;/code&gt; commands, we’re out of luck.&lt;/p&gt;

&lt;h2 id=&quot;a-more-robust-bindev&quot;&gt;A more robust &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;If we want to leverage the other CLI commands, we’re going to need to do some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt;-ing to check and manipulate the passed arguments.
Let’s start with the CLI we &lt;em&gt;want&lt;/em&gt; to have.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;bin/dev web,css         &lt;span class=&quot;c&quot;&gt;# (implicit) start the web and css processes&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;bin/dev start web,css   &lt;span class=&quot;c&quot;&gt;# (explicit) start the web and css processes&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;bin/dev run bin/rails c &lt;span class=&quot;c&quot;&gt;# start a rails console session&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;bin/dev version         &lt;span class=&quot;c&quot;&gt;# check the foreman version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For this we need to default to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start&lt;/code&gt; command, but allow overriding it.
And we’ll need to pass other arguments on to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; heroku &amp;amp;&amp;gt; /dev/null&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;You must install the Heroku CLI on your system before setup can continue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;☁️  https://devcenter.heroku.com/articles/heroku-cli&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;start&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;heroku_cmds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;run&apos;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;start&apos;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;version&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# If we were given any positional args we might need to handle sub-commands&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$# &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-gt&lt;/span&gt; 0 &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;first_arg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Shift heroku local commands off the array so we can pass remaining arguments along&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;heroku_cmds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[*]&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;first_arg&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;first_arg&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;shift
  &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fi
fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;heroku &lt;span class=&quot;s2&quot;&gt;&quot;local:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;🎉 And just like that, it works!
We’ve got a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev&lt;/code&gt; that works with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; supports its other commands.
You can even &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin/dev --help&lt;/code&gt; to see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;heroku local&lt;/code&gt;’s CLI help text.&lt;/p&gt;

&lt;p&gt;I’ll leave it as an exercise to the reader to adapt this for &lt;a href=&quot;https://github.com/strongloop/node-foreman&quot; title=&quot;Node Foreman is a Node.js version of the popular Foreman tool, with a few Node specific changes.&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node-foreman&lt;/code&gt;&lt;/a&gt; or other tools.&lt;/p&gt;

</description>
        <pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/bin-dev-for-heroku-local</link>
        <guid isPermaLink="true">https://stevenharman.net/bin-dev-for-heroku-local</guid>
        
        <category>shell</category>
        
        <category>rails</category>
        
        
      </item>
    
      <item>
        <title>Verbose Shell Scripts for Future You</title>
        <description>&lt;p&gt;Command line tools and &lt;a href=&quot;https://en.wikipedia.org/wiki/Shell_builtin&quot; title=&quot;Shell builtin&quot;&gt;shell builtins&lt;/a&gt; can offer both a short- and long-form way to specify options.
Sometimes these are also called short and long flags, respectively.
The upshot is a user experience with two ways to specify the same option.
A short and terse way.
And a longer, more verbose way.&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-HLsS&lt;/span&gt; http://stevenharman.net
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;⬆ is the same as ⬇
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;--head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--location&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--show-error&lt;/span&gt; http://stevenharman.net
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Both ways are useful, neither right nor wrong, and each with their own trade offs to consider.&lt;/p&gt;

&lt;h2 id=&quot;why-both&quot;&gt;Why Both?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/why-dont-we-have-both.gif&quot; alt=&quot;Meme: Girl shrugging and asking, &amp;quot;Why don&apos;t we have both?&amp;quot;&quot; class=&quot;img-thumbnail img-thumbnail--right&quot; width=&quot;300&quot; /&gt;
&lt;em&gt;Why are there two ways to say the same thing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s a good question.
One I’ve asked too.
But I’ve not found a definitive answer.
Perhaps the truth is lost to history at this point?&lt;/p&gt;

&lt;p&gt;From what I can tell, &lt;em&gt;originally&lt;/em&gt; (i.e., the early/original *nix flavored OSes) there were only single-character options.
At some point multi-character options were added, possibly via &lt;a href=&quot;http://en.wikipedia.org/wiki/GNU&quot; title=&quot;GNU: GNU&apos;s Not Unix&quot;&gt;GNU&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Along the way the sheer &lt;a href=&quot;https://danluu.com/cli-complexity/&quot; title=&quot;The growth of command line options, 1979-Present&quot;&gt;number of options also grew, a lot&lt;/a&gt;.
Is this a cause and effect relationship, a correlation, or pure happenstance?
I don’t know.
But today we have A LOT of options, and often multiple ways to express them.&lt;/p&gt;

&lt;p&gt;Let’s talk about the trade offs and when to use each style.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;short-options-are-good&quot;&gt;Short Options are Good.&lt;/h2&gt;

&lt;p&gt;They’re more terse.
Literally, they are less to type, less to read, and &lt;em&gt;smaller&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;These can be desirable traits when writing a one-off commands in your shell.
With less, to type you can execute the command sooner.
Seems Good™️.&lt;/p&gt;

&lt;p&gt;A drawback of this terseness is a decrease in readability and increase in cognitive load.
That is to say, they are more difficult for a human (or at least &lt;em&gt;this&lt;/em&gt; human) to parse and internally translate into a meaningful “name.”
Consider again the example I opened with:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-HLsS&lt;/span&gt; http://stevenharman.net
&lt;span class=&quot;gp&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;⬆ is the same as ⬇
&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;--head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--location&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--silent&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--show-error&lt;/span&gt; http://stevenharman.net
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To my eyes and brain, parsing and understanding that first line is difficult, at best.
Examples like the casing of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; referring to different, but related options, abound.
It’s also difficult to &lt;em&gt;notice&lt;/em&gt; the difference in casing when scanning the code.
Especially days, weeks, or years later, when you’ve totally forgotten that there is a difference.&lt;/p&gt;

&lt;p&gt;But hey, if I’m just ripping off a quick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cURL&lt;/code&gt; to check if my site is up, who cares? 
Presumably &lt;em&gt;I&lt;/em&gt; know and remember what each of those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-HLsS&lt;/code&gt; options are doing, so I saved myself a bunch of typing.
Which for me also means a bunch of typos. 🎉&lt;/p&gt;

&lt;p&gt;In summary, short options less to write and read, at the expense of not being as obvious or self-describing.&lt;/p&gt;

&lt;h2 id=&quot;long-options-are-good-too&quot;&gt;Long Options are Good, Too!&lt;/h2&gt;

&lt;p&gt;Well, they’re longer, no doubt about that!&lt;/p&gt;

&lt;p&gt;I find that makes them easier to read at a glance.
And their names help to tell &lt;em&gt;what they do&lt;/em&gt; - why they’re being used, in the context.
Both properties are immensely valuable when used in long-lived scripts.
For example, when as a part of a shell script that my team uses for daily operations.
Or perhaps even worse, a script we use rarely, but which does some crucial task.&lt;/p&gt;

&lt;p&gt;The glance-ability and memorability of the longer names also pays off when searching for a one-liner after the fact.
You might remember an option name and use it as a keyword to &lt;a href=&quot;https://github.com/junegunn/fzf#-&quot; title=&quot;fzf: Command-line Fuzzy Finder&quot;&gt;reverse fuzzy search&lt;/a&gt; (&lt;kbd&gt;^R&lt;/kbd&gt;) your command history.
A trick I use ALL THE TIME!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/curl-shell-history-example.gif&quot; alt=&quot;Shell reverse history fuzzy search with Zsh Navigation Tools&quot; class=&quot;img-thumbnail img-thumbnail--center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Above I search &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl data&lt;/code&gt; and quickly find a prior &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cURL&lt;/code&gt; command that posted some JSON data.
Exactly what I needed (because I can never remember exactly how that works).
The same goes for the many CLIs and their innumerable options that I use on a daily basis.&lt;/p&gt;

&lt;p&gt;A drawback to the verbosity is there’s more to type!
Which I suppose leads to more typos and &lt;a href=&quot;https://en.wikipedia.org/wiki/Exit_status&quot; title=&quot;Exit status&quot;&gt;non-zero exit codes&lt;/a&gt;.
Though, if we’re optimizing for read-time, rather than write-time, maybe that isn’t such a big deal. 🤷&lt;/p&gt;

&lt;h2 id=&quot;which-style-to-use-and-when&quot;&gt;Which Style to Use, and When?&lt;/h2&gt;

&lt;p&gt;Whatever history might have to say about &lt;em&gt;why&lt;/em&gt; we have long options, I prefer to use them when they’re available.
That goes for both one-off commands in the shell, and doubly so for scripts that will exist beyond their initial authoring.
Admittedly, sometimes I am a bit lazy, or overly rely on muscle memory, and fall back to short options. 🙈&lt;/p&gt;

&lt;p&gt;If you don’t already, I’d encourage you to try using long options, especially for scripts.
But like I said, neither is right nor wrong.
It Depends™️.
Consider the trade offs, and move forward.&lt;/p&gt;

</description>
        <pubDate>Mon, 01 Feb 2021 00:00:00 +0000</pubDate>
        <link>https://stevenharman.net/verbose-shell-scripts-for-future-you</link>
        <guid isPermaLink="true">https://stevenharman.net/verbose-shell-scripts-for-future-you</guid>
        
        <category>shell</category>
        
        
      </item>
    
      <item>
        <title>Well-behaved Ruby Objects: Equality</title>
        <description>&lt;p&gt;A “well-behaved” object in Ruby needs to understand the following:&lt;/p&gt;

&lt;p&gt;What makes two Ruby objects “equal”?&lt;br /&gt;
And which version of “equal” (there are several in Ruby)?&lt;br /&gt;
And what makes an object usable as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; key?&lt;br /&gt;
And is that the same thing that makes them &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Because I can never seem to remember the specifics.
And because my searching the Interwebs seems to find related, but not specific help.
And because I’ve got this blurgh-thing… I might as well use it to help future-me (and maybe you?).&lt;/p&gt;

&lt;h2 id=&quot;gimme-the-gist&quot;&gt;Gimme the gist&lt;/h2&gt;

&lt;p&gt;Implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; for use as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; key, and then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alias&lt;/code&gt; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; method to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; for the expected developer ergonomics.
Something like this:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Message&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;attr_reader&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:subject&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eql?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eql?&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What to know more about the specifics, or how to also make these objects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt;?
Read on, friend…&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;surely-someone-else-has-covered-this-already&quot;&gt;Surely someone else has covered this already&lt;/h2&gt;

&lt;p&gt;As it turns out, Russ Olsen covers this very topic, in depth and with great examples, in his book, &lt;a href=&quot;https://amzn.to/2RC7Ph3&quot; title=&quot;Eloquent Ruby, by Russ Olsen&quot;&gt;Eloquent Ruby&lt;/a&gt;.
I should know that because I’ve read that book, and recommend it!
However, it always takes me several trips to The Googles to find a snippet which eventually points me back to that book.
At which point I have to dig out my copy.
All of this only after I’ve tried a dozen other search terms resulting in articles about &lt;a href=&quot;https://medium.com/@khalidh64/difference-between-eql-equal-in-ruby-2ffa7f073532&quot; title=&quot;Difference Between ==, eql?, equal? in ruby&quot;&gt;the difference between the many forms of equality in Ruby&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So yes, to various degrees, it’s been covered before.
There is nothing new under the Sun.&lt;/p&gt;

&lt;p&gt;Like I said, I have this here blurgh and I’m gonna use it. 🤷&lt;/p&gt;

&lt;h2 id=&quot;every-day-object-equality&quot;&gt;Every-day object equality&lt;/h2&gt;

&lt;p&gt;For every-day use, the form of object equality we most often use in Ruby is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;, or double-equals, form.
By default, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; operator is an alias of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equal?&lt;/code&gt; method, which it inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;.
This form compares the object identity, effectively asking “are these the same object in memory?”&lt;/p&gt;

&lt;p&gt;We don’t want this form, so we need to override it to something more meaningful for our object.
Often two objects are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;, or double-equals equal, if they are of the same class, and some set of their internal data is also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;.
In the case above, that means both objects are a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Message&lt;/code&gt;, and their &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject&lt;/code&gt; are the same.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Please go read Mr. Olsen’s excellent coverage of the topic for more details and in-depth examples.&lt;/p&gt;

&lt;h2 id=&quot;use-as-a-hash-key&quot;&gt;Use as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; key&lt;/h2&gt;

&lt;p&gt;When used as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; key, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; will call the object’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; method to get hash code (an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt; value) to use when storing and searching for the key.
It then uses the object’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; method to determine if two hash keys are the same key.
From the &lt;a href=&quot;https://rubyapi.org/2.7/o/object#method-i-hash&quot; title=&quot;Ruby Docs: Object#hash&quot;&gt;Ruby docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; → integer&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Generates an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt; hash value for this object. This function must have the property that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.eql?(b)&lt;/code&gt; implies &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a.hash == b.hash&lt;/code&gt;.&lt;/p&gt;

  &lt;p&gt;The hash value is used along with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Integer&lt;/code&gt; will be truncated before being used.&lt;/p&gt;

  &lt;p&gt;The hash value for an object may not be identical across invocations or implementations of Ruby. If you need a stable identifier across Ruby invocations and implementations you will need to generate one with a custom method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, objects inherit their &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; methods from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Object&lt;/code&gt;, which compares the object identity, similar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;equal?&lt;/code&gt;.
As you might guess, we don’t want that for our object.&lt;/p&gt;

&lt;p&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt;, we can lean on Ruby already knowing how to calculate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; code for an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Array&lt;/code&gt; of values.
We’ll use all of the values composing the identity of the object itself.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hash&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To determine if two objects are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt;, and hence, the same keys in a hash, we want to make sure they’re of the same class, and some set of their internal data is also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eql?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Look familiar?
It’s the same as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; from above!&lt;/p&gt;

&lt;p&gt;In practice, I often implement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; since they are a bit of a duo - one requiring the other.
And then I alias &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; to be the same as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eql?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;other&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;eql?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s it, these objects now know if they are everyday-equal, and can serve as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; keys.
We’ve not covered &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;===&lt;/code&gt; here; the links above provide explanations about &lt;em&gt;when&lt;/em&gt;, &lt;em&gt;why&lt;/em&gt;, and &lt;em&gt;how&lt;/em&gt; you might need to reach for that form of equality amongst Ruby objects.
Again, go check out Mr. Olsen’s book; it’s great!&lt;/p&gt;

&lt;h2 id=&quot;what-about-comparable&quot;&gt;What about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt;?&lt;/h2&gt;

&lt;p&gt;Being able to compare, and thus &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sort&lt;/code&gt; two Ruby objects is a bit tangential to the forms of equality we’ve discussed here.&lt;/p&gt;

&lt;p&gt;Objects with a “natural ordering” will implement the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;=&amp;gt;&lt;/code&gt; or &lt;em&gt;spaceship&lt;/em&gt; operator.
This operator is used to determine if one object is less than the other, greater than the other, or equal to the other.
Ruby’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt; module will use that to implement several comparison operators.
And all of that can be use by other Ruby modules, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enmerable&lt;/code&gt; to do things like sort a collection.&lt;/p&gt;

&lt;p&gt;🤓 &lt;em&gt;Neat!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Again, Mr. Olsen’s book, or any number of other articles, including the &lt;a href=&quot;https://rubyapi.org/2.7/o/comparable&quot; title=&quot;Ruby Docs: Comparable&quot;&gt;Ruby docs for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Comparable&lt;/code&gt;&lt;/a&gt;, cover this in great detail.&lt;/p&gt;

&lt;h2 id=&quot;the-gist-once-more&quot;&gt;The gist, once more&lt;/h2&gt;

&lt;p&gt;To recap, implement the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hash&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; methods to use an object as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hash&lt;/code&gt; key.
Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alias&lt;/code&gt; the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eql?&lt;/code&gt; method to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;==&lt;/code&gt; to cover they typical, “every day”, usage we expect when writing/reading Ruby.&lt;/p&gt;

</description>
        <pubDate>Tue, 14 Apr 2020 16:40:01 +0000</pubDate>
        <link>https://stevenharman.net/well-behaved-ruby-object-equality</link>
        <guid isPermaLink="true">https://stevenharman.net/well-behaved-ruby-object-equality</guid>
        
        <category>ruby</category>
        
        
      </item>
    
      <item>
        <title>Debugging Homebrew with Pry</title>
        <description>&lt;p&gt;Over the years I’ve written a few &lt;a href=&quot;https://github.com/Homebrew/homebrew-core/blob/master/Formula/git-tracker.rb&quot;&gt;Homebrew formulas&lt;/a&gt; and sent the occasional Pull Request to update a formula or two.
But I’ve never done any work &lt;em&gt;within&lt;/em&gt; &lt;a href=&quot;https://brew.sh&quot;&gt;Homebrew&lt;/a&gt;.
I’ve never needed to debug how Homebrew itself worked.
Until now.&lt;/p&gt;

&lt;p&gt;I assumed our typical Ruby debugging tools, like &lt;a href=&quot;http://pryrepl.org&quot;&gt;Pry&lt;/a&gt; and &lt;a href=&quot;https://github.com/deivid-rodriguez/pry-byebug&quot;&gt;Pry-Byebug&lt;/a&gt; would work.
Homebrew is just Ruby, after all.
Which is true.
But it’s also a bit special, and we can’t do the normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require &quot;pry-byebug&quot;; binding.pry&lt;/code&gt; tricks.&lt;/p&gt;

&lt;p&gt;But we can, with a little poking around, still use those tools!&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h3 id=&quot;a-homebrew-command&quot;&gt;A Homebrew command&lt;/h3&gt;

&lt;p&gt;In this case, I was exploring a built-in command to see if I could extend it.
The &lt;a href=&quot;https://github.com/Homebrew/homebrew-core/blob/cb5e4b3ee75ae522360ddda5b27e67be67d56287/cmd/brew-postgresql-upgrade-database.rb&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew-postgres-upgrade-database&lt;/code&gt;&lt;/a&gt; command is a great little tool that ✨ automagically ✨ upgrades a PostgreSQL instance’s data between major versions.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew postgresql-upgrade-database
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Upgrading postgresql data from 11 to 12...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Moving postgresql data from /usr/local/var/postgres to /usr/local/var/postgres.old...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Creating database...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Migrating and upgrading data...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Upgraded postgresql data from 11 to 12!
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Your postgresql 11 data remains at /usr/local/var/postgres.old
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Except it only worked with the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PGDATA&lt;/code&gt; location.
Which is sensible.
But for… reasons, I had my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PGDATA&lt;/code&gt; directory in a different location.
I keep mine within my user directory at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.pg-data&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;a-first-failed-attempt&quot;&gt;A first, failed attempt&lt;/h3&gt;

&lt;p&gt;The first step was figuring out where this command lived on my machine so I could hack at it, as a proof-of-concept.
I needed it to allow an optional data directory.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;
/usr/local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Knowing Homebrew was installed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local&lt;/code&gt; I went there and poked around until I found what I was looking for in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb&lt;/code&gt;.
Phew, that’s a mouthful!&lt;/p&gt;

&lt;p&gt;From there I opened the code.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;vim /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Right above the existing code (snipped below for brevity) I tried the normal trick.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; #:  * `postgresql-upgrade-database`:
 #:    Upgrades the database for the `postgresql` formula.
&lt;span class=&quot;gi&quot;&gt;+ 
+require &quot;pry-byebug&quot;; binding.pry
&lt;/span&gt; 
 name = &quot;postgresql&quot;
 pg = Formula[name]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I tried to run the command, hoping to pop a Pry session.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew postgresql-upgrade-database
Error: cannot load such file &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; pry-byebug
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;require&lt;span class=&quot;s1&quot;&gt;&apos;
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require&apos;&lt;/span&gt;
/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb:6:in &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&amp;lt;top &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;required&lt;span class=&quot;o&quot;&gt;)&amp;gt;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require&apos;&lt;/span&gt;
/usr/local/Homebrew/Library/Homebrew/vendor/portable-ruby/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;require&lt;span class=&quot;s1&quot;&gt;&apos;
/usr/local/Homebrew/Library/Homebrew/utils.rb:82:in `require?&apos;&lt;/span&gt;
/usr/local/Homebrew/Library/Homebrew/brew.rb:108:in &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&amp;lt;main&amp;gt;&lt;span class=&quot;s1&quot;&gt;&apos;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;😔 Well, crap.&lt;/p&gt;

&lt;h3 id=&quot;a-second-successful-attempt&quot;&gt;A second, successful attempt!&lt;/h3&gt;

&lt;p&gt;On a lark, I &lt;a href=&quot;https://github.com/Homebrew/brew/search?q=pry&amp;amp;unscoped_q=pry&quot;&gt;searched the Homebrew code base for references to Pry&lt;/a&gt;, hoping for an Issue or someone else talking about this.
And it turns out, Homebrew has &lt;a href=&quot;https://github.com/Homebrew/brew/pull/3851/files&quot;&gt;some Pry support built it&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; #:  * `postgresql-upgrade-database`:
 #:    Upgrades the database for the `postgresql` formula.
&lt;span class=&quot;gi&quot;&gt;+ 
+ Homebrew.install_gem_setup_path! &quot;pry&quot;
+ Homebrew.install_gem_setup_path! &quot;pry-byebug&quot;, executable: &quot;pry&quot;
+ require &quot;pry-byebug&quot;; binding.pry
&lt;/span&gt; 
 name = &quot;postgresql&quot;
 pg = Formula[name]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Leveraging that prior work I was able to get a full Pry Byebug session to pop.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew postgresql-upgrade-database

From: /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/cmd/brew-postgresql-upgrade-database.rb @ line 10 :

     5: &lt;span class=&quot;c&quot;&gt;#:                    files. Same as the pg_ctl -D flag.&lt;/span&gt;
     6: Homebrew.install_gem_setup_path! &lt;span class=&quot;s2&quot;&gt;&quot;pry&quot;&lt;/span&gt;
     7: Homebrew.install_gem_setup_path! &lt;span class=&quot;s2&quot;&gt;&quot;pry-byebug&quot;&lt;/span&gt;, executable: &lt;span class=&quot;s2&quot;&gt;&quot;pry&quot;&lt;/span&gt;
     8: require &lt;span class=&quot;s2&quot;&gt;&quot;pry-byebug&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; binding.pry
     9:
 &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 10: name &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;postgresql&quot;&lt;/span&gt;
    11: pg &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; Formula[name]
    12: bin &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pg.bin
    13: var &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pg.var
    14: version &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; pg.version
    15: data_dir &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; options[:data_dir] &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; var/&lt;span class=&quot;s2&quot;&gt;&quot;postgres&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;🎉 Huzzah!&lt;/p&gt;

&lt;h3 id=&quot;and-then-a-pull-request&quot;&gt;And then, a Pull Request&lt;/h3&gt;

&lt;p&gt;From there it was a matter of stepping through, line by line, to understand how the command worked.
Then some &lt;em&gt;hackity-hacking&lt;/em&gt; to allow an optional data directory to be passed in. 
The net result was a &lt;a href=&quot;https://github.com/Homebrew/homebrew-core/pull/49135&quot;&gt;Pull Request&lt;/a&gt; allowing you to upgrade a PostgreSQL database with a custom data directory.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;brew postgres-upgrade-database &lt;span class=&quot;nt&quot;&gt;--D&lt;/span&gt; ~/.pg-data
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Upgrading postgresql data from 11 to 12...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Moving postgresql data from ~/.pg-data to ~/.pg-data.old...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Creating database...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Migrating and upgrading data...
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Upgraded postgresql data from 11 to 12!
&lt;span class=&quot;o&quot;&gt;==&amp;gt;&lt;/span&gt; Your postgresql 11 data remains at ~/.pg-data.old
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;🤞 Fingers crossed that PR is merged.&lt;/p&gt;

&lt;p&gt;Either way we (or at least I) learned how to debug Homebrew itself.
&lt;em&gt;Neat&lt;/em&gt;.&lt;/p&gt;
</description>
        <pubDate>Fri, 17 Jan 2020 21:00:01 +0000</pubDate>
        <link>https://stevenharman.net/debugging-homebrew-with-pry-byebug</link>
        <guid isPermaLink="true">https://stevenharman.net/debugging-homebrew-with-pry-byebug</guid>
        
        <category>ruby</category>
        
        <category>homebrew</category>
        
        
      </item>
    
      <item>
        <title>In Search Of… Enumerable#transform for Ruby</title>
        <description>&lt;p&gt;&lt;em&gt;In Search Of…&lt;/em&gt; a Ruby method with the semantics of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#map&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#inject&lt;/code&gt;.
That is, to transform values while also having access to the prior iteration return value.
Sounds odd, I know.&lt;/p&gt;

&lt;p&gt;Given the following “value object”&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Snapshot&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;initialize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;vi&quot;&gt;@value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;vi&quot;&gt;@value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I want to build an array of snapshots, based on some set of inputs.
Meaning, I &lt;em&gt;think&lt;/em&gt; I’d like something that looks like this&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Resulting in this&lt;/p&gt;

&lt;div class=&quot;language-irb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#=&amp;gt; [#&amp;lt;Snapshot:0x00007fe550d12558 @value=1&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12508 @value=3&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d124e0 @value=6&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d124b8 @value=10&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12468 @value=15&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12440 @value=21&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12418 @value=28&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d123c8 @value=36&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d123a0 @value=45&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12378 @value=55&amp;gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Right now, as of Ruby 2.5, I don’t know of a clean way of doing that.
&lt;em&gt;Clean&lt;/em&gt; being in the eye of the beholder, I suppose.
It can be done with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#map&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#inject&lt;/code&gt;, but it ain’t pretty.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h4 id=&quot;enumerablemap&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#map&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;Here we set an initial value, and re-assign it in each iteration.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;snap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Which works, but setting up the initial value only so we can re-assign it a new value each iteration feels odd.&lt;/p&gt;

&lt;h4 id=&quot;enumerableinject&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#inject&lt;/code&gt;&lt;/h4&gt;

&lt;p&gt;In this case we use an awkward initial setup (pre-calculating the first iteration), and juggle the real value in the block.&lt;/p&gt;

&lt;p&gt;🎩 &lt;em&gt;Hat tip to &lt;a href=&quot;https://twitter.com/al2o3cr&quot;&gt;Matt Jones&lt;/a&gt; for this one.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;inject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;snaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;snaps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On the up side, both options product the following, desired output!&lt;/p&gt;

&lt;div class=&quot;language-irb highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#=&amp;gt; [#&amp;lt;Snapshot:0x00007fe550d12558 @value=1&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12508 @value=3&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d124e0 @value=6&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d124b8 @value=10&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12468 @value=15&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12440 @value=21&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12418 @value=28&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d123c8 @value=36&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d123a0 @value=45&amp;gt;,
#=&amp;gt;  #&amp;lt;Snapshot:0x00007fe550d12378 @value=55&amp;gt;]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;enumerabletransform-maybe-&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#transform&lt;/code&gt;, maybe? 🤔&lt;/h4&gt;

&lt;p&gt;So, how about that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#transform&lt;/code&gt; I mentioned?
Well, it’s completely made up.
By me.
Just now.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m not particularly sold on the name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#transform&lt;/code&gt;, nor the other particulars.
Perhaps a new method isn’t even needed?
An optional argument to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#map&lt;/code&gt; might be sufficient?&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Snapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;prev_snap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This would mean there’d need to be a default initial value, of course.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nil&lt;/code&gt;, perhaps?
Or maybe follow &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#inject&lt;/code&gt;’s lead and if no initial value is provided, use the first value of the collection?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo.
&lt;cite&gt;&lt;a href=&quot;http://ruby-doc.org/core-2.5.3/Enumerable.html#method-i-inject&quot;&gt;Ruby Docs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enumerable#inject&lt;/code&gt;&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;or-maybe-not-&quot;&gt;Or… maybe not? 🤷&lt;/h4&gt;

&lt;p&gt;I’m not sure if this is needed.
Nor how useful it would be.&lt;/p&gt;

&lt;p&gt;What I do know is I reached for something like this today, and to my surprise, it wasn’t there.
I’ve become so accustomed to Ruby (sometimes with the aide of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport&lt;/code&gt;) having just the ditty I need, that I’m surprised when it doesn’t.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The goal of Ruby is to make programmers happy. I started out to make a programming language that would make me happy, and as a side effect it’s made many, many programmers happy.&lt;/p&gt;

  &lt;p&gt;I hope to see Ruby help every programmer in the world to be productive, and to enjoy programming, and to be happy. That is the primary purpose of Ruby language.
&lt;cite&gt;Yukihiro “Matz” Matsumoto&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What say you?
A potential addition for Ruby &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vNext&lt;/code&gt;?
Or am I expecting too much programmer happiness from this great language?&lt;/p&gt;
</description>
        <pubDate>Fri, 16 Nov 2018 15:56:01 +0000</pubDate>
        <link>https://stevenharman.net/in-search-of-enumerable-transform-for-ruby</link>
        <guid isPermaLink="true">https://stevenharman.net/in-search-of-enumerable-transform-for-ruby</guid>
        
        <category>ruby</category>
        
        
      </item>
    
      <item>
        <title>Reclaim Your Domain Model from Rails</title>
        <description>&lt;p&gt;&lt;strong&gt;TL,DR;&lt;/strong&gt;
When building an application using Rails, I prefer to keep all my model in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt;.
I reserve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt; for those other things - those not-my-domain-things.
I’d like to explain the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/deep-greens-med.jpg&quot; alt=&quot;Boundaries Amongst the Fields; Deep Greens&quot; class=&quot;img-thumbnail img-thumbnail--right&quot; width=&quot;300&quot; /&gt;
Rails has a history of co-opting names, as happened when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt;
library used the &lt;a href=&quot;http://www.martinfowler.com/eaaCatalog/activeRecord.html&quot; title=&quot;Active Record&quot;&gt;active record pattern&lt;/a&gt; name. A similar
co-opting has happened with the &lt;a href=&quot;http://c2.com/cgi/wiki?ModelViewController&quot; title=&quot;Model View Controller&quot;&gt;MVC pattern&lt;/a&gt; wherein many believe
Rails is an example of the MVC design pattern. In truth, it’s probably closer
to &lt;a href=&quot;http://en.wikipedia.org/wiki/Model_2&quot; title=&quot;MVC Model 2&quot;&gt;MVC Model 2&lt;/a&gt;… but I digress.&lt;/p&gt;

&lt;h3 id=&quot;model-view-whats-that-now&quot;&gt;Model View What’s-that-now?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;MVC&lt;/strong&gt; stands for Model, View, Controller. In Rails-land we know what the
&lt;strong&gt;Controllers&lt;/strong&gt; are. And while we don’t have &lt;strong&gt;Views&lt;/strong&gt; in the way that MVC
meant, we do have view-templates, and we call those our views. The &lt;strong&gt;Model&lt;/strong&gt; is
meant to be all the things it takes to model our problem domain. As applied to
Rails, the &lt;strong&gt;Model&lt;/strong&gt; seems the most misunderstood/misused of the MVC
triumvirate.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Rails gave us the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt; directory, a natural home for our model. Yet,
that was also where Rails dumped all our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::Base&lt;/code&gt;-derived objects.
Combined with the “Skinny Controller, Fat Model” mantra of 2006-ish Rails-land,
that directory had grown full of bloated, tightly coupled, low cohesion
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecored::Base&lt;/code&gt;-derived objects. &lt;em&gt;Our model!&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;models-too-fat&quot;&gt;Models Too Fat?&lt;/h2&gt;

&lt;p&gt;Eventually we realized that shoving all those disparate concerns into a single
class was a Bad Idea™. There was a movement afoot to thin down our now
overly-fat models. So, we re-learned &lt;a href=&quot;http://en.wikipedia.org/wiki/Plain_Old_Java_Object&quot; title=&quot;Plain Old Java Object&quot;&gt;some forgotten lessons&lt;/a&gt; and the use
of POROs became a Good Thing™. We needed to break our domain models down into
smaller, more cohesive objects, but we had a new problem. &lt;em&gt;Where to put these
new files?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As Rails had &lt;em&gt;reserved&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt; directory for
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::Base&lt;/code&gt;-derived objects, we had to find a new home for those
POROs. As luck would have it, Rails had given us a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt; directory, and that
thing was basically deserted. We began breaking up our model and littering it
amongst the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt; directories.&lt;/p&gt;

&lt;p&gt;Our model had become more loosely coupled, with more cohesive parts, but it
wasn’t living together. Why are we forcibly segregating the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::Base&lt;/code&gt; portions from the rest of our model? If it’s a single
domain model, why doesn’t it live together?&lt;/p&gt;

&lt;p&gt;These days I put all the domain-specific objects into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt;
directory. I reserve the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib/&lt;/code&gt; directory for things which could ostensibly be
&lt;a href=&quot;http://rubygems.org/&quot; title=&quot;A package manager for Ruby&quot;&gt;gemified&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;so-many-files-so-many-boundaries&quot;&gt;So. Many. Files. So Many Boundaries.&lt;/h2&gt;

&lt;p&gt;I’ve heard grumblings that having all the domain model living together
results in a lot of files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt; directory, making it hard to
organize and navigate. To this I say, &lt;em&gt;YES, it does.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, I suspect that is a sign of another problem. To wit, &lt;em&gt;do you actually
have multiple domains emerging, which could perhaps be broken out into discrete
apps?&lt;/em&gt; If that is the case, make that boundary explicit and break out a new
application!&lt;/p&gt;

&lt;p&gt;So long as those other domains are small or emerging it seems excessive to
split them out into their own apps. So, what do we do? Why we segregate them
with internal &lt;a href=&quot;http://martinfowler.com/bliki/BoundedContext.html&quot; title=&quot;Bounded Context&quot;&gt;domain boundaries&lt;/a&gt;, of course!&lt;/p&gt;

&lt;h3 id=&quot;an-example-boundary&quot;&gt;An Example Boundary.&lt;/h3&gt;

&lt;p&gt;A lot of apps have a search feature (or set of search features).
Search is a whole domain on its own, and often not germane to the primary
domain. Pull all search concerns into a namespace, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Search::&lt;/code&gt;. To help with
organization, put all files to do with search in a directory of their own. For example,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/search/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/controllers/search/&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/views/search/&lt;/code&gt;, etc…&lt;/p&gt;

&lt;p&gt;With just a few changes we’ve reclaimed the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models/&lt;/code&gt; directory, better
organized our files, and defined some new boundaries within our code base. Ah,
much better.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;
&lt;a href=&quot;http://www.flickr.com/photos/x1brett/8025844399/&quot; title=&quot;Deep Greens - Aerial07&quot;&gt;&lt;em&gt;Deep Greens&lt;/em&gt; image&lt;/a&gt; courtesy of Brett Jordan.
&lt;/small&gt;&lt;/p&gt;

</description>
        <pubDate>Fri, 28 Feb 2014 19:51:47 +0000</pubDate>
        <link>https://stevenharman.net/reclaim-your-domain-model-from-rails</link>
        <guid isPermaLink="true">https://stevenharman.net/reclaim-your-domain-model-from-rails</guid>
        
        <category>ruby</category>
        
        <category>rails</category>
        
        <category>design</category>
        
        
      </item>
    
  </channel>
</rss>
