redis/redis-rb · error · Redis::SubscriptionError
This client is already subscribed
Error message
This client is already subscribed
What it means
One Redis instance supports at most one active subscription. _subscription raises Redis::SubscriptionError 'This client is already subscribed' when a new subscribe/psubscribe/subscribe_with_timeout block starts while @subscription_client is still set — i.e. a previous subscription loop on this same instance has not finished (the block form sets it for the loop's duration and clears it in an ensure).
Source
Thrown at lib/redis.rb:302
if @options.fetch(:protocol, 3).to_i == 3 && Client.resp3_unsupported?(error)
@options = @options.merge(protocol: 2)
@client.close
@client = build_client
# Warn only once the RESP2 client is actually in place — if the rebuild itself raises we
# haven't really fallen back. Fires once per client: @options[:protocol] is now 2, so this
# branch never re-enters. Passing `protocol: 2` explicitly skips it (and silences this).
warn("Redis: the server does not support RESP3 (the HELLO 3 handshake failed); falling back " \
"to RESP2. Pass `protocol: 2` to select RESP2 explicitly and silence this warning.")
retry
end
raise
end
def _subscription(method, timeout, channels, block)
if block
if @subscription_client
raise SubscriptionError, "This client is already subscribed"
end
begin
# The pub/sub second socket is opened via @client.pubsub, which connects through
# ensure_connected rather than a command path. Route it through #synchronize so the same
# RESP3->RESP2 fallback applies when subscribe is the first operation against an old server.
@subscription_client = SubscribedClient.new(synchronize(&:pubsub))
if timeout > 0
@subscription_client.send(method, timeout, *channels, &block)
else
@subscription_client.send(method, *channels, &block)
end
ensure
@subscription_client&.close
@subscription_client = nil
end
else
unless @subscription_clientView on GitHub (pinned to 2ba9010b91)
Solutions
- Give each concurrent subscription its own Redis instance (Redis.new with the same URL)
- Subscribe once to all channels in a single call — the block receives messages from every channel
- Sequence subscriptions on one client: wait for the previous block to return (it exits after unsubscribe/timeout) before starting the next
Example fix
# before
redis.subscribe('news') do |on|
on.message { |_, msg| redis.subscribe('alerts') { } } # nested: raises already subscribed
end
# after
redis.subscribe('news', 'alerts') do |on|
on.message { |channel, msg| handle(channel, msg) }
end Defensive patterns
Strategy: validation
Validate before calling
SUB_LOCK = Mutex.new def subscribe_exclusive(redis, *channels, &block) raise Redis::SubscriptionError, 'another subscription is active' unless SUB_LOCK.try_lock redis.subscribe(*channels, &block) ensure SUB_LOCK.unlock if SUB_LOCK.owned? end
Try / catch
begin
redis.subscribe('ch') { |on| on.message { |_, m| handle(m) } }
rescue Redis::SubscriptionError
subscriber = Redis.new(url: redis.connection[:host] ? config_url : config_url)
subscriber.subscribe('ch') { |on| on.message { |_, m| handle(m) } }
end Prevention
- Use one dedicated Redis instance per concurrent subscription
- Never call subscribe/psubscribe from inside a subscription block on the same client
- Subscribe to all needed channels in one call — the block receives every message
When it happens
Trigger: Calling redis.subscribe (or psubscribe, subscribe_with_timeout, ssubscribe) from inside another subscription block on the same instance, or from a second thread while a subscription loop is still running on the same Redis object.
Common situations: A background thread runs redis.subscribe { ... } while the main thread subscribes on the same shared client; pub/sub fan-out code that starts overlapping subscriptions; Rails/Sidekiq apps sharing one global Redis constant for both commands and subscriptions.
Related errors
AI-assisted analysis of redis/redis-rb@2ba9010b91 (2026-08-23).
Data as JSON: /api/errors/ca13a9217ee562ab.
Report an issue: GitHub.