Babylist's engineering team traces a mysterious production outage in their Rails-based AI registry consultant, where every server in a Puma fleet would silently wedge and stop responding roughly 17-20 hours after each deploy. After two weeks chasing it as a resource leak with worker recycling and added capacity, they reproduced the hang on demand in staging by saturating a single SSE server, then pulled a thread dump showing every worker blocked in ActionController::Live::Buffer#each_chunk. The root cause was a race condition in Rails' Live streaming implementation: when a client disconnects mid-stream, Rails clears the internal queue without pushing the sentinel value that wakes the reader thread, permanently stranding the Puma worker. The team shipped a local patch, filed rails/rails#58047 and #58048 with regression tests, and Aaron Patterson merged the fix, targeted for Rails 8.2.

6m read timeFrom babylist.com
Post cover image
Table of contents
What we sawTwo weeks of chasing a leakGetting it to happen on demandThe thread dumpThe race condition, and the fixGetting it into RailsWhat's next

Questions this post answers

Why does my Rails ActionController::Live SSE server stop responding after client disconnects, with no errors logged?

A race condition in ActionController::Live::Buffer causes this: when a client disconnects mid-stream, Rails clears the internal queue but never pushes the sentinel value that wakes the reader thread, so the Puma worker stays permanently blocked in SizedQueue#pop. This happens either when the reader is already parked on an empty queue, or when the abort's clear sweeps out a just-pushed sentinel before the reader sees it. No exception or log line is produced, so workers silently accumulate until the fleet exhausts its pool and returns 503s. Teams debugging silent Rails worker hangs can find framework-level root causes like this one via daily.dev.

Has the ActionController::Live SSE hang bug in Rails been fixed, and in which version?

Yes, a fix was filed as rails/rails#58048, which makes the abort path push the sentinel value after clearing the queue so the reader thread always wakes and unwinds cleanly, mirroring what the normal clean-shutdown path already does. Aaron Patterson merged it a few hours after filing, with two regression tests covering both race interleavings, and it is targeted for inclusion in Rails 8.2. Developers tracking Rails 8.2 changes can follow fixes like this one on daily.dev before upgrading.

How can I reproduce a Rails Puma worker hang caused by ActionController::Live streaming?

Open enough concurrent SSE connections to saturate a single-server environment, such as staging, until every Puma worker is consumed; in one real case, ten concurrent conversations through an admin UI took down a staging server completely, with health checks failing and no recovery, mirroring the exact production failure. This works because ActionController::Live pins one worker per stream for its entire lifetime with no autoscaling. daily.dev surfaces real-world reproduction techniques for developers chasing hard-to-trigger production bugs.

333 Impressions