activesupport 6.1.7.2 → 7.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +866 -411
- 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 +2 -2
- data/lib/active_support/backtrace_cleaner.rb +27 -7
- data/lib/active_support/benchmarkable.rb +3 -2
- 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 +52 -19
- data/lib/active_support/cache/mem_cache_store.rb +195 -60
- 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 +478 -247
- data/lib/active_support/callbacks.rb +227 -105
- data/lib/active_support/code_generator.rb +65 -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 +1 -1
- 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 -14
- 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 +41 -18
- 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 +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 +40 -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 -193
- 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 +29 -10
- data/lib/active_support/core_ext/time/conversions.rb +14 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +1 -0
- data/lib/active_support/current_attributes.rb +46 -21
- 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 +42 -25
- 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 +4 -4
- 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 +79 -49
- 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 +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 +28 -13
- 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 +38 -18
- data/lib/active_support/html_safe_translation.rb +43 -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 -63
- 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 +3 -1
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +96 -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 +109 -35
- data/lib/active_support/notifications.rb +24 -24
- 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/rounding_helper.rb +1 -5
- 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 +104 -80
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +83 -21
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +14 -12
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +18 -11
- 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 +70 -0
- data/lib/active_support/tagged_logging.rb +60 -24
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +60 -14
- 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/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 +38 -17
- 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 +107 -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 -120
- data/lib/active_support/per_thread_registry.rb +0 -61
@@ -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
|
@@ -1,18 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/marshal"
|
4
3
|
require "active_support/core_ext/file/atomic"
|
5
4
|
require "active_support/core_ext/string/conversions"
|
6
5
|
require "uri/common"
|
7
6
|
|
8
7
|
module ActiveSupport
|
9
8
|
module Cache
|
10
|
-
#
|
9
|
+
# = \File \Cache \Store
|
11
10
|
#
|
12
|
-
#
|
13
|
-
# an in-memory cache inside of a block.
|
11
|
+
# A cache store implementation which stores everything on the filesystem.
|
14
12
|
class FileStore < Store
|
15
|
-
prepend Strategy::LocalCache
|
16
13
|
attr_reader :cache_path
|
17
14
|
|
18
15
|
DIR_FORMATTER = "%03X"
|
@@ -48,14 +45,33 @@ module ActiveSupport
|
|
48
45
|
end
|
49
46
|
end
|
50
47
|
|
51
|
-
#
|
52
|
-
#
|
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
|
+
#
|
53
60
|
def increment(name, amount = 1, options = nil)
|
54
61
|
modify_value(name, amount, options)
|
55
62
|
end
|
56
63
|
|
57
|
-
#
|
58
|
-
#
|
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
|
+
#
|
59
75
|
def decrement(name, amount = 1, options = nil)
|
60
76
|
modify_value(name, -amount, options)
|
61
77
|
end
|
@@ -71,21 +87,33 @@ module ActiveSupport
|
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
90
|
+
def inspect # :nodoc:
|
91
|
+
"#<#{self.class.name} cache_path=#{@cache_path}, options=#{@options.inspect}>"
|
92
|
+
end
|
93
|
+
|
74
94
|
private
|
75
95
|
def read_entry(key, **options)
|
76
|
-
if
|
77
|
-
entry =
|
96
|
+
if payload = read_serialized_entry(key, **options)
|
97
|
+
entry = deserialize_entry(payload)
|
78
98
|
entry if entry.is_a?(Cache::Entry)
|
79
99
|
end
|
80
|
-
|
81
|
-
|
100
|
+
end
|
101
|
+
|
102
|
+
def read_serialized_entry(key, **)
|
103
|
+
File.binread(key) if File.exist?(key)
|
104
|
+
rescue => error
|
105
|
+
logger.error("FileStoreError (#{error}): #{error.message}") if logger
|
82
106
|
nil
|
83
107
|
end
|
84
108
|
|
85
109
|
def write_entry(key, entry, **options)
|
110
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
111
|
+
end
|
112
|
+
|
113
|
+
def write_serialized_entry(key, payload, **options)
|
86
114
|
return false if options[:unless_exist] && File.exist?(key)
|
87
115
|
ensure_cache_path(File.dirname(key))
|
88
|
-
File.atomic_write(key, cache_path) { |f| f.write(
|
116
|
+
File.atomic_write(key, cache_path) { |f| f.write(payload) }
|
89
117
|
true
|
90
118
|
end
|
91
119
|
|
@@ -95,11 +123,13 @@ module ActiveSupport
|
|
95
123
|
File.delete(key)
|
96
124
|
delete_empty_directories(File.dirname(key))
|
97
125
|
true
|
98
|
-
rescue
|
126
|
+
rescue
|
99
127
|
# Just in case the error was caused by another process deleting the file first.
|
100
|
-
raise
|
128
|
+
raise if File.exist?(key)
|
101
129
|
false
|
102
130
|
end
|
131
|
+
else
|
132
|
+
false
|
103
133
|
end
|
104
134
|
end
|
105
135
|
|
@@ -146,7 +176,7 @@ module ActiveSupport
|
|
146
176
|
|
147
177
|
# Translate a file path into a key.
|
148
178
|
def file_path_key(path)
|
149
|
-
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
|
179
|
+
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last.delete(File::SEPARATOR)
|
150
180
|
URI.decode_www_form_component(fname, Encoding::UTF_8)
|
151
181
|
end
|
152
182
|
|
@@ -176,8 +206,8 @@ module ActiveSupport
|
|
176
206
|
end
|
177
207
|
end
|
178
208
|
|
179
|
-
# Modifies the amount of an
|
180
|
-
# If the key is not found
|
209
|
+
# Modifies the amount of an integer value that is stored in the cache.
|
210
|
+
# If the key is not found it is created and set to +amount+.
|
181
211
|
def modify_value(name, amount, options)
|
182
212
|
file_name = normalize_key(name, options)
|
183
213
|
|
@@ -188,6 +218,9 @@ module ActiveSupport
|
|
188
218
|
num = num.to_i + amount
|
189
219
|
write(name, num, options)
|
190
220
|
num
|
221
|
+
else
|
222
|
+
write(name, Integer(amount), options)
|
223
|
+
amount
|
191
224
|
end
|
192
225
|
end
|
193
226
|
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"
|
11
|
+
require "delegate"
|
10
12
|
require "active_support/core_ext/enumerable"
|
11
|
-
require "active_support/core_ext/marshal"
|
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,27 +24,15 @@ 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
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
module LocalCacheWithRaw # :nodoc:
|
33
|
-
private
|
34
|
-
def write_entry(key, entry, **options)
|
35
|
-
if options[:raw] && local_cache
|
36
|
-
raw_entry = Entry.new(entry.value.to_s)
|
37
|
-
raw_entry.expires_at = entry.expires_at
|
38
|
-
super(key, raw_entry, **options)
|
39
|
-
else
|
40
|
-
super
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
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
|
44
36
|
|
45
37
|
# Advertise cache versioning support.
|
46
38
|
def self.supports_cache_versioning?
|
@@ -48,8 +40,48 @@ module ActiveSupport
|
|
48
40
|
end
|
49
41
|
|
50
42
|
prepend Strategy::LocalCache
|
51
|
-
prepend LocalCacheWithRaw
|
52
43
|
|
44
|
+
module DupLocalCache
|
45
|
+
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
46
|
+
def write_entry(_key, entry)
|
47
|
+
if entry.is_a?(Entry)
|
48
|
+
entry.dup_value!
|
49
|
+
end
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_entry(key)
|
54
|
+
entry = super do
|
55
|
+
new_entry = yield
|
56
|
+
if entry.is_a?(Entry)
|
57
|
+
new_entry.dup_value!
|
58
|
+
end
|
59
|
+
new_entry
|
60
|
+
end
|
61
|
+
entry = entry.dup
|
62
|
+
|
63
|
+
if entry.is_a?(Entry)
|
64
|
+
entry.dup_value!
|
65
|
+
end
|
66
|
+
|
67
|
+
entry
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def local_cache
|
73
|
+
if ActiveSupport::Cache.format_version == 6.1
|
74
|
+
if local_cache = super
|
75
|
+
DupLocalStore.new(local_cache)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
prepend DupLocalCache
|
83
|
+
|
84
|
+
KEY_MAX_SIZE = 250
|
53
85
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
54
86
|
|
55
87
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
@@ -67,61 +99,113 @@ module ActiveSupport
|
|
67
99
|
addresses = nil if addresses.compact.empty?
|
68
100
|
pool_options = retrieve_pool_options(options)
|
69
101
|
|
70
|
-
if pool_options
|
71
|
-
Dalli::Client.new(addresses, options)
|
72
|
-
else
|
73
|
-
ensure_connection_pool_added!
|
102
|
+
if pool_options
|
74
103
|
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
104
|
+
else
|
105
|
+
Dalli::Client.new(addresses, options)
|
75
106
|
end
|
76
107
|
end
|
77
108
|
|
78
|
-
# Creates a new MemCacheStore object, with the given memcached server
|
109
|
+
# Creates a new +MemCacheStore+ object, with the given memcached server
|
79
110
|
# addresses. Each address is either a host name, or a host-with-port string
|
80
111
|
# in the form of "host_name:port". For example:
|
81
112
|
#
|
82
113
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
83
114
|
#
|
84
|
-
# If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise,
|
85
|
-
# MemCacheStore will connect to localhost:11211 (the default memcached port).
|
115
|
+
# If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
|
116
|
+
# +MemCacheStore+ will connect to localhost:11211 (the default memcached port).
|
117
|
+
# Passing a +Dalli::Client+ instance is deprecated and will be removed. Please pass an address instead.
|
86
118
|
def initialize(*addresses)
|
87
119
|
addresses = addresses.flatten
|
88
120
|
options = addresses.extract_options!
|
121
|
+
if options.key?(:cache_nils)
|
122
|
+
options[:skip_nil] = !options.delete(:cache_nils)
|
123
|
+
end
|
89
124
|
super(options)
|
90
125
|
|
91
126
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
92
|
-
raise ArgumentError, "First argument must be an empty array,
|
127
|
+
raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
|
93
128
|
end
|
94
129
|
if addresses.first.is_a?(Dalli::Client)
|
130
|
+
ActiveSupport.deprecator.warn(<<~MSG)
|
131
|
+
Initializing MemCacheStore with a Dalli::Client is deprecated and will be removed in Rails 7.2.
|
132
|
+
Use memcached server addresses instead.
|
133
|
+
MSG
|
95
134
|
@data = addresses.first
|
96
135
|
else
|
97
|
-
mem_cache_options = options.dup
|
98
|
-
|
99
|
-
@
|
136
|
+
@mem_cache_options = options.dup
|
137
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
138
|
+
@mem_cache_options[:compress] = false
|
139
|
+
(OVERRIDDEN_OPTIONS - %i(compress)).each { |name| @mem_cache_options.delete(name) }
|
140
|
+
@data = self.class.build_mem_cache(*(addresses + [@mem_cache_options]))
|
100
141
|
end
|
101
142
|
end
|
102
143
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
144
|
+
def inspect
|
145
|
+
instance = @data || @mem_cache_options
|
146
|
+
"#<#{self.class} options=#{options.inspect} mem_cache=#{instance.inspect}>"
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# :method: write
|
151
|
+
# :call-seq: write(name, value, options = nil)
|
152
|
+
#
|
153
|
+
# Behaves the same as ActiveSupport::Cache::Store#write, but supports
|
154
|
+
# additional options specific to memcached.
|
155
|
+
#
|
156
|
+
# ==== Additional Options
|
157
|
+
#
|
158
|
+
# * <tt>raw: true</tt> - Sends the value directly to the server as raw
|
159
|
+
# bytes. The value must be a string or number. You can use memcached
|
160
|
+
# direct operations like +increment+ and +decrement+ only on raw values.
|
161
|
+
#
|
162
|
+
# * <tt>unless_exist: true</tt> - Prevents overwriting an existing cache
|
163
|
+
# entry.
|
164
|
+
|
165
|
+
# Increment a cached integer value using the memcached incr atomic operator.
|
166
|
+
# Returns the updated value.
|
167
|
+
#
|
168
|
+
# If the key is unset or has expired, it will be set to +amount+:
|
169
|
+
#
|
170
|
+
# cache.increment("foo") # => 1
|
171
|
+
# cache.increment("bar", 100) # => 100
|
172
|
+
#
|
173
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
174
|
+
#
|
175
|
+
# cache.write("baz", 5, raw: true)
|
176
|
+
# cache.increment("baz") # => 6
|
177
|
+
#
|
178
|
+
# Incrementing a non-numeric value, or a value written without
|
179
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
107
180
|
def increment(name, amount = 1, options = nil)
|
108
181
|
options = merged_options(options)
|
109
182
|
instrument(:increment, name, amount: amount) do
|
110
183
|
rescue_error_with nil do
|
111
|
-
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
|
184
|
+
@data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in], amount) }
|
112
185
|
end
|
113
186
|
end
|
114
187
|
end
|
115
188
|
|
116
|
-
# Decrement a cached value
|
117
|
-
#
|
118
|
-
#
|
119
|
-
# to
|
189
|
+
# Decrement a cached integer value using the memcached decr atomic operator.
|
190
|
+
# Returns the updated value.
|
191
|
+
#
|
192
|
+
# If the key is unset or has expired, it will be set to 0. Memcached
|
193
|
+
# does not support negative counters.
|
194
|
+
#
|
195
|
+
# cache.decrement("foo") # => 0
|
196
|
+
#
|
197
|
+
# To set a specific value, call #write passing <tt>raw: true</tt>:
|
198
|
+
#
|
199
|
+
# cache.write("baz", 5, raw: true)
|
200
|
+
# cache.decrement("baz") # => 4
|
201
|
+
#
|
202
|
+
# Decrementing a non-numeric value, or a value written without
|
203
|
+
# <tt>raw: true</tt>, will fail and return +nil+.
|
120
204
|
def decrement(name, amount = 1, options = nil)
|
121
205
|
options = merged_options(options)
|
122
206
|
instrument(:decrement, name, amount: amount) do
|
123
207
|
rescue_error_with nil do
|
124
|
-
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
|
208
|
+
@data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in], 0) }
|
125
209
|
end
|
126
210
|
end
|
127
211
|
end
|
@@ -138,23 +222,47 @@ module ActiveSupport
|
|
138
222
|
end
|
139
223
|
|
140
224
|
private
|
225
|
+
def default_serializer
|
226
|
+
if Cache.format_version == 6.1
|
227
|
+
ActiveSupport.deprecator.warn <<~EOM
|
228
|
+
Support for `config.active_support.cache_format_version = 6.1` has been deprecated and will be removed in Rails 7.2.
|
229
|
+
|
230
|
+
Check the Rails upgrade guide at https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#new-activesupport-cache-serialization-format
|
231
|
+
for more information on how to upgrade.
|
232
|
+
EOM
|
233
|
+
Cache::SerializerWithFallback[:passthrough]
|
234
|
+
else
|
235
|
+
super
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
141
239
|
# Read an entry from the cache.
|
142
240
|
def read_entry(key, **options)
|
143
|
-
|
241
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
242
|
+
end
|
243
|
+
|
244
|
+
def read_serialized_entry(key, **options)
|
245
|
+
rescue_error_with(nil) do
|
246
|
+
@data.with { |c| c.get(key, options) }
|
247
|
+
end
|
144
248
|
end
|
145
249
|
|
146
250
|
# Write an entry to the cache.
|
147
251
|
def write_entry(key, entry, **options)
|
252
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_serialized_entry(key, payload, **options)
|
148
256
|
method = options[:unless_exist] ? :add : :set
|
149
|
-
value = options[:raw] ? entry.value.to_s : serialize_entry(entry)
|
150
257
|
expires_in = options[:expires_in].to_i
|
151
258
|
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
|
152
259
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
153
260
|
expires_in += 5.minutes
|
154
261
|
end
|
155
262
|
rescue_error_with false do
|
156
|
-
#
|
157
|
-
|
263
|
+
# Don't pass compress option to Dalli since we are already dealing with compression.
|
264
|
+
options.delete(:compress)
|
265
|
+
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
158
266
|
end
|
159
267
|
end
|
160
268
|
|
@@ -162,14 +270,22 @@ module ActiveSupport
|
|
162
270
|
def read_multi_entries(names, **options)
|
163
271
|
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
164
272
|
|
165
|
-
raw_values =
|
273
|
+
raw_values = begin
|
274
|
+
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
275
|
+
rescue Dalli::UnmarshalError
|
276
|
+
{}
|
277
|
+
end
|
278
|
+
|
166
279
|
values = {}
|
167
280
|
|
168
281
|
raw_values.each do |key, value|
|
169
|
-
entry = deserialize_entry(value)
|
282
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
170
283
|
|
171
|
-
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
172
|
-
|
284
|
+
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
285
|
+
begin
|
286
|
+
values[keys_to_names[key]] = entry.value
|
287
|
+
rescue DeserializationError
|
288
|
+
end
|
173
289
|
end
|
174
290
|
end
|
175
291
|
|
@@ -181,31 +297,50 @@ module ActiveSupport
|
|
181
297
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
182
298
|
end
|
183
299
|
|
300
|
+
def serialize_entry(entry, raw: false, **options)
|
301
|
+
if raw
|
302
|
+
entry.value.to_s
|
303
|
+
else
|
304
|
+
super(entry, raw: raw, **options)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
184
308
|
# Memcache keys are binaries. So we need to force their encoding to binary
|
185
309
|
# before applying the regular expression to ensure we are escaping all
|
186
310
|
# characters properly.
|
187
311
|
def normalize_key(key, options)
|
188
312
|
key = super
|
189
|
-
|
190
313
|
if key
|
191
314
|
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
192
315
|
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
193
|
-
key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
|
194
|
-
end
|
195
316
|
|
317
|
+
if key.size > KEY_MAX_SIZE
|
318
|
+
key_separator = ":hash:"
|
319
|
+
key_hash = ActiveSupport::Digest.hexdigest(key)
|
320
|
+
key_trim_size = KEY_MAX_SIZE - key_separator.size - key_hash.size
|
321
|
+
key = "#{key[0, key_trim_size]}#{key_separator}#{key_hash}"
|
322
|
+
end
|
323
|
+
end
|
196
324
|
key
|
197
325
|
end
|
198
326
|
|
199
|
-
def deserialize_entry(payload)
|
200
|
-
|
201
|
-
|
202
|
-
|
327
|
+
def deserialize_entry(payload, raw: false, **)
|
328
|
+
if payload && raw
|
329
|
+
Entry.new(payload)
|
330
|
+
else
|
331
|
+
super(payload)
|
332
|
+
end
|
203
333
|
end
|
204
334
|
|
205
335
|
def rescue_error_with(fallback)
|
206
336
|
yield
|
207
|
-
rescue Dalli::DalliError =>
|
208
|
-
logger.error("DalliError (#{
|
337
|
+
rescue Dalli::DalliError => error
|
338
|
+
logger.error("DalliError (#{error}): #{error.message}") if logger
|
339
|
+
ActiveSupport.error_reporter&.report(
|
340
|
+
error,
|
341
|
+
severity: :warning,
|
342
|
+
source: "mem_cache_store.active_support",
|
343
|
+
)
|
209
344
|
fallback
|
210
345
|
end
|
211
346
|
end
|