Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concurrency issue in redis-rb 4.6.0 (using redis-namespace) #1088

Closed
sergio91pt opened this issue Mar 4, 2022 · 5 comments · Fixed by resque/redis-namespace#192
Closed

Comments

@sergio91pt
Copy link

sergio91pt commented Mar 4, 2022

With the upgrade of redis-rb from 4.5.1 to 4.6.0 we started observing ocasional odd behavior under load:

A non-pipelined redis.evalsha(sha, keys, args) returning a Redis::Future instead of a String causing an error when the application tries to parse it.

And occasionally at the same time but in a different thread we noticed a misbehavior when a pipelined exists? wrongly evaluates to true.

(open, half_open) = execute_redis_pipeline { |pipeline|
  pipeline.exists?(open_key(name))
  pipeline.exists?(half_open_key(name))
}
# Both should be false according to our observability metrics

# For reference:
def execute_redis_command
  yield
rescue Redis::BaseError => e
  raise CircuitBreakerError.new(e.message, e)
end

def execute_redis_pipeline(&block)
  execute_redis_command { redis.pipelined(&block) }
end

Although relatively rare they seem related with the redis-rb gem upgrade, since we updated our pipelines and sidekiq dependency to avoid deprecation warnings 1 week before upgrading redis-rb, without problem.

Using:

Ruby 3.0.1
connection_pool (2.2.5)
redis (4.6.0) (default driver, w/ redis sentinel, no cluster)
redis-namespace (1.8.1)
sidekiq (6.4.1)
sidekiq-pro (5.3.1)
newrelic_rpm (7.2.0)
@byroot byroot changed the title Concurrency issue in redis-rb 1.6.0 Concurrency issue in redis-rb 4.6.0 Mar 4, 2022
@byroot
Copy link
Collaborator

byroot commented Mar 4, 2022

returning a Redis::Future instead of a String causing an error when the application tries to parse it.

That would suggest that the ensure block wouldn't be executed somehow:

redis-rb/lib/redis.rb

Lines 171 to 172 in 610c783

ensure
@client = prior_client

Do you use Timeout.timeout?

I'd suggest something to your connection pool to make sure redis.instance_variable_get(:@client) is not a pipeline, that should help pinpoint where the leak happens.

@sergio91pt
Copy link
Author

Thanks for the quick response.
We don't use Timeout.timeout and a quick look at the connection_pool code, it also isn't used there, only ConditionVariable#wait.

@byroot
Copy link
Collaborator

byroot commented Mar 4, 2022

It's possible some other gem might use it. Either way, short term I really recomend the modified connection pool, both to find the source of the problem, and to negate it's impact.

Basically when the connection is checked back in the pool, you want to inspect it to make sure a pipeline didn't leak.

I reviewed the code, and there's no fundamental change on how pipelined is handled, so I don't see anything specific to 4.6.0 that could cause this. But if you do find some more clues, I'm all ears.

casperisfine pushed a commit to casperisfine/redis-namespace that referenced this issue Mar 7, 2022
Fix: resque#191
Fix: redis/redis-rb#1088

In redis-rb 4.6.0, the object yielded by `multi` and `pipelined`
is an unsynchronized `PipelinedConnection` object.

This changed opened a thread safety issue in Redis::Namespace:

  - T1: `redis.multi do`, sets `Namespace#redis = PipelinedConnection`
  - T2: any command called before T1 exists the `multi` block is called on the pipelined connection
@casperisfine
Copy link

Most of the discussion on this issue actually happened on an unrelated one: #1069 (comment)

casperisfine pushed a commit to casperisfine/redis-namespace that referenced this issue Mar 7, 2022
Fix: resque#191
Fix: redis/redis-rb#1088

In redis-rb 4.6.0, the object yielded by `multi` and `pipelined`
is an unsynchronized `PipelinedConnection` object.

This changed opened a thread safety issue in Redis::Namespace:

  - T1: `redis.multi do`, sets `Namespace#redis = PipelinedConnection`
  - T2: any command called before T1 exists the `multi` block is called on the pipelined connection
casperisfine pushed a commit to casperisfine/redis-namespace that referenced this issue Mar 7, 2022
Fix: resque#191
Fix: redis/redis-rb#1088

In redis-rb 4.6.0, the object yielded by `multi` and `pipelined`
is an unsynchronized `PipelinedConnection` object.

This changed opened a thread safety issue in Redis::Namespace:

  - T1: `redis.multi do`, sets `Namespace#redis = PipelinedConnection`
  - T2: any command called before T1 exists the `multi` block is called on the pipelined connection
casperisfine pushed a commit to casperisfine/redis-namespace that referenced this issue Mar 7, 2022
Fix: resque#191
Fix: redis/redis-rb#1088

In redis-rb 4.6.0, the object yielded by `multi` and `pipelined`
is an unsynchronized `PipelinedConnection` object.

This changed opened a thread safety issue in Redis::Namespace:

  - T1: `redis.multi do`, sets `Namespace#redis = PipelinedConnection`
  - T2: any command called before T1 exists the `multi` block is called on the pipelined connection
@byroot byroot changed the title Concurrency issue in redis-rb 4.6.0 Concurrency issue in redis-rb 4.6.0 (using redis-namespace) Mar 7, 2022
@byroot
Copy link
Collaborator

byroot commented Mar 7, 2022

redis-namespace 1.8.2 is out: https://rubygems.org/gems/redis-namespace/versions/1.8.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants