Merge pull request #43718 from esparta/fix_race_conditions_test_cache_v

ActiveSupport::Cache, fix race conditions on test/cache - part V
This commit is contained in:
Rafael Mendonça França 2021-11-29 16:51:21 -05:00 committed by GitHub
commit be3ed75ec7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 86 deletions

View File

@ -2,15 +2,16 @@
module LocalCacheBehavior
def test_instrumentation_with_local_cache
key = SecureRandom.uuid
events = with_instrumentation "write" do
@cache.write("foo", "bar")
@cache.write(key, SecureRandom.uuid)
end
assert_equal @cache.class.name, events[0].payload[:store]
@cache.with_local_cache do
events = with_instrumentation "read" do
@cache.read("foo")
@cache.read("foo")
@cache.read(key)
@cache.read(key)
end
expected = [@cache.class.name, @cache.send(:local_cache).class.name]
@ -19,21 +20,24 @@ module LocalCacheBehavior
end
def test_local_writes_are_persistent_on_the_remote_cache
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
retval = @cache.with_local_cache do
@cache.write("foo", "bar")
@cache.write(key, value)
end
assert retval
assert_equal "bar", @cache.read("foo")
assert_equal value, @cache.read(key)
end
def test_clear_also_clears_local_cache
key = SecureRandom.uuid
@cache.with_local_cache do
@cache.write("foo", "bar")
@cache.write(key, SecureRandom.alphanumeric)
@cache.clear
assert_nil @cache.read("foo")
assert_nil @cache.read(key)
end
assert_nil @cache.read("foo")
assert_nil @cache.read(key)
end
def test_cleanup_clears_local_cache_but_not_remote_cache
@ -43,79 +47,98 @@ module LocalCacheBehavior
skip
end
@cache.with_local_cache do
@cache.write("foo", "bar")
assert_equal "bar", @cache.read("foo")
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
other_value = SecureRandom.alphanumeric
@cache.send(:bypass_local_cache) { @cache.write("foo", "baz") }
assert_equal "bar", @cache.read("foo")
@cache.with_local_cache do
@cache.write(key, value)
assert_equal value, @cache.read(key)
@cache.send(:bypass_local_cache) { @cache.write(key, other_value) }
assert_equal value, @cache.read(key)
@cache.cleanup
assert_equal "baz", @cache.read("foo")
assert_equal other_value, @cache.read(key)
end
end
def test_local_cache_of_write
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.write("foo", "bar")
@peek.delete("foo")
assert_equal "bar", @cache.read("foo")
@cache.write(key, value)
@peek.delete(key)
assert_equal value, @cache.read(key)
end
end
def test_local_cache_of_read_returns_a_copy_of_the_entry
key = SecureRandom.alphanumeric.to_sym
value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.write(:foo, type: "bar")
value = @cache.read(:foo)
assert_equal("bar", value.delete(:type))
assert_equal({ type: "bar" }, @cache.read(:foo))
@cache.write(key, type: value)
local_value = @cache.read(key)
assert_equal(value, local_value.delete(:type))
assert_equal({ type: value }, @cache.read(key))
end
end
def test_local_cache_of_read
@cache.write("foo", "bar")
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.write(key, value)
@cache.with_local_cache do
assert_equal "bar", @cache.read("foo")
assert_equal value, @cache.read(key)
end
end
def test_local_cache_of_read_nil
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
assert_nil @cache.read("foo")
@cache.send(:bypass_local_cache) { @cache.write "foo", "bar" }
assert_nil @cache.read("foo")
assert_nil @cache.read(key)
@cache.send(:bypass_local_cache) { @cache.write(key, value) }
assert_nil @cache.read(key)
end
end
def test_local_cache_fetch
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.send(:local_cache).write_entry "foo", "bar"
assert_equal "bar", @cache.send(:local_cache).fetch_entry("foo")
@cache.send(:local_cache).write_entry(key, value)
assert_equal value, @cache.send(:local_cache).fetch_entry(key)
end
end
def test_local_cache_of_write_nil
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
assert @cache.write("foo", nil)
assert_nil @cache.read("foo")
@peek.write("foo", "bar")
assert_nil @cache.read("foo")
assert @cache.write(key, nil)
assert_nil @cache.read(key)
@peek.write(key, value)
assert_nil @cache.read(key)
end
end
def test_local_cache_of_write_with_unless_exist
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.write("foo", "bar")
@cache.write("foo", "baz", unless_exist: true)
assert_equal @peek.read("foo"), @cache.read("foo")
@cache.write(key, value)
@cache.write(key, SecureRandom.alphanumeric, unless_exist: true)
assert_equal @peek.read(key), @cache.read(key)
end
end
def test_local_cache_of_delete
key = SecureRandom.uuid
@cache.with_local_cache do
@cache.write("foo", "bar")
@cache.delete("foo")
assert_nil @cache.read("foo")
@cache.write(key, SecureRandom.alphanumeric)
@cache.delete(key)
assert_nil @cache.read(key)
end
end
@ -126,94 +149,112 @@ module LocalCacheBehavior
skip
end
prefix = SecureRandom.alphanumeric
key = "#{prefix}#{SecureRandom.uuid}"
other_key = "#{prefix}#{SecureRandom.uuid}"
third_key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.write("foo", "bar")
@cache.write("fop", "bar")
@cache.write("bar", "foo")
@cache.delete_matched("fo*")
assert_not @cache.exist?("foo")
assert_not @cache.exist?("fop")
assert_equal "foo", @cache.read("bar")
@cache.write(key, SecureRandom.alphanumeric)
@cache.write(other_key, SecureRandom.alphanumeric)
@cache.write(third_key, value)
@cache.delete_matched("#{prefix}*")
assert_not @cache.exist?(key)
assert_not @cache.exist?(other_key)
assert_equal value, @cache.read(third_key)
end
end
def test_local_cache_of_exist
key = SecureRandom.uuid
@cache.with_local_cache do
@cache.write("foo", "bar")
@peek.delete("foo")
assert @cache.exist?("foo")
@cache.write(key, SecureRandom.alphanumeric)
@peek.delete(key)
assert @cache.exist?(key)
end
end
def test_local_cache_of_increment
key = SecureRandom.uuid
@cache.with_local_cache do
@cache.write("foo", 1, raw: true)
@peek.write("foo", 2, raw: true)
@cache.increment("foo")
@cache.write(key, 1, raw: true)
@peek.write(key, 2, raw: true)
@cache.increment(key)
expected = @peek.read("foo", raw: true)
expected = @peek.read(key, raw: true)
assert_equal 3, Integer(expected)
assert_equal expected, @cache.read("foo", raw: true)
assert_equal expected, @cache.read(key, raw: true)
end
end
def test_local_cache_of_decrement
key = SecureRandom.uuid
@cache.with_local_cache do
@cache.write("foo", 1, raw: true)
@peek.write("foo", 3, raw: true)
@cache.write(key, 1, raw: true)
@peek.write(key, 3, raw: true)
@cache.decrement("foo")
expected = @peek.read("foo", raw: true)
@cache.decrement(key)
expected = @peek.read(key, raw: true)
assert_equal 2, Integer(expected)
assert_equal expected, @cache.read("foo", raw: true)
assert_equal expected, @cache.read(key, raw: true)
end
end
def test_local_cache_of_fetch_multi
key = SecureRandom.uuid
other_key = SecureRandom.uuid
@cache.with_local_cache do
@cache.fetch_multi("foo", "bar") { |_key| true }
@peek.delete("foo")
@peek.delete("bar")
assert_equal true, @cache.read("foo")
assert_equal true, @cache.read("bar")
@cache.fetch_multi(key, other_key) { |_key| true }
@peek.delete(key)
@peek.delete(other_key)
assert_equal true, @cache.read(key)
assert_equal true, @cache.read(other_key)
end
end
def test_local_cache_of_read_multi
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
other_key = SecureRandom.uuid
other_value = SecureRandom.alphanumeric
@cache.with_local_cache do
@cache.write("foo", "foo", raw: true)
@cache.write("bar", "bar", raw: true)
values = @cache.read_multi("foo", "bar", raw: true)
assert_equal "foo", @cache.read("foo", raw: true)
assert_equal "bar", @cache.read("bar", raw: true)
assert_equal "foo", values["foo"]
assert_equal "bar", values["bar"]
@cache.write(key, value, raw: true)
@cache.write(other_key, other_value, raw: true)
values = @cache.read_multi(key, other_key, raw: true)
assert_equal value, @cache.read(key, raw: true)
assert_equal other_value, @cache.read(other_key, raw: true)
assert_equal value, values[key]
assert_equal other_value, values[other_key]
end
end
def test_initial_object_mutation_after_write
key = SecureRandom.uuid
@cache.with_local_cache do
initial = +"bar"
@cache.write("foo", initial)
@cache.write(key, initial)
initial << "baz"
assert_equal "bar", @cache.read("foo")
assert_equal "bar", @cache.read(key)
end
end
def test_initial_object_mutation_after_fetch
key = SecureRandom.uuid
@cache.with_local_cache do
initial = +"bar"
@cache.fetch("foo") { initial }
@cache.fetch(key) { initial }
initial << "baz"
assert_equal "bar", @cache.read("foo")
assert_equal "bar", @cache.fetch("foo")
assert_equal "bar", @cache.read(key)
assert_equal "bar", @cache.fetch(key)
end
end
def test_middleware
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
app = lambda { |env|
result = @cache.write("foo", "bar")
assert_equal "bar", @cache.read("foo") # make sure 'foo' was written
result = @cache.write(key, value)
assert_equal value, @cache.read(key) # make sure 'foo' was written
assert result
[200, {}, []]
}
@ -222,23 +263,27 @@ module LocalCacheBehavior
end
def test_local_race_condition_protection
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
other_value = SecureRandom.alphanumeric
@cache.with_local_cache do
time = Time.now
@cache.write("foo", "bar", expires_in: 60)
@cache.write(key, value, expires_in: 60)
Time.stub(:now, time + 61) do
result = @cache.fetch("foo", race_condition_ttl: 10) do
assert_equal "bar", @cache.read("foo")
"baz"
result = @cache.fetch(key, race_condition_ttl: 10) do
assert_equal value, @cache.read(key)
other_value
end
assert_equal "baz", result
assert_equal other_value, result
end
end
end
def test_local_cache_should_read_and_write_false
key = SecureRandom.uuid
@cache.with_local_cache do
assert @cache.write("foo", false)
assert_equal false, @cache.read("foo")
assert @cache.write(key, false)
assert_equal false, @cache.read(key)
end
end
end

View File

@ -79,9 +79,15 @@ class MemCacheStoreTest < ActiveSupport::TestCase
# Overrides test from LocalCacheBehavior in order to stub out the cache clear
# and replace it with a delete.
def test_clear_also_clears_local_cache
key = "#{@namespace}:foo"
key = SecureRandom.uuid
cache = lookup_store(raw: true)
client.stub(:flush_all, -> { client.delete(key) }) do
super
cache.with_local_cache do
cache.write(key, SecureRandom.alphanumeric)
cache.clear
assert_nil cache.read(key)
end
assert_nil cache.read(key)
end
end