From ff2d34b208771c8a1ef4ea37d607a39d15abe13d Mon Sep 17 00:00:00 2001 From: Sam Giffney Date: Thu, 7 May 2026 07:48:05 +1000 Subject: [PATCH] Memoize Runnable#mode to stop allocating a String + StringInquirer per poll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before 960694e8 (Dec 2023, "Extract Supervised concern from Runnable"), the mode was set once in #start as `@mode = mode.to_s.inquiry` and read back via `attr_reader :mode`. After that commit, the reader was replaced with a method that re-runs `(@mode || DEFAULT_MODE).to_s.inquiry` on every call. `#mode` is invoked in the poll-loop hot path via `running_async?` / `running_as_fork?` / `running_inline?`, themselves called from `shutting_down?` on every iteration of the dispatcher, worker, and scheduler loops. With the default `queue.yml` (1s dispatcher, 0.1s worker), this is ~3600 redundant allocations per hour per process — a String and an ActiveSupport::StringInquirer for a value that doesn't change after the supervisor sets it once. Restoring the original "compute the inquiry at write time, cache it afterwards" semantic via a custom `mode=` writer eliminates the allocations with no behaviour change. The default-mode fallback (used when `mode=` was never called) memoizes on first read of `#mode`. Reproduction harness and validation: https://github.com//solid-queue-idle-memory-repro heapy diff with `GC.start(full_mark: true, immediate_sweep: true)` before each `ObjectSpace.dump_all` confirms the `runnable.rb` line disappears from retained-allocation reports after this change. Full test suite (227 runs, 1299 assertions) passes with no regressions. Refs #262, #330, #405. --- lib/solid_queue/processes/runnable.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/solid_queue/processes/runnable.rb b/lib/solid_queue/processes/runnable.rb index c6e002e4f..7738391cf 100644 --- a/lib/solid_queue/processes/runnable.rb +++ b/lib/solid_queue/processes/runnable.rb @@ -4,7 +4,9 @@ module SolidQueue::Processes module Runnable include Supervised - attr_writer :mode + def mode=(value) + @mode = (value || DEFAULT_MODE).to_s.inquiry + end def start run_in_mode do @@ -33,7 +35,7 @@ def alive? DEFAULT_MODE = :async def mode - (@mode || DEFAULT_MODE).to_s.inquiry + @mode ||= DEFAULT_MODE.to_s.inquiry end def run_in_mode(&block)