HerokuEthan LimchaysengIncident / postmortem
The Adventures of Rendezvous in Heroku’s New Architecture
Summary
Heroku’s migration of Rendezvous, the bidirectional proxy that connects Heroku Run sessions to one-off dynos, from EC2 Classic to AWS VPCs caused one customer’s app to hit H12 timeouts. The move replaced a single Elastic IP ingress and process-level TLS termination with a Network Load Balancer, whose larger TLS frames exposed a blocking flaw in Rendezvous’s single-threaded Ruby channel loop. That loop used IO.select on the TCP socket and dyno terminal, but OpenSSL::SSL::SSLSocket could retain unread buffered data after a partial read; IO.select saw no TCP bytes and blocked beyond the Heroku Router’s 30-second limit. Engineers reproduced the behavior across two languages, instrumented the instance agent, then fixed the channel with separate threads and corrected socket-reading and IO.select handling. After staging tests, the customer moved back to VPC Rendezvous and confirmed the issue was resolved.
Context
In July 2021, Heroku moved Rendezvous from EC2 Classic into AWS VPCs. Most Heroku Run sessions continued to work, but a customer that created one-off dynos inside request handling began receiving H12 timeouts because sessions exceeded the Heroku Router’s 30-second response limit.
Approach / What changed
The investigation examined NLB TLS termination, instance types, Ruby versions, and recipient runtimes, then used custom applications and additional logging on the EC2 agent to isolate the failure. The fix made the Rendezvous channel dual-threaded and corrected socket-reading and IO.select handling so buffered OpenSSL data could not cause a prolonged block.
Takeaways
Network Load Balancers provided 5–7x more throughput than the prior EIP approach in Heroku’s internal tests, but their larger TLS frames exposed the existing channel-loop bug.
IO.select monitored the underlying TCP socket while OpenSSL::SSL::SSLSocket could still hold unread data in its own buffer, allowing the loop to block despite readable application data.
The deployed fix used one thread for each connection side and changed socket-read handling; staging validation preceded the customer’s return to the VPC-based Rendezvous path.
Source and freshness
- Source
- heroku.com
- Published
- Added to Yomu
Summaries are generated from the original by a language model. Check the original before relying on it.
Keep reading
Related reading
The Good Stuff
Paid picks we use ourselves. Links carry a referral code — a subscription through one earns us a commission at no extra cost to you.
The best engineering reads, once a week
One useful email. Unsubscribe anytime.