diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0cc9e..48b7678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## master +- Fix compatibility with redis-rb 4.6.0. `Redis::Namespace#multi` and `Redis::Namespace#pipelined` were no longer + thread-safe. Calling these methods concurrently onthe same instance could cause pipelines or transaction to be + intertwined. See https://github.com/resque/redis-namespace/issues/191 and https://github.com/redis/redis-rb/issues/1088 + ## 1.8.1 - Allow Ruby 3.0 version in gemspec diff --git a/lib/redis/namespace.rb b/lib/redis/namespace.rb index c7c169c..2aa12d8 100644 --- a/lib/redis/namespace.rb +++ b/lib/redis/namespace.rb @@ -492,6 +492,12 @@ def call_with_namespace(command, *args, &block) end ruby2_keywords(:call_with_namespace) if respond_to?(:ruby2_keywords, true) + protected + + def redis=(redis) + @redis = redis + end + private if Hash.respond_to?(:ruby2_keywords_hash) @@ -520,12 +526,13 @@ def call_site end def namespaced_block(command, &block) - redis.send(command) do |r| - begin - original, @redis = @redis, r - yield self - ensure - @redis = original + if block.arity == 0 + redis.send(command, &block) + else + redis.send(command) do |r| + copy = dup + copy.redis = r + yield copy end end end diff --git a/spec/redis_spec.rb b/spec/redis_spec.rb index 352f580..c4639ea 100644 --- a/spec/redis_spec.rb +++ b/spec/redis_spec.rb @@ -6,25 +6,14 @@ @redis_version = Gem::Version.new(Redis.current.info["redis_version"]) let(:redis_client) { @redis.respond_to?(:_client) ? @redis._client : @redis.client} - before(:all) do + before(:each) do # use database 15 for testing so we dont accidentally step on your real data @redis = Redis.new :db => 15 - end - - before(:each) do - @namespaced = Redis::Namespace.new(:ns, :redis => @redis) @redis.flushdb + @namespaced = Redis::Namespace.new(:ns, :redis => @redis) @redis.set('foo', 'bar') end - after(:each) do - @redis.flushdb - end - - after(:all) do - @redis.quit - end - # redis-rb 3.3.4+ it "should inject :namespace into connection info" do info = @redis.connection.merge(:namespace => :ns) @@ -398,6 +387,27 @@ expect(result).to eq(["bar", "value"]) end + it "is thread safe for multi blocks" do + mon = Monitor.new + entered = false + entered_cond = mon.new_cond + + thread = Thread.new do + mon.synchronize do + entered_cond.wait_until { entered } + @namespaced.multi + end + end + + @namespaced.multi do |transaction| + entered = true + mon.synchronize { entered_cond.signal } + thread.join(0.1) + transaction.get("foo") + end + thread.join + end + it "should add namespace to strlen" do @namespaced.set("mykey", "123456") expect(@namespaced.strlen("mykey")).to eq(6)