From 610ec983753894ad8b8c99bf06b939f12e42a617 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Wed, 29 Apr 2026 16:24:03 +0300 Subject: [PATCH 1/2] Ignore closed terminal log errors in Puma plugin --- lib/puma/plugin/solid_queue.rb | 3 +++ test/unit/puma_plugin_test.rb | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 test/unit/puma_plugin_test.rb diff --git a/lib/puma/plugin/solid_queue.rb b/lib/puma/plugin/solid_queue.rb index 8a7aea28b..2285fd14c 100644 --- a/lib/puma/plugin/solid_queue.rb +++ b/lib/puma/plugin/solid_queue.rb @@ -129,5 +129,8 @@ def puma_dead? def log(...) log_writer.log(...) + rescue Errno::EIO + # The controlling terminal can disappear before the monitor thread shuts + # down the child process. Keep shutdown moving even when logging cannot. end end diff --git a/test/unit/puma_plugin_test.rb b/test/unit/puma_plugin_test.rb new file mode 100644 index 000000000..710b01b40 --- /dev/null +++ b/test/unit/puma_plugin_test.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "test_helper" +require "puma/plugin/solid_queue" + +class PumaPluginTest < ActiveSupport::TestCase + class ClosedTerminalLogWriter + def log(...) + raise Errno::EIO + end + end + + test "monitor still stops the process when shutdown logging fails" do + plugin = Puma::Plugins.find("solid_queue").new + plugin.instance_variable_set(:@log_writer, ClosedTerminalLogWriter.new) + + plugin.stubs(:puma_dead?).returns(true) + Process.expects(:kill).with(:INT, Process.pid) + + plugin.send(:monitor, :puma_dead?, "Detected Puma has gone away, stopping Solid Queue...") + end +end From 6b4c0c9e0116f7718c01951af0085544d74bf7c5 Mon Sep 17 00:00:00 2001 From: Rosa Gutierrez Date: Sun, 30 Aug 2026 19:16:46 +0200 Subject: [PATCH 2/2] Keep shutting down when the plugin's output pipe is gone too A closed terminal raises Errno::EIO, but the same monitor thread can also find its output gone as EPIPE (stdout piped to a reader that exited) or EBADF (stream already closed during shutdown). Treat the three the same way, and cover each in the test. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01CACej2M9mLVDwpE3Bk8V41 --- lib/puma/plugin/solid_queue.rb | 7 ++++--- test/unit/puma_plugin_test.rb | 22 ++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/puma/plugin/solid_queue.rb b/lib/puma/plugin/solid_queue.rb index 2285fd14c..3fff5d417 100644 --- a/lib/puma/plugin/solid_queue.rb +++ b/lib/puma/plugin/solid_queue.rb @@ -129,8 +129,9 @@ def puma_dead? def log(...) log_writer.log(...) - rescue Errno::EIO - # The controlling terminal can disappear before the monitor thread shuts - # down the child process. Keep shutdown moving even when logging cannot. + rescue Errno::EIO, Errno::EPIPE, Errno::EBADF + # The controlling terminal can disappear, or the output pipe close, before + # the monitor thread shuts down the child process. Keep shutdown moving + # even when logging cannot. end end diff --git a/test/unit/puma_plugin_test.rb b/test/unit/puma_plugin_test.rb index 710b01b40..24325ef8f 100644 --- a/test/unit/puma_plugin_test.rb +++ b/test/unit/puma_plugin_test.rb @@ -4,19 +4,25 @@ require "puma/plugin/solid_queue" class PumaPluginTest < ActiveSupport::TestCase - class ClosedTerminalLogWriter + class ClosedOutputLogWriter + def initialize(error) + @error = error + end + def log(...) - raise Errno::EIO + raise @error end end - test "monitor still stops the process when shutdown logging fails" do - plugin = Puma::Plugins.find("solid_queue").new - plugin.instance_variable_set(:@log_writer, ClosedTerminalLogWriter.new) + [ Errno::EIO, Errno::EPIPE, Errno::EBADF ].each do |error| + test "monitor still stops the process when shutdown logging fails with #{error}" do + plugin = Puma::Plugins.find("solid_queue").new + plugin.instance_variable_set(:@log_writer, ClosedOutputLogWriter.new(error)) - plugin.stubs(:puma_dead?).returns(true) - Process.expects(:kill).with(:INT, Process.pid) + plugin.stubs(:puma_dead?).returns(true) + Process.expects(:kill).with(:INT, Process.pid) - plugin.send(:monitor, :puma_dead?, "Detected Puma has gone away, stopping Solid Queue...") + plugin.send(:monitor, :puma_dead?, "Detected Puma has gone away, stopping Solid Queue...") + end end end