activesupport 6.0.6.1 → 7.1.3.2
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 +865 -438
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +30 -10
- data/lib/active_support/benchmarkable.rb +4 -3
- data/lib/active_support/broadcast_logger.rb +250 -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 +208 -63
- data/lib/active_support/cache/memory_store.rb +120 -38
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +201 -208
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -66
- data/lib/active_support/cache.rb +539 -261
- data/lib/active_support/callbacks.rb +273 -142
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +53 -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 +19 -6
- data/lib/active_support/configuration_file.rb +51 -0
- 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/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +19 -29
- 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 +18 -16
- data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- 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 +146 -72
- 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 +3 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +5 -5
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- 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/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
- data/lib/active_support/core_ext/module/concerning.rb +14 -8
- data/lib/active_support/core_ext/module/delegation.rb +75 -42
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +1 -26
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
- 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 +15 -4
- 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 +52 -28
- 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 +6 -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/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +3 -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 +51 -10
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +85 -194
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +46 -8
- data/lib/active_support/core_ext/time/conversions.rb +16 -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/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +54 -22
- 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 -769
- data/lib/active_support/deprecation/behaviors.rb +77 -38
- 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 +54 -0
- data/lib/active_support/deprecation/instance_delegator.rb +31 -5
- data/lib/active_support/deprecation/method_wrappers.rb +12 -28
- data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
- data/lib/active_support/deprecation/reporting.rb +76 -16
- data/lib/active_support/deprecation.rb +36 -4
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -68
- 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 +24 -12
- data/lib/active_support/duration.rb +136 -56
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +46 -13
- data/lib/active_support/environment_inquirer.rb +40 -0
- 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 +86 -137
- 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 +31 -12
- 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 +79 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +86 -42
- 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 +29 -27
- data/lib/active_support/inflector/inflections.rb +26 -9
- data/lib/active_support/inflector/methods.rb +54 -64
- data/lib/active_support/inflector/transliterate.rb +7 -5
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +6 -5
- data/lib/active_support/json/encoding.rb +31 -45
- data/lib/active_support/key_generator.rb +32 -7
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +10 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +101 -32
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +24 -25
- data/lib/active_support/message_encryptor.rb +205 -58
- 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 +237 -86
- 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_configuration.rb +2 -1
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +35 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +15 -52
- data/lib/active_support/multibyte/unicode.rb +8 -122
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +310 -105
- data/lib/active_support/notifications/instrumenter.rb +113 -48
- data/lib/active_support/notifications.rb +56 -29
- data/lib/active_support/number_helper/number_converter.rb +15 -8
- 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_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +379 -304
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +23 -3
- data/lib/active_support/parameter_filter.rb +104 -75
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +90 -6
- data/lib/active_support/reloader.rb +12 -4
- 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 +58 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +5 -3
- data/lib/active_support/subscriber.rb +23 -47
- data/lib/active_support/syntax_error_proxy.rb +70 -0
- data/lib/active_support/tagged_logging.rb +84 -23
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +73 -20
- 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 +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +16 -95
- data/lib/active_support/testing/parallelize_executor.rb +81 -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 +89 -19
- data/lib/active_support/time_with_zone.rb +105 -70
- data/lib/active_support/values/time_zone.rb +59 -26
- 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 +9 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +40 -1
- metadata +127 -40
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -25
- 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,34 +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
|
-
end
|
182
|
-
|
183
|
-
def redis
|
184
|
-
@redis ||= begin
|
185
|
-
pool_options = self.class.send(:retrieve_pool_options, redis_options)
|
186
|
-
|
187
|
-
if pool_options.any?
|
188
|
-
self.class.send(:ensure_connection_pool_added!)
|
189
|
-
::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
|
190
|
-
else
|
191
|
-
self.class.build_redis(**redis_options)
|
192
|
-
end
|
193
|
-
end
|
161
|
+
super(universal_options)
|
194
162
|
end
|
195
163
|
|
196
164
|
def inspect
|
197
|
-
|
198
|
-
"<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
|
165
|
+
"#<#{self.class} options=#{options.inspect} redis=#{redis.inspect}>"
|
199
166
|
end
|
200
167
|
|
201
168
|
# Cache Store API implementation.
|
@@ -203,14 +170,13 @@ module ActiveSupport
|
|
203
170
|
# Read multiple values at once. Returns a hash of requested keys ->
|
204
171
|
# fetched values.
|
205
172
|
def read_multi(*names)
|
206
|
-
if
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
211
179
|
end
|
212
|
-
else
|
213
|
-
super
|
214
180
|
end
|
215
181
|
end
|
216
182
|
|
@@ -234,24 +200,37 @@ module ActiveSupport
|
|
234
200
|
unless String === matcher
|
235
201
|
raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
|
236
202
|
end
|
237
|
-
redis.
|
203
|
+
redis.then do |c|
|
238
204
|
pattern = namespace_key(matcher, options)
|
239
205
|
cursor = "0"
|
240
206
|
# Fetch keys in batches using SCAN to avoid blocking the Redis server.
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
207
|
+
nodes = c.respond_to?(:nodes) ? c.nodes : [c]
|
208
|
+
|
209
|
+
nodes.each do |node|
|
210
|
+
begin
|
211
|
+
cursor, keys = node.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE)
|
212
|
+
node.del(*keys) unless keys.empty?
|
213
|
+
end until cursor == "0"
|
214
|
+
end
|
245
215
|
end
|
246
216
|
end
|
247
217
|
end
|
248
218
|
|
249
|
-
#
|
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
|
250
231
|
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
# Calling it on a value not stored with :raw will initialize that value
|
254
|
-
# to zero.
|
232
|
+
# Incrementing a non-numeric value, or a value written without
|
233
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
255
234
|
#
|
256
235
|
# Failsafe: Raises errors.
|
257
236
|
def increment(name, amount = 1, options = nil)
|
@@ -259,22 +238,25 @@ module ActiveSupport
|
|
259
238
|
failsafe :increment do
|
260
239
|
options = merged_options(options)
|
261
240
|
key = normalize_key(name, options)
|
262
|
-
|
263
|
-
redis.with do |c|
|
264
|
-
c.incrby(key, amount).tap do
|
265
|
-
write_key_expiry(c, key, options)
|
266
|
-
end
|
267
|
-
end
|
241
|
+
change_counter(key, amount, options)
|
268
242
|
end
|
269
243
|
end
|
270
244
|
end
|
271
245
|
|
272
|
-
#
|
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+:
|
273
250
|
#
|
274
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
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+.
|
278
260
|
#
|
279
261
|
# Failsafe: Raises errors.
|
280
262
|
def decrement(name, amount = 1, options = nil)
|
@@ -282,12 +264,7 @@ module ActiveSupport
|
|
282
264
|
failsafe :decrement do
|
283
265
|
options = merged_options(options)
|
284
266
|
key = normalize_key(name, options)
|
285
|
-
|
286
|
-
redis.with do |c|
|
287
|
-
c.decrby(key, amount).tap do
|
288
|
-
write_key_expiry(c, key, options)
|
289
|
-
end
|
290
|
-
end
|
267
|
+
change_counter(key, -amount, options)
|
291
268
|
end
|
292
269
|
end
|
293
270
|
end
|
@@ -309,67 +286,60 @@ module ActiveSupport
|
|
309
286
|
if namespace = merged_options(options)[:namespace]
|
310
287
|
delete_matched "*", namespace: namespace
|
311
288
|
else
|
312
|
-
redis.
|
289
|
+
redis.then { |c| c.flushdb }
|
313
290
|
end
|
314
291
|
end
|
315
292
|
end
|
316
293
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
end
|
321
|
-
|
322
|
-
def mset_capable? #:nodoc:
|
323
|
-
set_redis_capabilities unless defined? @mset_capable
|
324
|
-
@mset_capable
|
294
|
+
# Get info from redis servers.
|
295
|
+
def stats
|
296
|
+
redis.then { |c| c.info }
|
325
297
|
end
|
326
298
|
|
327
299
|
private
|
328
|
-
def
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
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
|
+
}
|
337
310
|
end
|
338
311
|
|
339
312
|
# Store provider interface:
|
340
313
|
# Read an entry from the cache.
|
341
314
|
def read_entry(key, **options)
|
342
|
-
|
343
|
-
raw = options&.fetch(:raw, false)
|
344
|
-
deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
|
345
|
-
end
|
315
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
346
316
|
end
|
347
317
|
|
348
|
-
def
|
349
|
-
|
350
|
-
|
351
|
-
else
|
352
|
-
super
|
318
|
+
def read_serialized_entry(key, raw: false, **options)
|
319
|
+
failsafe :read_entry do
|
320
|
+
redis.then { |c| c.get(key) }
|
353
321
|
end
|
354
322
|
end
|
355
323
|
|
356
|
-
def
|
357
|
-
options = names.extract_options!
|
324
|
+
def read_multi_entries(names, **options)
|
358
325
|
options = merged_options(options)
|
359
326
|
return {} if names == []
|
360
327
|
raw = options&.fetch(:raw, false)
|
361
328
|
|
362
329
|
keys = names.map { |name| normalize_key(name, options) }
|
363
330
|
|
364
|
-
values = failsafe(:
|
365
|
-
redis.
|
331
|
+
values = failsafe(:read_multi_entries, returning: {}) do
|
332
|
+
redis.then { |c| c.mget(*keys) }
|
366
333
|
end
|
367
334
|
|
368
335
|
names.zip(values).each_with_object({}) do |(name, value), results|
|
369
336
|
if value
|
370
337
|
entry = deserialize_entry(value, raw: raw)
|
371
338
|
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
|
372
|
-
|
339
|
+
begin
|
340
|
+
results[name] = entry.value
|
341
|
+
rescue DeserializationError
|
342
|
+
end
|
373
343
|
end
|
374
344
|
end
|
375
345
|
end
|
@@ -378,9 +348,11 @@ module ActiveSupport
|
|
378
348
|
# Write an entry to the cache.
|
379
349
|
#
|
380
350
|
# Requires Redis 2.6.12+ for extended SET options.
|
381
|
-
def write_entry(key, entry,
|
382
|
-
|
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
|
383
354
|
|
355
|
+
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, pipeline: nil, **options)
|
384
356
|
# If race condition TTL is in use, ensure that cache entries
|
385
357
|
# stick around a bit longer after they would have expired
|
386
358
|
# so we can purposefully serve stale entries.
|
@@ -388,53 +360,58 @@ module ActiveSupport
|
|
388
360
|
expires_in += 5.minutes
|
389
361
|
end
|
390
362
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
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
|
367
|
+
end
|
396
368
|
|
397
|
-
|
398
|
-
|
399
|
-
|
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 }
|
400
374
|
end
|
401
375
|
end
|
402
376
|
end
|
403
377
|
|
404
|
-
|
405
|
-
|
406
|
-
|
378
|
+
# Delete an entry from the cache.
|
379
|
+
def delete_entry(key, **options)
|
380
|
+
failsafe :delete_entry, returning: false do
|
381
|
+
redis.then { |c| c.del(key) == 1 }
|
407
382
|
end
|
408
383
|
end
|
409
384
|
|
410
|
-
#
|
411
|
-
def
|
412
|
-
failsafe :
|
413
|
-
redis.
|
385
|
+
# Deletes multiple entries in the cache. Returns the number of entries deleted.
|
386
|
+
def delete_multi_entries(entries, **_options)
|
387
|
+
failsafe :delete_multi_entries, returning: 0 do
|
388
|
+
redis.then { |c| c.del(entries) }
|
414
389
|
end
|
415
390
|
end
|
416
391
|
|
417
392
|
# Nonstandard store provider API to write multiple values at once.
|
418
|
-
def write_multi_entries(entries,
|
419
|
-
if entries.
|
420
|
-
|
421
|
-
|
422
|
-
|
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
|
423
402
|
end
|
424
|
-
else
|
425
|
-
super
|
426
403
|
end
|
427
404
|
end
|
428
405
|
end
|
429
406
|
|
430
407
|
# Truncate keys that exceed 1kB.
|
431
408
|
def normalize_key(key, options)
|
432
|
-
truncate_key super
|
409
|
+
truncate_key super&.b
|
433
410
|
end
|
434
411
|
|
435
412
|
def truncate_key(key)
|
436
|
-
if key.bytesize > max_key_bytesize
|
437
|
-
suffix = ":
|
413
|
+
if key && key.bytesize > max_key_bytesize
|
414
|
+
suffix = ":hash:#{ActiveSupport::Digest.hexdigest(key)}"
|
438
415
|
truncate_at = max_key_bytesize - suffix.bytesize
|
439
416
|
"#{key.byteslice(0, truncate_at)}#{suffix}"
|
440
417
|
else
|
@@ -442,52 +419,68 @@ module ActiveSupport
|
|
442
419
|
end
|
443
420
|
end
|
444
421
|
|
445
|
-
def deserialize_entry(
|
446
|
-
if
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
if raw != written_raw
|
451
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
452
|
-
Using a different value for the raw option when reading and writing
|
453
|
-
to a cache key is deprecated for :redis_cache_store and Rails 6.0
|
454
|
-
will stop automatically detecting the format when reading to avoid
|
455
|
-
marshal loading untrusted raw strings.
|
456
|
-
MSG
|
457
|
-
end
|
458
|
-
|
459
|
-
entry.is_a?(Entry) ? entry : Entry.new(entry)
|
422
|
+
def deserialize_entry(payload, raw: false, **)
|
423
|
+
if raw && !payload.nil?
|
424
|
+
Entry.new(payload)
|
425
|
+
else
|
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
|
-
|
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
|