activesupport 7.0.8.7 → 7.2.2.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 +143 -459
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +3 -1
- data/lib/active_support/backtrace_cleaner.rb +39 -7
- data/lib/active_support/benchmarkable.rb +1 -0
- 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 +49 -17
- data/lib/active_support/cache/mem_cache_store.rb +94 -128
- data/lib/active_support/cache/memory_store.rb +80 -25
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +165 -152
- data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
- data/lib/active_support/cache/strategy/local_cache.rb +29 -14
- data/lib/active_support/cache.rb +363 -291
- data/lib/active_support/callbacks.rb +118 -134
- data/lib/active_support/code_generator.rb +15 -10
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +10 -0
- data/lib/active_support/core_ext/array/conversions.rb +1 -2
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +17 -34
- data/lib/active_support/core_ext/date/blank.rb +4 -0
- data/lib/active_support/core_ext/date/conversions.rb +1 -2
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
- data/lib/active_support/core_ext/date_time/blank.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +2 -2
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +7 -10
- data/lib/active_support/core_ext/enumerable.rb +3 -75
- data/lib/active_support/core_ext/erb/util.rb +201 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +20 -119
- data/lib/active_support/core_ext/module/deprecation.rb +12 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/blank.rb +45 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
- data/lib/active_support/core_ext/object/json.rb +17 -7
- data/lib/active_support/core_ext/object/with.rb +46 -0
- data/lib/active_support/core_ext/object/with_options.rb +4 -4
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +20 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +28 -7
- 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 +1 -5
- data/lib/active_support/core_ext/string/conversions.rb +1 -1
- 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 +16 -5
- data/lib/active_support/core_ext/string/multibyte.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +34 -177
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +36 -30
- data/lib/active_support/core_ext/time/compatibility.rb +16 -0
- data/lib/active_support/core_ext/time/conversions.rb +1 -3
- data/lib/active_support/core_ext/time/zones.rb +4 -4
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/core_ext.rb +0 -1
- data/lib/active_support/current_attributes.rb +53 -46
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/delegation.rb +202 -0
- data/lib/active_support/dependencies/autoload.rb +9 -16
- data/lib/active_support/deprecation/behaviors.rb +65 -42
- data/lib/active_support/deprecation/constant_accessor.rb +47 -25
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +3 -5
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
- data/lib/active_support/deprecation/reporting.rb +49 -27
- data/lib/active_support/deprecation.rb +39 -9
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +66 -172
- data/lib/active_support/duration/iso8601_parser.rb +2 -2
- data/lib/active_support/duration/iso8601_serializer.rb +1 -4
- data/lib/active_support/duration.rb +13 -7
- data/lib/active_support/encrypted_configuration.rb +30 -9
- data/lib/active_support/encrypted_file.rb +9 -4
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +160 -36
- data/lib/active_support/evented_file_update_checker.rb +0 -1
- data/lib/active_support/execution_wrapper.rb +4 -5
- data/lib/active_support/file_update_checker.rb +5 -3
- data/lib/active_support/fork_tracker.rb +4 -32
- 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 +41 -25
- data/lib/active_support/html_safe_translation.rb +19 -6
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +23 -11
- data/lib/active_support/inflector/transliterate.rb +3 -1
- data/lib/active_support/isolated_execution_state.rb +26 -22
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +6 -4
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +74 -34
- data/lib/active_support/logger.rb +22 -60
- data/lib/active_support/logger_thread_safe_level.rb +10 -32
- data/lib/active_support/message_encryptor.rb +197 -53
- 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 +305 -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 +220 -89
- 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 +111 -45
- 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 +4 -2
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +248 -87
- data/lib/active_support/notifications/instrumenter.rb +93 -25
- data/lib/active_support/notifications.rb +29 -28
- data/lib/active_support/number_helper/number_converter.rb +16 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/number_helper.rb +379 -318
- data/lib/active_support/option_merger.rb +2 -2
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +67 -15
- data/lib/active_support/parameter_filter.rb +84 -69
- data/lib/active_support/proxy_object.rb +8 -3
- data/lib/active_support/railtie.rb +25 -20
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +2 -0
- data/lib/active_support/secure_compare_rotator.rb +16 -9
- data/lib/active_support/string_inquirer.rb +4 -2
- data/lib/active_support/subscriber.rb +10 -27
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +64 -25
- data/lib/active_support/test_case.rb +156 -7
- data/lib/active_support/testing/assertions.rb +28 -12
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +54 -0
- data/lib/active_support/testing/deprecation.rb +20 -27
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +21 -9
- data/lib/active_support/testing/method_call_assertions.rb +7 -8
- data/lib/active_support/testing/parallelization/server.rb +3 -0
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +43 -0
- data/lib/active_support/testing/tests_without_assertions.rb +19 -0
- data/lib/active_support/testing/time_helpers.rb +38 -16
- data/lib/active_support/time_with_zone.rb +12 -18
- data/lib/active_support/values/time_zone.rb +25 -14
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +12 -3
- data/lib/active_support.rb +15 -3
- metadata +140 -19
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/deprecation/instance_delegator.rb +0 -38
- data/lib/active_support/per_thread_registry.rb +0 -65
- data/lib/active_support/ruby_features.rb +0 -7
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module Cache
|
7
|
+
# This class is used to represent cache entries. Cache entries have a value, an optional
|
8
|
+
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
|
9
|
+
# on the cache. The version is used to support the :version option on the cache for rejecting
|
10
|
+
# mismatches.
|
11
|
+
#
|
12
|
+
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
13
|
+
# using short instance variable names that are lazily defined.
|
14
|
+
class Entry # :nodoc:
|
15
|
+
class << self
|
16
|
+
def unpack(members)
|
17
|
+
new(members[0], expires_at: members[1], version: members[2])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :version
|
22
|
+
|
23
|
+
# Creates a new cache entry for the specified value. Options supported are
|
24
|
+
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
|
25
|
+
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
|
26
|
+
@value = value
|
27
|
+
@version = version
|
28
|
+
@created_at = 0.0
|
29
|
+
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
|
30
|
+
@compressed = true if compressed
|
31
|
+
end
|
32
|
+
|
33
|
+
def value
|
34
|
+
compressed? ? uncompress(@value) : @value
|
35
|
+
end
|
36
|
+
|
37
|
+
def mismatched?(version)
|
38
|
+
@version && version && @version != version
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks if the entry is expired. The +expires_in+ parameter can override
|
42
|
+
# the value set when the entry was created.
|
43
|
+
def expired?
|
44
|
+
@expires_in && @created_at + @expires_in <= Time.now.to_f
|
45
|
+
end
|
46
|
+
|
47
|
+
def expires_at
|
48
|
+
@expires_in ? @created_at + @expires_in : nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def expires_at=(value)
|
52
|
+
if value
|
53
|
+
@expires_in = value.to_f - @created_at
|
54
|
+
else
|
55
|
+
@expires_in = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the size of the cached value. This could be less than
|
60
|
+
# <tt>value.bytesize</tt> if the data is compressed.
|
61
|
+
def bytesize
|
62
|
+
case value
|
63
|
+
when NilClass
|
64
|
+
0
|
65
|
+
when String
|
66
|
+
@value.bytesize
|
67
|
+
else
|
68
|
+
@s ||= Marshal.dump(@value).bytesize
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def compressed? # :nodoc:
|
73
|
+
defined?(@compressed)
|
74
|
+
end
|
75
|
+
|
76
|
+
def compressed(compress_threshold)
|
77
|
+
return self if compressed?
|
78
|
+
|
79
|
+
case @value
|
80
|
+
when nil, true, false, Numeric
|
81
|
+
uncompressed_size = 0
|
82
|
+
when String
|
83
|
+
uncompressed_size = @value.bytesize
|
84
|
+
else
|
85
|
+
serialized = Marshal.dump(@value)
|
86
|
+
uncompressed_size = serialized.bytesize
|
87
|
+
end
|
88
|
+
|
89
|
+
if uncompressed_size >= compress_threshold
|
90
|
+
serialized ||= Marshal.dump(@value)
|
91
|
+
compressed = Zlib::Deflate.deflate(serialized)
|
92
|
+
|
93
|
+
if compressed.bytesize < uncompressed_size
|
94
|
+
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
def local?
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
104
|
+
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
105
|
+
# serialize entries to protect against accidental cache modifications.
|
106
|
+
def dup_value!
|
107
|
+
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
|
108
|
+
if @value.is_a?(String)
|
109
|
+
@value = @value.dup
|
110
|
+
else
|
111
|
+
@value = Marshal.load(Marshal.dump(@value))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def pack
|
117
|
+
members = [value, expires_at, version]
|
118
|
+
members.pop while !members.empty? && members.last.nil?
|
119
|
+
members
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def uncompress(value)
|
124
|
+
marshal_load(Zlib::Inflate.inflate(value))
|
125
|
+
end
|
126
|
+
|
127
|
+
def marshal_load(payload)
|
128
|
+
Marshal.load(payload)
|
129
|
+
rescue ArgumentError => error
|
130
|
+
raise Cache::DeserializationError, error.message
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -6,10 +6,9 @@ require "uri/common"
|
|
6
6
|
|
7
7
|
module ActiveSupport
|
8
8
|
module Cache
|
9
|
-
#
|
9
|
+
# = \File \Cache \Store
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# an in-memory cache inside of a block.
|
11
|
+
# A cache store implementation which stores everything on the filesystem.
|
13
12
|
class FileStore < Store
|
14
13
|
attr_reader :cache_path
|
15
14
|
|
@@ -46,22 +45,42 @@ module ActiveSupport
|
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
49
|
-
#
|
50
|
-
#
|
48
|
+
# Increment a cached integer value. Returns the updated value.
|
49
|
+
#
|
50
|
+
# If the key is unset, it starts from +0+:
|
51
|
+
#
|
52
|
+
# cache.increment("foo") # => 1
|
53
|
+
# cache.increment("bar", 100) # => 100
|
54
|
+
#
|
55
|
+
# To set a specific value, call #write:
|
56
|
+
#
|
57
|
+
# cache.write("baz", 5)
|
58
|
+
# cache.increment("baz") # => 6
|
59
|
+
#
|
51
60
|
def increment(name, amount = 1, options = nil)
|
52
61
|
modify_value(name, amount, options)
|
53
62
|
end
|
54
63
|
|
55
|
-
#
|
56
|
-
#
|
64
|
+
# Decrement a cached integer value. Returns the updated value.
|
65
|
+
#
|
66
|
+
# If the key is unset, it will be set to +-amount+.
|
67
|
+
#
|
68
|
+
# cache.decrement("foo") # => -1
|
69
|
+
#
|
70
|
+
# To set a specific value, call #write:
|
71
|
+
#
|
72
|
+
# cache.write("baz", 5)
|
73
|
+
# cache.decrement("baz") # => 4
|
74
|
+
#
|
57
75
|
def decrement(name, amount = 1, options = nil)
|
58
76
|
modify_value(name, -amount, options)
|
59
77
|
end
|
60
78
|
|
61
79
|
def delete_matched(matcher, options = nil)
|
62
80
|
options = merged_options(options)
|
81
|
+
matcher = key_matcher(matcher, options)
|
82
|
+
|
63
83
|
instrument(:delete_matched, matcher.inspect) do
|
64
|
-
matcher = key_matcher(matcher, options)
|
65
84
|
search_dir(cache_path) do |path|
|
66
85
|
key = file_path_key(path)
|
67
86
|
delete_entry(path, **options) if key.match(matcher)
|
@@ -69,6 +88,10 @@ module ActiveSupport
|
|
69
88
|
end
|
70
89
|
end
|
71
90
|
|
91
|
+
def inspect # :nodoc:
|
92
|
+
"#<#{self.class.name} cache_path=#{@cache_path}, options=#{@options.inspect}>"
|
93
|
+
end
|
94
|
+
|
72
95
|
private
|
73
96
|
def read_entry(key, **options)
|
74
97
|
if payload = read_serialized_entry(key, **options)
|
@@ -106,6 +129,8 @@ module ActiveSupport
|
|
106
129
|
raise if File.exist?(key)
|
107
130
|
false
|
108
131
|
end
|
132
|
+
else
|
133
|
+
false
|
109
134
|
end
|
110
135
|
end
|
111
136
|
|
@@ -152,7 +177,7 @@ module ActiveSupport
|
|
152
177
|
|
153
178
|
# Translate a file path into a key.
|
154
179
|
def file_path_key(path)
|
155
|
-
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
|
180
|
+
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR)
|
156
181
|
URI.decode_www_form_component(fname, Encoding::UTF_8)
|
157
182
|
end
|
158
183
|
|
@@ -182,17 +207,24 @@ module ActiveSupport
|
|
182
207
|
end
|
183
208
|
end
|
184
209
|
|
185
|
-
# Modifies the amount of an
|
186
|
-
# If the key is not found
|
210
|
+
# Modifies the amount of an integer value that is stored in the cache.
|
211
|
+
# If the key is not found it is created and set to +amount+.
|
187
212
|
def modify_value(name, amount, options)
|
188
|
-
|
213
|
+
options = merged_options(options)
|
214
|
+
key = normalize_key(name, options)
|
215
|
+
version = normalize_version(name, options)
|
216
|
+
amount = Integer(amount)
|
189
217
|
|
190
|
-
lock_file(
|
191
|
-
|
218
|
+
lock_file(key) do
|
219
|
+
entry = read_entry(key, **options)
|
192
220
|
|
193
|
-
if
|
194
|
-
|
195
|
-
|
221
|
+
if !entry || entry.expired? || entry.mismatched?(version)
|
222
|
+
write(name, amount, options)
|
223
|
+
amount
|
224
|
+
else
|
225
|
+
num = entry.value.to_i + amount
|
226
|
+
entry = Entry.new(num, expires_at: entry.expires_at, version: entry.version)
|
227
|
+
write_entry(key, entry)
|
196
228
|
num
|
197
229
|
end
|
198
230
|
end
|
@@ -3,16 +3,20 @@
|
|
3
3
|
begin
|
4
4
|
require "dalli"
|
5
5
|
rescue LoadError => e
|
6
|
-
|
6
|
+
warn "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
+
require "connection_pool"
|
10
11
|
require "delegate"
|
11
12
|
require "active_support/core_ext/enumerable"
|
12
13
|
require "active_support/core_ext/array/extract_options"
|
14
|
+
require "active_support/core_ext/numeric/time"
|
13
15
|
|
14
16
|
module ActiveSupport
|
15
17
|
module Cache
|
18
|
+
# = Memcached \Cache \Store
|
19
|
+
#
|
16
20
|
# A cache store implementation which stores data in Memcached:
|
17
21
|
# https://memcached.org
|
18
22
|
#
|
@@ -20,12 +24,16 @@ module ActiveSupport
|
|
20
24
|
#
|
21
25
|
# Special features:
|
22
26
|
# - Clustering and load balancing. One can specify multiple memcached servers,
|
23
|
-
# and MemCacheStore will load balance between all available servers. If a
|
24
|
-
# server goes down, then MemCacheStore will ignore it until it comes back up.
|
27
|
+
# and +MemCacheStore+ will load balance between all available servers. If a
|
28
|
+
# server goes down, then +MemCacheStore+ will ignore it until it comes back up.
|
25
29
|
#
|
26
|
-
# MemCacheStore implements the Strategy::LocalCache strategy which
|
27
|
-
# an in-memory cache inside of a block.
|
30
|
+
# +MemCacheStore+ implements the Strategy::LocalCache strategy which
|
31
|
+
# implements an in-memory cache inside of a block.
|
28
32
|
class MemCacheStore < Store
|
33
|
+
# These options represent behavior overridden by this implementation and should
|
34
|
+
# not be allowed to get down to the Dalli client
|
35
|
+
OVERRIDDEN_OPTIONS = UNIVERSAL_OPTIONS
|
36
|
+
|
29
37
|
# Advertise cache versioning support.
|
30
38
|
def self.supports_cache_versioning?
|
31
39
|
true
|
@@ -33,46 +41,7 @@ module ActiveSupport
|
|
33
41
|
|
34
42
|
prepend Strategy::LocalCache
|
35
43
|
|
36
|
-
|
37
|
-
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
38
|
-
def write_entry(_key, entry)
|
39
|
-
if entry.is_a?(Entry)
|
40
|
-
entry.dup_value!
|
41
|
-
end
|
42
|
-
super
|
43
|
-
end
|
44
|
-
|
45
|
-
def fetch_entry(key)
|
46
|
-
entry = super do
|
47
|
-
new_entry = yield
|
48
|
-
if entry.is_a?(Entry)
|
49
|
-
new_entry.dup_value!
|
50
|
-
end
|
51
|
-
new_entry
|
52
|
-
end
|
53
|
-
entry = entry.dup
|
54
|
-
|
55
|
-
if entry.is_a?(Entry)
|
56
|
-
entry.dup_value!
|
57
|
-
end
|
58
|
-
|
59
|
-
entry
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
def local_cache
|
65
|
-
if ActiveSupport::Cache.format_version == 6.1
|
66
|
-
if local_cache = super
|
67
|
-
DupLocalStore.new(local_cache)
|
68
|
-
end
|
69
|
-
else
|
70
|
-
super
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
prepend DupLocalCache
|
75
|
-
|
44
|
+
KEY_MAX_SIZE = 250
|
76
45
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
77
46
|
|
78
47
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
@@ -90,22 +59,21 @@ module ActiveSupport
|
|
90
59
|
addresses = nil if addresses.compact.empty?
|
91
60
|
pool_options = retrieve_pool_options(options)
|
92
61
|
|
93
|
-
if pool_options
|
94
|
-
Dalli::Client.new(addresses, options)
|
95
|
-
else
|
96
|
-
ensure_connection_pool_added!
|
62
|
+
if pool_options
|
97
63
|
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
64
|
+
else
|
65
|
+
Dalli::Client.new(addresses, options)
|
98
66
|
end
|
99
67
|
end
|
100
68
|
|
101
|
-
# Creates a new MemCacheStore object, with the given memcached server
|
69
|
+
# Creates a new +MemCacheStore+ object, with the given memcached server
|
102
70
|
# addresses. Each address is either a host name, or a host-with-port string
|
103
71
|
# in the form of "host_name:port". For example:
|
104
72
|
#
|
105
73
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
106
74
|
#
|
107
75
|
# If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
|
108
|
-
# MemCacheStore will connect to localhost:11211 (the default memcached port).
|
76
|
+
# +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
|
109
77
|
def initialize(*addresses)
|
110
78
|
addresses = addresses.flatten
|
111
79
|
options = addresses.extract_options!
|
@@ -115,17 +83,19 @@ module ActiveSupport
|
|
115
83
|
super(options)
|
116
84
|
|
117
85
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
118
|
-
raise ArgumentError, "First argument must be an empty array,
|
119
|
-
end
|
120
|
-
if addresses.first.is_a?(Dalli::Client)
|
121
|
-
@data = addresses.first
|
122
|
-
else
|
123
|
-
mem_cache_options = options.dup
|
124
|
-
# The value "compress: false" prevents duplicate compression within Dalli.
|
125
|
-
mem_cache_options[:compress] = false
|
126
|
-
(UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) }
|
127
|
-
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
86
|
+
raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
|
128
87
|
end
|
88
|
+
|
89
|
+
@mem_cache_options = options.dup
|
90
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
91
|
+
@mem_cache_options[:compress] = false
|
92
|
+
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
93
|
+
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
94
|
+
end
|
95
|
+
|
96
|
+
def inspect
|
97
|
+
instance = @data || @mem_cache_options
|
98
|
+
"#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>"
|
129
99
|
end
|
130
100
|
|
131
101
|
##
|
@@ -144,28 +114,54 @@ module ActiveSupport
|
|
144
114
|
# * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
|
145
115
|
# entry.
|
146
116
|
|
147
|
-
# Increment a cached value
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# to
|
117
|
+
# Increment a cached integer value using the memcached incr atomic operator.
|
118
|
+
# Returns the updated value.
|
119
|
+
#
|
120
|
+
# If the key is unset or has expired, it will be set to +amount+:
|
121
|
+
#
|
122
|
+
# cache.increment("foo") # => 1
|
123
|
+
# cache.increment("bar", 100) # => 100
|
124
|
+
#
|
125
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
126
|
+
#
|
127
|
+
# cache.write("baz", 5, raw: true)
|
128
|
+
# cache.increment("baz") # => 6
|
129
|
+
#
|
130
|
+
# Incrementing a non-numeric value, or a value written without
|
131
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
151
132
|
def increment(name, amount = 1, options = nil)
|
152
133
|
options = merged_options(options)
|
153
|
-
|
134
|
+
key = normalize_key(name, options)
|
135
|
+
|
136
|
+
instrument(:increment, key, amount: amount) do
|
154
137
|
rescue_error_with nil do
|
155
|
-
@data.with { |c| c.incr(
|
138
|
+
@data.with { |c| c.incr(key, amount, options[:expires_in], amount) }
|
156
139
|
end
|
157
140
|
end
|
158
141
|
end
|
159
142
|
|
160
|
-
# Decrement a cached value
|
161
|
-
#
|
162
|
-
#
|
163
|
-
# to
|
143
|
+
# Decrement a cached integer value using the memcached decr atomic operator.
|
144
|
+
# Returns the updated value.
|
145
|
+
#
|
146
|
+
# If the key is unset or has expired, it will be set to 0. Memcached
|
147
|
+
# does not support negative counters.
|
148
|
+
#
|
149
|
+
# cache.decrement("foo") # => 0
|
150
|
+
#
|
151
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
152
|
+
#
|
153
|
+
# cache.write("baz", 5, raw: true)
|
154
|
+
# cache.decrement("baz") # => 4
|
155
|
+
#
|
156
|
+
# Decrementing a non-numeric value, or a value written without
|
157
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
164
158
|
def decrement(name, amount = 1, options = nil)
|
165
159
|
options = merged_options(options)
|
166
|
-
|
160
|
+
key = normalize_key(name, options)
|
161
|
+
|
162
|
+
instrument(:decrement, key, amount: amount) do
|
167
163
|
rescue_error_with nil do
|
168
|
-
@data.with { |c| c.decr(
|
164
|
+
@data.with { |c| c.decr(key, amount, options[:expires_in], 0) }
|
169
165
|
end
|
170
166
|
end
|
171
167
|
end
|
@@ -182,54 +178,6 @@ module ActiveSupport
|
|
182
178
|
end
|
183
179
|
|
184
180
|
private
|
185
|
-
module Coders # :nodoc:
|
186
|
-
class << self
|
187
|
-
def [](version)
|
188
|
-
case version
|
189
|
-
when 6.1
|
190
|
-
Rails61Coder
|
191
|
-
when 7.0
|
192
|
-
Rails70Coder
|
193
|
-
else
|
194
|
-
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
module Loader
|
200
|
-
def load(payload)
|
201
|
-
if payload.is_a?(Entry)
|
202
|
-
payload
|
203
|
-
else
|
204
|
-
Cache::Coders::Loader.load(payload)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
module Rails61Coder
|
210
|
-
include Loader
|
211
|
-
extend self
|
212
|
-
|
213
|
-
def dump(entry)
|
214
|
-
entry
|
215
|
-
end
|
216
|
-
|
217
|
-
def dump_compressed(entry, threshold)
|
218
|
-
entry.compressed(threshold)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
module Rails70Coder
|
223
|
-
include Cache::Coders::Rails70Coder
|
224
|
-
include Loader
|
225
|
-
extend self
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
def default_coder
|
230
|
-
Coders[Cache.format_version]
|
231
|
-
end
|
232
|
-
|
233
181
|
# Read an entry from the cache.
|
234
182
|
def read_entry(key, **options)
|
235
183
|
deserialize_entry(read_serialized_entry(key, **options), **options)
|
@@ -253,10 +201,10 @@ module ActiveSupport
|
|
253
201
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
254
202
|
expires_in += 5.minutes
|
255
203
|
end
|
256
|
-
rescue_error_with
|
204
|
+
rescue_error_with nil do
|
257
205
|
# Don't pass compress option to Dalli since we are already dealing with compression.
|
258
206
|
options.delete(:compress)
|
259
|
-
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
207
|
+
@data.with { |c| !!c.send(method, key, payload, expires_in, **options) }
|
260
208
|
end
|
261
209
|
end
|
262
210
|
|
@@ -264,14 +212,22 @@ module ActiveSupport
|
|
264
212
|
def read_multi_entries(names, **options)
|
265
213
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
266
214
|
|
267
|
-
raw_values =
|
215
|
+
raw_values = begin
|
216
|
+
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
217
|
+
rescue Dalli::UnmarshalError
|
218
|
+
{}
|
219
|
+
end
|
220
|
+
|
268
221
|
values = {}
|
269
222
|
|
270
223
|
raw_values.each do |key, value|
|
271
224
|
entry = deserialize_entry(value, raw: options[:raw])
|
272
225
|
|
273
|
-
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
274
|
-
|
226
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
227
|
+
begin
|
228
|
+
values[keys_to_names[key]] = entry.value
|
229
|
+
rescue DeserializationError
|
230
|
+
end
|
275
231
|
end
|
276
232
|
end
|
277
233
|
|
@@ -299,7 +255,13 @@ module ActiveSupport
|
|
299
255
|
if key
|
300
256
|
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
301
257
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
302
|
-
|
258
|
+
|
259
|
+
if key.size > KEY_MAX_SIZE
|
260
|
+
key_separator = ":hash:"
|
261
|
+
key_hash = ActiveSupport::Digest.hexdigest(key)
|
262
|
+
key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
|
263
|
+
key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
|
264
|
+
end
|
303
265
|
end
|
304
266
|
key
|
305
267
|
end
|
@@ -315,8 +277,12 @@ module ActiveSupport
|
|
315
277
|
def rescue_error_with(fallback)
|
316
278
|
yield
|
317
279
|
rescue Dalli::DalliError => error
|
318
|
-
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
319
280
|
logger.error("DalliError (#{error}): #{error.message}") if logger
|
281
|
+
ActiveSupport.error_reporter&.report(
|
282
|
+
error,
|
283
|
+
severity: :warning,
|
284
|
+
source: "mem_cache_store.active_support",
|
285
|
+
)
|
320
286
|
fallback
|
321
287
|
end
|
322
288
|
end
|