activesupport 6.1.0 → 7.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1075 -325
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +32 -7
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +201 -62
- data/lib/active_support/cache/memory_store.rb +86 -24
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +186 -193
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +63 -71
- data/lib/active_support/cache.rb +487 -249
- data/lib/active_support/callbacks.rb +227 -105
- data/lib/active_support/code_generator.rb +70 -0
- data/lib/active_support/concern.rb +9 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +18 -5
- data/lib/active_support/configuration_file.rb +7 -2
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +15 -13
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/subclasses.rb +37 -26
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +24 -9
- data/lib/active_support/core_ext/date/conversions.rb +16 -15
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +85 -83
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +8 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +49 -22
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +81 -43
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/name_error.rb +2 -8
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -77
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +31 -11
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +49 -27
- data/lib/active_support/core_ext/object/to_query.rb +2 -4
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +0 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/conversions.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +17 -10
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +85 -165
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +30 -8
- data/lib/active_support/core_ext/time/conversions.rb +15 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes.rb +47 -20
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -788
- data/lib/active_support/deprecation/behaviors.rb +66 -40
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +9 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +38 -23
- data/lib/active_support/deprecation/reporting.rb +43 -26
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -72
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +9 -3
- data/lib/active_support/duration.rb +83 -52
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +29 -13
- data/lib/active_support/environment_inquirer.rb +23 -3
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -0
- data/lib/active_support/evented_file_update_checker.rb +20 -7
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +44 -22
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +28 -11
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +44 -19
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +21 -14
- data/lib/active_support/inflector/inflections.rb +25 -7
- data/lib/active_support/inflector/methods.rb +50 -64
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +27 -45
- data/lib/active_support/key_generator.rb +31 -6
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +4 -2
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +97 -35
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_thread_safe_level.rb +11 -34
- data/lib/active_support/message_encryptor.rb +206 -56
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +235 -84
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +112 -46
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +12 -11
- data/lib/active_support/multibyte/unicode.rb +9 -49
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +304 -114
- data/lib/active_support/notifications/instrumenter.rb +117 -35
- data/lib/active_support/notifications.rb +25 -25
- data/lib/active_support/number_helper/number_converter.rb +14 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -4
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +10 -6
- data/lib/active_support/number_helper/rounding_helper.rb +2 -6
- data/lib/active_support/number_helper.rb +379 -319
- data/lib/active_support/option_merger.rb +10 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +15 -1
- data/lib/active_support/parameter_filter.rb +105 -81
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +83 -21
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +18 -11
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +3 -3
- data/lib/active_support/subscriber.rb +11 -40
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +65 -25
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +61 -15
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- data/lib/active_support/testing/parallelization/server.rb +4 -0
- data/lib/active_support/testing/parallelization/worker.rb +3 -0
- data/lib/active_support/testing/parallelization.rb +4 -0
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +49 -16
- data/lib/active_support/time_with_zone.rb +39 -28
- data/lib/active_support/values/time_zone.rb +50 -18
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +28 -1
- metadata +150 -18
- data/lib/active_support/core_ext/marshal.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -28
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -29
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
@@ -5,61 +5,54 @@ begin
|
|
5
5
|
require "redis"
|
6
6
|
require "redis/distributed"
|
7
7
|
rescue LoadError
|
8
|
-
warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"
|
8
|
+
warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \">= 4.0.1\"`"
|
9
9
|
raise
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
require "digest/sha2"
|
19
|
-
require "active_support/core_ext/marshal"
|
12
|
+
require "connection_pool"
|
13
|
+
require "active_support/core_ext/array/wrap"
|
14
|
+
require "active_support/core_ext/hash/slice"
|
15
|
+
require "active_support/core_ext/numeric/time"
|
16
|
+
require "active_support/digest"
|
20
17
|
|
21
18
|
module ActiveSupport
|
22
19
|
module Cache
|
23
|
-
|
24
|
-
def with
|
25
|
-
yield self
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
::Redis.include(ConnectionPoolLike)
|
30
|
-
::Redis::Distributed.include(ConnectionPoolLike)
|
31
|
-
|
32
|
-
# Redis cache store.
|
20
|
+
# = Redis \Cache \Store
|
33
21
|
#
|
34
|
-
# Deployment note: Take care to use a
|
35
|
-
# than pointing this at
|
36
|
-
#
|
22
|
+
# Deployment note: Take care to use a <b>dedicated Redis cache</b> rather
|
23
|
+
# than pointing this at a persistent Redis server (for example, one used as
|
24
|
+
# an Active Job queue). Redis won't cope well with mixed usage patterns and it
|
25
|
+
# won't expire cache entries by default.
|
37
26
|
#
|
38
27
|
# Redis cache server setup guide: https://redis.io/topics/lru-cache
|
39
28
|
#
|
40
|
-
# * Supports vanilla Redis, hiredis, and Redis::Distributed
|
41
|
-
# * Supports Memcached-like sharding across Redises with Redis::Distributed
|
29
|
+
# * Supports vanilla Redis, hiredis, and +Redis::Distributed+.
|
30
|
+
# * Supports Memcached-like sharding across Redises with +Redis::Distributed+.
|
42
31
|
# * Fault tolerant. If the Redis server is unavailable, no exceptions are
|
43
32
|
# raised. Cache fetches are all misses and writes are dropped.
|
44
33
|
# * Local cache. Hot in-memory primary cache within block/middleware scope.
|
45
|
-
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
46
|
-
# 4.0.1+ for distributed mget support.
|
34
|
+
# * +read_multi+ and +write_multi+ support for Redis mget/mset. Use
|
35
|
+
# +Redis::Distributed+ 4.0.1+ for distributed mget support.
|
47
36
|
# * +delete_matched+ support for Redis KEYS globs.
|
48
37
|
class RedisCacheStore < Store
|
49
|
-
# Keys are truncated with
|
38
|
+
# Keys are truncated with the Active Support digest if they exceed 1kB
|
50
39
|
MAX_KEY_BYTESIZE = 1024
|
51
40
|
|
52
41
|
DEFAULT_REDIS_OPTIONS = {
|
53
|
-
connect_timeout:
|
42
|
+
connect_timeout: 1,
|
54
43
|
read_timeout: 1,
|
55
44
|
write_timeout: 1,
|
56
|
-
reconnect_attempts: 0,
|
57
45
|
}
|
58
46
|
|
59
47
|
DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
|
60
48
|
if logger
|
61
49
|
logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
|
62
50
|
end
|
51
|
+
ActiveSupport.error_reporter&.report(
|
52
|
+
exception,
|
53
|
+
severity: :warning,
|
54
|
+
source: "redis_cache_store.active_support",
|
55
|
+
)
|
63
56
|
end
|
64
57
|
|
65
58
|
# The maximum number of entries to receive per SCAN call.
|
@@ -71,35 +64,7 @@ module ActiveSupport
|
|
71
64
|
true
|
72
65
|
end
|
73
66
|
|
74
|
-
# Support raw values in the local cache strategy.
|
75
|
-
module LocalCacheWithRaw # :nodoc:
|
76
|
-
private
|
77
|
-
def write_entry(key, entry, **options)
|
78
|
-
if options[:raw] && local_cache
|
79
|
-
raw_entry = Entry.new(serialize_entry(entry, raw: true))
|
80
|
-
raw_entry.expires_at = entry.expires_at
|
81
|
-
super(key, raw_entry, **options)
|
82
|
-
else
|
83
|
-
super
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def write_multi_entries(entries, **options)
|
88
|
-
if options[:raw] && local_cache
|
89
|
-
raw_entries = entries.map do |key, entry|
|
90
|
-
raw_entry = Entry.new(serialize_entry(entry, raw: true))
|
91
|
-
raw_entry.expires_at = entry.expires_at
|
92
|
-
end.to_h
|
93
|
-
|
94
|
-
super(raw_entries, **options)
|
95
|
-
else
|
96
|
-
super
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
67
|
prepend Strategy::LocalCache
|
102
|
-
prepend LocalCacheWithRaw
|
103
68
|
|
104
69
|
class << self
|
105
70
|
# Factory method to create a new Redis instance.
|
@@ -113,7 +78,7 @@ module ActiveSupport
|
|
113
78
|
# :url String -> Redis.new(url: …)
|
114
79
|
# :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
|
115
80
|
#
|
116
|
-
def build_redis(redis: nil, url: nil, **redis_options)
|
81
|
+
def build_redis(redis: nil, url: nil, **redis_options) # :nodoc:
|
117
82
|
urls = Array(url)
|
118
83
|
|
119
84
|
if redis.is_a?(Proc)
|
@@ -121,9 +86,11 @@ module ActiveSupport
|
|
121
86
|
elsif redis
|
122
87
|
redis
|
123
88
|
elsif urls.size > 1
|
124
|
-
build_redis_distributed_client
|
89
|
+
build_redis_distributed_client(urls: urls, **redis_options)
|
90
|
+
elsif urls.empty?
|
91
|
+
build_redis_client(**redis_options)
|
125
92
|
else
|
126
|
-
build_redis_client
|
93
|
+
build_redis_client(url: urls.first, **redis_options)
|
127
94
|
end
|
128
95
|
end
|
129
96
|
|
@@ -134,18 +101,21 @@ module ActiveSupport
|
|
134
101
|
end
|
135
102
|
end
|
136
103
|
|
137
|
-
def build_redis_client(
|
138
|
-
::Redis.new
|
104
|
+
def build_redis_client(**redis_options)
|
105
|
+
::Redis.new(DEFAULT_REDIS_OPTIONS.merge(redis_options))
|
139
106
|
end
|
140
107
|
end
|
141
108
|
|
142
|
-
attr_reader :redis_options
|
143
109
|
attr_reader :max_key_bytesize
|
110
|
+
attr_reader :redis
|
144
111
|
|
145
112
|
# Creates a new Redis cache store.
|
146
113
|
#
|
147
|
-
#
|
148
|
-
#
|
114
|
+
# There are four ways to provide the Redis client used by the cache: the
|
115
|
+
# +:redis+ param can be a Redis instance or a block that returns a Redis
|
116
|
+
# instance, or the +:url+ param can be a string or an array of strings
|
117
|
+
# which will be used to create a Redis instance or a +Redis::Distributed+
|
118
|
+
# instance.
|
149
119
|
#
|
150
120
|
# Option Class Result
|
151
121
|
# :redis Proc -> options[:redis].call
|
@@ -168,35 +138,31 @@ module ActiveSupport
|
|
168
138
|
#
|
169
139
|
# Race condition TTL is not set by default. This can be used to avoid
|
170
140
|
# "thundering herd" cache writes when hot cache entries are expired.
|
171
|
-
# See
|
172
|
-
|
173
|
-
|
141
|
+
# See ActiveSupport::Cache::Store#fetch for more.
|
142
|
+
#
|
143
|
+
# Setting <tt>skip_nil: true</tt> will not cache nil results:
|
144
|
+
#
|
145
|
+
# cache.fetch('foo') { nil }
|
146
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
147
|
+
# cache.exist?('foo') # => true
|
148
|
+
# cache.exist?('bar') # => false
|
149
|
+
def initialize(error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
|
150
|
+
universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
|
151
|
+
|
152
|
+
if pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
153
|
+
@redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
154
|
+
else
|
155
|
+
@redis = self.class.build_redis(**redis_options)
|
156
|
+
end
|
174
157
|
|
175
158
|
@max_key_bytesize = MAX_KEY_BYTESIZE
|
176
159
|
@error_handler = error_handler
|
177
160
|
|
178
|
-
super
|
179
|
-
compress: compress, compress_threshold: compress_threshold,
|
180
|
-
expires_in: expires_in, race_condition_ttl: race_condition_ttl,
|
181
|
-
coder: coder
|
182
|
-
end
|
183
|
-
|
184
|
-
def redis
|
185
|
-
@redis ||= begin
|
186
|
-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
187
|
-
|
188
|
-
if pool_options.any?
|
189
|
-
self.class.send(:ensure_connection_pool_added!)
|
190
|
-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
191
|
-
else
|
192
|
-
self.class.build_redis(**redis_options)
|
193
|
-
end
|
194
|
-
end
|
161
|
+
super(universal_options)
|
195
162
|
end
|
196
163
|
|
197
164
|
def inspect
|
198
|
-
|
199
|
-
"#<#{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
|
165
|
+
"#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
|
200
166
|
end
|
201
167
|
|
202
168
|
# Cache Store API implementation.
|
@@ -204,14 +170,13 @@ module ActiveSupport
|
|
204
170
|
# Read multiple values at once. Returns a hash of requested keys ->
|
205
171
|
# fetched values.
|
206
172
|
def read_multi(*names)
|
207
|
-
if
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
173
|
+
return {} if names.empty?
|
174
|
+
|
175
|
+
options = names.extract_options!
|
176
|
+
instrument_multi(:read_multi, names, options) do |payload|
|
177
|
+
read_multi_entries(names, **options).tap do |results|
|
178
|
+
payload[:hits] = results.keys
|
212
179
|
end
|
213
|
-
else
|
214
|
-
super
|
215
180
|
end
|
216
181
|
end
|
217
182
|
|
@@ -235,7 +200,7 @@ module ActiveSupport
|
|
235
200
|
unless String === matcher
|
236
201
|
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
237
202
|
end
|
238
|
-
redis.
|
203
|
+
redis.then do |c|
|
239
204
|
pattern = namespace_key(matcher, options)
|
240
205
|
cursor = "0"
|
241
206
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
@@ -251,12 +216,21 @@ module ActiveSupport
|
|
251
216
|
end
|
252
217
|
end
|
253
218
|
|
254
|
-
#
|
219
|
+
# Increment a cached integer value using the Redis incrby atomic operator.
|
220
|
+
# Returns the updated value.
|
221
|
+
#
|
222
|
+
# If the key is unset or has expired, it will be set to +amount+:
|
223
|
+
#
|
224
|
+
# cache.increment("foo") # => 1
|
225
|
+
# cache.increment("bar", 100) # => 100
|
226
|
+
#
|
227
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
228
|
+
#
|
229
|
+
# cache.write("baz", 5, raw: true)
|
230
|
+
# cache.increment("baz") # => 6
|
255
231
|
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
# Calling it on a value not stored with :raw will initialize that value
|
259
|
-
# to zero.
|
232
|
+
# Incrementing a non-numeric value, or a value written without
|
233
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
260
234
|
#
|
261
235
|
# Failsafe: Raises errors.
|
262
236
|
def increment(name, amount = 1, options = nil)
|
@@ -264,22 +238,25 @@ module ActiveSupport
|
|
264
238
|
failsafe :increment do
|
265
239
|
options = merged_options(options)
|
266
240
|
key = normalize_key(name, options)
|
267
|
-
|
268
|
-
redis.with do |c|
|
269
|
-
c.incrby(key, amount).tap do
|
270
|
-
write_key_expiry(c, key, options)
|
271
|
-
end
|
272
|
-
end
|
241
|
+
change_counter(key, amount, options)
|
273
242
|
end
|
274
243
|
end
|
275
244
|
end
|
276
245
|
|
277
|
-
#
|
246
|
+
# Decrement a cached integer value using the Redis decrby atomic operator.
|
247
|
+
# Returns the updated value.
|
248
|
+
#
|
249
|
+
# If the key is unset or has expired, it will be set to +-amount+:
|
278
250
|
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
251
|
+
# cache.decrement("foo") # => -1
|
252
|
+
#
|
253
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
254
|
+
#
|
255
|
+
# cache.write("baz", 5, raw: true)
|
256
|
+
# cache.decrement("baz") # => 4
|
257
|
+
#
|
258
|
+
# Decrementing a non-numeric value, or a value written without
|
259
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
283
260
|
#
|
284
261
|
# Failsafe: Raises errors.
|
285
262
|
def decrement(name, amount = 1, options = nil)
|
@@ -287,12 +264,7 @@ module ActiveSupport
|
|
287
264
|
failsafe :decrement do
|
288
265
|
options = merged_options(options)
|
289
266
|
key = normalize_key(name, options)
|
290
|
-
|
291
|
-
redis.with do |c|
|
292
|
-
c.decrby(key, amount).tap do
|
293
|
-
write_key_expiry(c, key, options)
|
294
|
-
end
|
295
|
-
end
|
267
|
+
change_counter(key, -amount, options)
|
296
268
|
end
|
297
269
|
end
|
298
270
|
end
|
@@ -314,67 +286,60 @@ module ActiveSupport
|
|
314
286
|
if namespace = merged_options(options)[:namespace]
|
315
287
|
delete_matched "*", namespace: namespace
|
316
288
|
else
|
317
|
-
redis.
|
289
|
+
redis.then { |c| c.flushdb }
|
318
290
|
end
|
319
291
|
end
|
320
292
|
end
|
321
293
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
end
|
326
|
-
|
327
|
-
def mset_capable? #:nodoc:
|
328
|
-
set_redis_capabilities unless defined? @mset_capable
|
329
|
-
@mset_capable
|
294
|
+
# Get info from redis servers.
|
295
|
+
def stats
|
296
|
+
redis.then { |c| c.info }
|
330
297
|
end
|
331
298
|
|
332
299
|
private
|
333
|
-
def
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
300
|
+
def pipeline_entries(entries, &block)
|
301
|
+
redis.then { |c|
|
302
|
+
if c.is_a?(Redis::Distributed)
|
303
|
+
entries.group_by { |k, _v| c.node_for(k) }.each do |node, sub_entries|
|
304
|
+
node.pipelined { |pipe| yield(pipe, sub_entries) }
|
305
|
+
end
|
306
|
+
else
|
307
|
+
c.pipelined { |pipe| yield(pipe, entries) }
|
308
|
+
end
|
309
|
+
}
|
342
310
|
end
|
343
311
|
|
344
312
|
# Store provider interface:
|
345
313
|
# Read an entry from the cache.
|
346
314
|
def read_entry(key, **options)
|
347
|
-
|
348
|
-
raw = options&.fetch(:raw, false)
|
349
|
-
deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
|
350
|
-
end
|
315
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
351
316
|
end
|
352
317
|
|
353
|
-
def
|
354
|
-
|
355
|
-
|
356
|
-
else
|
357
|
-
super
|
318
|
+
def read_serialized_entry(key, raw: false, **options)
|
319
|
+
failsafe :read_entry do
|
320
|
+
redis.then { |c| c.get(key) }
|
358
321
|
end
|
359
322
|
end
|
360
323
|
|
361
|
-
def
|
362
|
-
options = names.extract_options!
|
324
|
+
def read_multi_entries(names, **options)
|
363
325
|
options = merged_options(options)
|
364
326
|
return {} if names == []
|
365
327
|
raw = options&.fetch(:raw, false)
|
366
328
|
|
367
329
|
keys = names.map { |name| normalize_key(name, options) }
|
368
330
|
|
369
|
-
values = failsafe(:
|
370
|
-
redis.
|
331
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
332
|
+
redis.then { |c| c.mget(*keys) }
|
371
333
|
end
|
372
334
|
|
373
335
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
374
336
|
if value
|
375
337
|
entry = deserialize_entry(value, raw: raw)
|
376
338
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
377
|
-
|
339
|
+
begin
|
340
|
+
results[name] = entry.value
|
341
|
+
rescue DeserializationError
|
342
|
+
end
|
378
343
|
end
|
379
344
|
end
|
380
345
|
end
|
@@ -383,9 +348,11 @@ module ActiveSupport
|
|
383
348
|
# Write an entry to the cache.
|
384
349
|
#
|
385
350
|
# Requires Redis 2.6.12+ for extended SET options.
|
386
|
-
def write_entry(key, entry,
|
387
|
-
|
351
|
+
def write_entry(key, entry, raw: false, **options)
|
352
|
+
write_serialized_entry(key, serialize_entry(entry, raw: raw, **options), raw: raw, **options)
|
353
|
+
end
|
388
354
|
|
355
|
+
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options)
|
389
356
|
# If race condition TTL is in use, ensure that cache entries
|
390
357
|
# stick around a bit longer after they would have expired
|
391
358
|
# so we can purposefully serve stale entries.
|
@@ -393,46 +360,46 @@ module ActiveSupport
|
|
393
360
|
expires_in += 5.minutes
|
394
361
|
end
|
395
362
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
401
|
-
|
402
|
-
redis.with { |c| c.set key, serialized_entry, **modifiers }
|
403
|
-
else
|
404
|
-
redis.with { |c| c.set key, serialized_entry }
|
405
|
-
end
|
363
|
+
modifiers = {}
|
364
|
+
if unless_exist || expires_in
|
365
|
+
modifiers[:nx] = unless_exist
|
366
|
+
modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
|
406
367
|
end
|
407
|
-
end
|
408
368
|
|
409
|
-
|
410
|
-
|
411
|
-
|
369
|
+
if pipeline
|
370
|
+
pipeline.set(key, payload, **modifiers)
|
371
|
+
else
|
372
|
+
failsafe :write_entry, returning: false do
|
373
|
+
redis.then { |c| c.set key, payload, **modifiers }
|
374
|
+
end
|
412
375
|
end
|
413
376
|
end
|
414
377
|
|
415
378
|
# Delete an entry from the cache.
|
416
|
-
def delete_entry(key, options)
|
379
|
+
def delete_entry(key, **options)
|
417
380
|
failsafe :delete_entry, returning: false do
|
418
|
-
redis.
|
381
|
+
redis.then { |c| c.del(key) == 1 }
|
419
382
|
end
|
420
383
|
end
|
421
384
|
|
422
385
|
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
423
386
|
def delete_multi_entries(entries, **_options)
|
424
|
-
|
387
|
+
failsafe :delete_multi_entries, returning: 0 do
|
388
|
+
redis.then { |c| c.del(entries) }
|
389
|
+
end
|
425
390
|
end
|
426
391
|
|
427
392
|
# Nonstandard store provider API to write multiple values at once.
|
428
|
-
def write_multi_entries(entries,
|
429
|
-
if entries.
|
430
|
-
|
431
|
-
|
432
|
-
|
393
|
+
def write_multi_entries(entries, **options)
|
394
|
+
return if entries.empty?
|
395
|
+
|
396
|
+
failsafe :write_multi_entries do
|
397
|
+
pipeline_entries(entries) do |pipeline, sharded_entries|
|
398
|
+
options = options.dup
|
399
|
+
options[:pipeline] = pipeline
|
400
|
+
sharded_entries.each do |key, entry|
|
401
|
+
write_entry key, entry, **options
|
433
402
|
end
|
434
|
-
else
|
435
|
-
super
|
436
403
|
end
|
437
404
|
end
|
438
405
|
end
|
@@ -444,7 +411,7 @@ module ActiveSupport
|
|
444
411
|
|
445
412
|
def truncate_key(key)
|
446
413
|
if key && key.bytesize > max_key_bytesize
|
447
|
-
suffix = ":
|
414
|
+
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
448
415
|
truncate_at = max_key_bytesize - suffix.bytesize
|
449
416
|
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
450
417
|
else
|
@@ -452,42 +419,68 @@ module ActiveSupport
|
|
452
419
|
end
|
453
420
|
end
|
454
421
|
|
455
|
-
def deserialize_entry(payload, raw:)
|
456
|
-
if
|
457
|
-
Entry.new(payload
|
422
|
+
def deserialize_entry(payload, raw: false, **)
|
423
|
+
if raw && !payload.nil?
|
424
|
+
Entry.new(payload)
|
458
425
|
else
|
459
426
|
super(payload)
|
460
427
|
end
|
461
428
|
end
|
462
429
|
|
463
|
-
def serialize_entry(entry, raw: false)
|
430
|
+
def serialize_entry(entry, raw: false, **options)
|
464
431
|
if raw
|
465
432
|
entry.value.to_s
|
466
433
|
else
|
467
|
-
super(entry)
|
434
|
+
super(entry, raw: raw, **options)
|
468
435
|
end
|
469
436
|
end
|
470
437
|
|
471
|
-
def serialize_entries(entries,
|
438
|
+
def serialize_entries(entries, **options)
|
472
439
|
entries.transform_values do |entry|
|
473
|
-
serialize_entry
|
440
|
+
serialize_entry(entry, **options)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def change_counter(key, amount, options)
|
445
|
+
redis.then do |c|
|
446
|
+
c = c.node_for(key) if c.is_a?(Redis::Distributed)
|
447
|
+
|
448
|
+
expires_in = options[:expires_in]
|
449
|
+
|
450
|
+
if expires_in
|
451
|
+
if supports_expire_nx?
|
452
|
+
count, _ = c.pipelined do |pipeline|
|
453
|
+
pipeline.incrby(key, amount)
|
454
|
+
pipeline.call(:expire, key, expires_in.to_i, "NX")
|
455
|
+
end
|
456
|
+
else
|
457
|
+
count, ttl = c.pipelined do |pipeline|
|
458
|
+
pipeline.incrby(key, amount)
|
459
|
+
pipeline.ttl(key)
|
460
|
+
end
|
461
|
+
c.expire(key, expires_in.to_i) if ttl < 0
|
462
|
+
end
|
463
|
+
else
|
464
|
+
count = c.incrby(key, amount)
|
465
|
+
end
|
466
|
+
|
467
|
+
count
|
474
468
|
end
|
475
469
|
end
|
476
470
|
|
471
|
+
def supports_expire_nx?
|
472
|
+
return @supports_expire_nx if defined?(@supports_expire_nx)
|
473
|
+
|
474
|
+
redis_versions = redis.then { |c| Array.wrap(c.info("server")).pluck("redis_version") }
|
475
|
+
@supports_expire_nx = redis_versions.all? { |v| Gem::Version.new(v) >= Gem::Version.new("7.0.0") }
|
476
|
+
end
|
477
|
+
|
477
478
|
def failsafe(method, returning: nil)
|
478
479
|
yield
|
479
|
-
rescue ::Redis::BaseError =>
|
480
|
-
|
480
|
+
rescue ::Redis::BaseError => error
|
481
|
+
@error_handler&.call(method: method, exception: error, returning: returning)
|
481
482
|
returning
|
482
483
|
end
|
483
|
-
|
484
|
-
def handle_exception(exception:, method:, returning:)
|
485
|
-
if @error_handler
|
486
|
-
@error_handler.(method: method, exception: exception, returning: returning)
|
487
|
-
end
|
488
|
-
rescue => failsafe
|
489
|
-
warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
|
490
|
-
end
|
491
484
|
end
|
492
485
|
end
|
493
486
|
end
|