Skip to content

Commit 8291666

Browse files
authored
Merge pull request rails#48381 from Shopify/as-cache-strict-arguments
Eagerly validate pool arguments in Redis and MemCache stores
2 parents 5824fd9 + c07812c commit 8291666

File tree

5 files changed

+77
-44
lines changed

5 files changed

+77
-44
lines changed

activesupport/lib/active_support/cache.rb

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -186,35 +186,44 @@ class << self
186186
private_constant :DEFAULT_POOL_OPTIONS
187187

188188
def retrieve_pool_options(options)
189-
unless options.key?(:pool) || options.key?(:pool_size) || options.key?(:pool_timeout)
190-
options[:pool] = true
191-
end
189+
if options.key?(:pool)
190+
pool_options = options.delete(:pool)
191+
elsif options.key?(:pool_size) || options.key?(:pool_timeout)
192+
pool_options = {}
193+
194+
if options.key?(:pool_size)
195+
ActiveSupport.deprecator.warn(<<~MSG)
196+
Using :pool_size is deprecated and will be removed in Rails 7.2.
197+
Use `pool: { size: #{options[:pool_size].inspect} }` instead.
198+
MSG
199+
pool_options[:size] = options.delete(:pool_size)
200+
end
192201

193-
if (pool_options = options.delete(:pool))
194-
if Hash === pool_options
195-
DEFAULT_POOL_OPTIONS.merge(pool_options)
196-
else
197-
DEFAULT_POOL_OPTIONS
202+
if options.key?(:pool_timeout)
203+
ActiveSupport.deprecator.warn(<<~MSG)
204+
Using :pool_timeout is deprecated and will be removed in Rails 7.2.
205+
Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
206+
MSG
207+
pool_options[:timeout] = options.delete(:pool_timeout)
198208
end
199209
else
200-
{}.tap do |pool_options|
201-
if options[:pool_size]
202-
ActiveSupport.deprecator.warn(<<~MSG)
203-
Using :pool_size is deprecated and will be removed in Rails 7.2.
204-
Use `pool: { size: #{options[:pool_size].inspect} }` instead.
205-
MSG
206-
pool_options[:size] = options.delete(:pool_size)
207-
end
208-
209-
if options[:pool_timeout]
210-
ActiveSupport.deprecator.warn(<<~MSG)
211-
Using :pool_timeout is deprecated and will be removed in Rails 7.2.
212-
Use `pool: { timeout: #{options[:pool_timeout].inspect} }` instead.
213-
MSG
214-
pool_options[:timeout] = options.delete(:pool_timeout)
215-
end
216-
end
210+
pool_options = true
217211
end
212+
213+
case pool_options
214+
when false, nil
215+
return false
216+
when true
217+
pool_options = DEFAULT_POOL_OPTIONS
218+
when Hash
219+
pool_options[:size] = Integer(pool_options[:size]) if pool_options.key?(:size)
220+
pool_options[:timeout] = Float(pool_options[:timeout]) if pool_options.key?(:timeout)
221+
pool_options = DEFAULT_POOL_OPTIONS.merge(pool_options)
222+
else
223+
raise TypeError, "Invalid :pool argument, expected Hash, got: #{pool_options.inspect}"
224+
end
225+
226+
pool_options unless pool_options.empty?
218227
end
219228
end
220229

activesupport/lib/active_support/cache/mem_cache_store.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,10 @@ def self.build_mem_cache(*addresses) # :nodoc:
9999
addresses = nil if addresses.compact.empty?
100100
pool_options = retrieve_pool_options(options)
101101

102-
if pool_options.empty?
103-
Dalli::Client.new(addresses, options)
104-
else
102+
if pool_options
105103
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
104+
else
105+
Dalli::Client.new(addresses, options)
106106
end
107107
end
108108

activesupport/lib/active_support/cache/redis_cache_store.rb

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def build_redis_client(**redis_options)
105105

106106
attr_reader :redis_options
107107
attr_reader :max_key_bytesize
108+
attr_reader :redis
108109

109110
# Creates a new Redis cache store.
110111
#
@@ -141,7 +142,11 @@ def build_redis_client(**redis_options)
141142
# cache.exist?('foo') # => true
142143
# cache.exist?('bar') # => false
143144
def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, coder: default_coder, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, skip_nil: false, **redis_options)
144-
@redis_options = redis_options
145+
if pool_options = self.class.send(:retrieve_pool_options, redis_options)
146+
@redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
147+
else
148+
@redis = self.class.build_redis(**redis_options)
149+
end
145150

146151
@max_key_bytesize = MAX_KEY_BYTESIZE
147152
@error_handler = error_handler
@@ -153,21 +158,8 @@ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, c
153158
coder: coder, skip_nil: skip_nil
154159
end
155160

156-
def redis
157-
@redis ||= begin
158-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
159-
160-
if pool_options.any?
161-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
162-
else
163-
self.class.build_redis(**redis_options)
164-
end
165-
end
166-
end
167-
168161
def inspect
169-
instance = @redis || @redis_options
170-
"#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
162+
"#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
171163
end
172164

173165
# Cache Store API implementation.

activesupport/test/cache/stores/mem_cache_store_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ def after_teardown
8686
include ConnectionPoolBehavior
8787
include FailureSafetyBehavior
8888

89+
test "validate pool arguments" do
90+
assert_raises TypeError do
91+
ActiveSupport::Cache::MemCacheStore.new(pool: { size: [] })
92+
end
93+
94+
assert_raises TypeError do
95+
ActiveSupport::Cache::MemCacheStore.new(pool: { timeout: [] })
96+
end
97+
98+
ActiveSupport::Cache::MemCacheStore.new(pool: { size: "12", timeout: "1.5" })
99+
end
100+
101+
test "instantiating the store doesn't connect to Memcache" do
102+
ActiveSupport::Cache::MemCacheStore.new("memcached://localhost:1")
103+
end
104+
89105
# Overrides test from LocalCacheBehavior in order to stub out the cache clear
90106
# and replace it with a delete.
91107
def test_clear_also_clears_local_cache

activesupport/test/cache/stores/redis_cache_store_test.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,25 @@ class InitializationTest < ActiveSupport::TestCase
110110
assert_same @cache.redis, redis_instance
111111
end
112112

113+
test "validate pool arguments" do
114+
assert_raises TypeError do
115+
build(url: REDIS_URL, pool: { size: [] })
116+
end
117+
118+
assert_raises TypeError do
119+
build(url: REDIS_URL, pool: { timeout: [] })
120+
end
121+
122+
build(url: REDIS_URL, pool: { size: "12", timeout: "1.5" })
123+
end
124+
125+
test "instantiating the store doesn't connect to Redis" do
126+
build(url: "redis://localhost:1")
127+
end
128+
113129
private
114130
def build(**kwargs)
115-
ActiveSupport::Cache::RedisCacheStore.new(**kwargs.merge(pool: false)).tap(&:redis)
131+
ActiveSupport::Cache::RedisCacheStore.new(pool: false, **kwargs).tap(&:redis)
116132
end
117133
end
118134

0 commit comments

Comments
 (0)