Summary
When a terminal window is closed while running a Rails server with the Solid Queue Puma plugin enabled, the Solid Queue supervisor and worker processes are not cleaned up and continue holding the port, preventing the server from being restarted.
Steps to Reproduce
rails new repro --api
cd repro
RAILS_ENV=production SOLID_QUEUE_IN_PUMA=1 ./bin/rails s
# Close terminal window (do not use Ctrl-C)
lsof -i :3000
RAILS_ENV=production SOLID_QUEUE_IN_PUMA=1 ./bin/rails s # Fails because port is in use
Expected Behavior
All Solid Queue processes exit when the terminal is closed and the port is freed.
Actual Behavior
The Solid Queue supervisor and worker processes remain running and hold the port. The server cannot be restarted without manually killing them:
$ lsof -i :3000
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ruby 72516 brad 9u IPv4 ... 0t0 TCP localhost:hbci (LISTEN)
ruby 72521 brad 9u IPv4 ... 0t0 TCP localhost:hbci (LISTEN)
ruby 72522 brad 9u IPv4 ... 0t0 TCP localhost:hbci (LISTEN)
ruby 72523 brad 9u IPv4 ... 0t0 TCP localhost:hbci (LISTEN)
Root Cause
When the terminal is closed, the PTY is torn down. The monitor_puma thread in the Puma plugin correctly detects that Puma has died by observing Process.ppid change to 1, and calls log before sending Process.kill(:INT, $$). However, log_writer.log attempts to write to STDOUT, which raises Errno::EIO because the PTY no longer exists. This exception bubbles up and prevents Process.kill from ever being reached, leaving the supervisor and its children running indefinitely.
Because the terminal is already closed, this error is silently swallowed. Adding explicit error logging to a file revealed the cause:
[2026-04-14 11:44:12 -0400] Failed to write to Puma log: Errno::EIO Input/output error @ rb_sys_fail_on_write - "Detected Puma has gone away, stopping Solid Queue.."
The relevant code in lib/puma/plugin/solid_queue.rb:
def monitor(process_dead, message)
loop do
if send(process_dead)
log message # <-- raises Errno::EIO, Process.kill never reached
Process.kill(:INT, $$)
break
end
sleep 2
end
end
def log(...)
log_writer.log(...) # <-- Errno::EIO when PTY is torn down
end
Fix
Rescue Errno::EIO in the log method so that the kill signal is always reached:
def log(*args)
log_writer.log(*args)
rescue Errno::EIO
# Terminal is gone, ignore logging errors during shutdown
end
Note: There may be other relevant errors that should be handled, such as Errno::EPIPE or Errno::EBADF, but I haven't managed to produce them in my testing.
Environment
- macOS, Ubuntu (with tmux)
- Puma 8.0.0
- solid_queue 1.4.0
Summary
When a terminal window is closed while running a Rails server with the Solid Queue Puma plugin enabled, the Solid Queue supervisor and worker processes are not cleaned up and continue holding the port, preventing the server from being restarted.
Steps to Reproduce
Expected Behavior
All Solid Queue processes exit when the terminal is closed and the port is freed.
Actual Behavior
The Solid Queue supervisor and worker processes remain running and hold the port. The server cannot be restarted without manually killing them:
Root Cause
When the terminal is closed, the PTY is torn down. The
monitor_pumathread in the Puma plugin correctly detects that Puma has died by observingProcess.ppidchange to 1, and callslogbefore sendingProcess.kill(:INT, $$). However,log_writer.logattempts to write to STDOUT, which raisesErrno::EIObecause the PTY no longer exists. This exception bubbles up and preventsProcess.killfrom ever being reached, leaving the supervisor and its children running indefinitely.Because the terminal is already closed, this error is silently swallowed. Adding explicit error logging to a file revealed the cause:
The relevant code in
lib/puma/plugin/solid_queue.rb:Fix
Rescue
Errno::EIOin thelogmethod so that the kill signal is always reached:Note: There may be other relevant errors that should be handled, such as
Errno::EPIPEorErrno::EBADF, but I haven't managed to produce them in my testing.Environment