Repository navigation
Configure ActionCable Redis reconnection retries - #8244
Open
donny-wong wants to merge 1 commit into
Open
donny-wong wants to merge 1 commit into
donny-wong wants to merge 1 commit into
Conversation
When Redis restarted, every MarkUs web process lost its pub/sub subscription and never got it back: ActionCable's Redis adapter defaults to a single, immediate reconnect attempt, which fails while Redis is still starting, after which the listener thread exits for the lifetime of the process. Realtime updates (submission collection, autotest results, PDF splitting, group creation) then silently stop until MarkUs itself is restarted. Set reconnect_attempts to a list of delays so the listener keeps retrying for roughly 19 minutes (enough to cover a host reboot) and resubscribes to all channels once Redis is back. ActionCable passes the same option to the connection it publishes broadcasts on, which would make a broadcast issued during an outage block the calling thread for the whole window. Override the adapter's redis_connector so publishing connections keep a single quick retry and fail fast, while the listener keeps the long schedule. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
donny-wong
force-pushed
the
cable-redis-reconnect-attempts
branch
from
October 29, 2026 17:34
1cb969c to
c1739fd
Compare
donny-wong
marked this pull request as ready for review
October 29, 2026 17:43
Collaborator
Coverage Report for CI Build 40924034898Coverage increased (+0.002%) to 91.065%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
reconnect_attempts: [1, 2, 5, 10, 30, 60, 120, 300, 300, 300]to the Redis adapter config inconfig/cable.yml(development and production).config/initializers/action_cable.rb, which overrides the adapter'sredis_connectorso the connections ActionCable opens for publishing keep a single quick retry, while the subscription listener uses the long schedule above.spec/initializers/action_cable_spec.rbguarding the initializer (no socket is opened, so it runs without Redis).Motivation
After Redis was restarted on
tiny, the running MarkUs processes never re-established their pub/sub subscriptions (redis-cli client listshowedsub=0for every client) and had to be restarted by hand. Realtime updates over ActionCable (submission collection progress, autotest results, PDF splitting, group creation) silently stop in that state while the site otherwise looks healthy.Cause, from the ActionCable 8.1.4 adapter (
lib/action_cable/subscription_adapter/redis.rb):reconnect_attemptsdefaults to1, which is converted to a single retry with a 0 s delay. That retry runs while Redis is still starting, fails, and the listener thread exits.ensure_listener_runninguses@thread ||= Thread.new, so once the thread has exited nothing recreates it. New browser subscriptions cannot revive it either.With a list of delays the listener sleeps 1 s, 2 s, 5 s … between attempts (about 19 minutes total, enough to cover a host reboot or patching window) and, on success, re-issues
SUBSCRIBEfor every channel it previously had. The attempt counter resets after each successful reconnect, so the budget applies per outage. The listener only sleeps between attempts, so a long tail costs nothing.Why the initializer
ActionCable passes the whole cable config, including
reconnect_attempts, toRedis.newfor every connection it opens, andPUBLISHis a retryable command in redis-client. Without the initializer, abroadcastissued while Redis is down blocks the calling thread until Redis returns or the window is exhausted. MarkUs broadcasts from Resque jobs (harmless) but also from three job-status controller actions (ExamTemplatesController,GroupsController,SubmissionsController), where that would pin a Puma thread for up to 19 minutes.The listener wraps its subscribe in
without_reconnectand runs its own retry loop driven by the cable config, so overridingredis_connectoronly affects the publishing side.No gem upgrades are needed:
redis6.0.0 /redis-client0.30.1 already accept an array for this option.Verification (Docker Compose, development)
The reconnect behaviour itself cannot be covered by the test suite: the test environment uses
adapter: test, so the Redis adapter is never loaded there, and the scenario needs a real Redis killed mid-run. To repeat the manual check: subscribe a process throughActionCable.server.pubsub.subscribe,docker compose restart redis, wait ~15 s, and confirmredis-cli client listshowssub≥ 1 again and a broadcast is received; thendocker compose pause redisand confirm abroadcastcall raises within ~2 s instead of blocking.client listagain showed the subscription, and a broadcast sent afterwards was received.broadcastcall raisedRedis::CannotConnectErrorafter 2.0 s (one connect timeout plus one retry). Without the initializer the same call blocked for the whole 114 s outage and returned only when Redis resumed.@reconnect_attempts= the 10-entry list, publisher connection@reconnect_attempts=[0].Deploy note
MarkUs must be restarted once after this is deployed so the processes pick up the new config. After that, a Redis restart no longer requires a MarkUs restart.
Known limitations
300entries) if longer outages are expected.Type of Change
Checklist
Before opening your pull request:
After opening your pull request:
🤖 Generated with Claude Code