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
@@ -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"
|
@@ -20,7 +17,7 @@ module ActiveSupport
|
|
20
17
|
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
21
18
|
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
|
22
19
|
|
23
|
-
def initialize(cache_path, options
|
20
|
+
def initialize(cache_path, **options)
|
24
21
|
super(options)
|
25
22
|
@cache_path = cache_path.to_s
|
26
23
|
end
|
@@ -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|
|
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,15 +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 "
|
10
|
+
require "connection_pool"
|
11
|
+
require "delegate"
|
12
|
+
require "active_support/core_ext/enumerable"
|
11
13
|
require "active_support/core_ext/array/extract_options"
|
14
|
+
require "active_support/core_ext/numeric/time"
|
12
15
|
|
13
16
|
module ActiveSupport
|
14
17
|
module Cache
|
18
|
+
# = Memcached \Cache \Store
|
19
|
+
#
|
15
20
|
# A cache store implementation which stores data in Memcached:
|
16
21
|
# https://memcached.org
|
17
22
|
#
|
@@ -19,25 +24,15 @@ module ActiveSupport
|
|
19
24
|
#
|
20
25
|
# Special features:
|
21
26
|
# - Clustering and load balancing. One can specify multiple memcached servers,
|
22
|
-
# and MemCacheStore will load balance between all available servers. If a
|
23
|
-
# 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.
|
24
29
|
#
|
25
|
-
# MemCacheStore implements the Strategy::LocalCache strategy which
|
26
|
-
# 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.
|
27
32
|
class MemCacheStore < Store
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
def write_entry(key, entry, **options)
|
32
|
-
if options[:raw] && local_cache
|
33
|
-
raw_entry = Entry.new(entry.value.to_s)
|
34
|
-
raw_entry.expires_at = entry.expires_at
|
35
|
-
super(key, raw_entry, **options)
|
36
|
-
else
|
37
|
-
super
|
38
|
-
end
|
39
|
-
end
|
40
|
-
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
|
41
36
|
|
42
37
|
# Advertise cache versioning support.
|
43
38
|
def self.supports_cache_versioning?
|
@@ -45,78 +40,172 @@ module ActiveSupport
|
|
45
40
|
end
|
46
41
|
|
47
42
|
prepend Strategy::LocalCache
|
48
|
-
prepend LocalCacheWithRaw
|
49
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
|
50
85
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
51
86
|
|
52
87
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
53
|
-
#
|
88
|
+
# If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
|
89
|
+
# - ENV["MEMCACHE_SERVERS"] (if defined)
|
90
|
+
# - "127.0.0.1:11211" (otherwise)
|
54
91
|
#
|
55
92
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
|
56
|
-
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["
|
93
|
+
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
|
57
94
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
|
58
95
|
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
|
59
96
|
def self.build_mem_cache(*addresses) # :nodoc:
|
60
97
|
addresses = addresses.flatten
|
61
98
|
options = addresses.extract_options!
|
62
|
-
addresses =
|
99
|
+
addresses = nil if addresses.compact.empty?
|
63
100
|
pool_options = retrieve_pool_options(options)
|
64
101
|
|
65
|
-
if pool_options
|
66
|
-
Dalli::Client.new(addresses, options)
|
67
|
-
else
|
68
|
-
ensure_connection_pool_added!
|
102
|
+
if pool_options
|
69
103
|
ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
|
104
|
+
else
|
105
|
+
Dalli::Client.new(addresses, options)
|
70
106
|
end
|
71
107
|
end
|
72
108
|
|
73
|
-
# Creates a new MemCacheStore object, with the given memcached server
|
109
|
+
# Creates a new +MemCacheStore+ object, with the given memcached server
|
74
110
|
# addresses. Each address is either a host name, or a host-with-port string
|
75
111
|
# in the form of "host_name:port". For example:
|
76
112
|
#
|
77
113
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
78
114
|
#
|
79
|
-
# If no addresses are
|
80
|
-
#
|
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.
|
81
118
|
def initialize(*addresses)
|
82
119
|
addresses = addresses.flatten
|
83
120
|
options = addresses.extract_options!
|
121
|
+
if options.key?(:cache_nils)
|
122
|
+
options[:skip_nil] = !options.delete(:cache_nils)
|
123
|
+
end
|
84
124
|
super(options)
|
85
125
|
|
86
126
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
87
|
-
raise ArgumentError, "First argument must be an empty array,
|
127
|
+
raise ArgumentError, "First argument must be an empty array, address, or array of addresses."
|
88
128
|
end
|
89
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
|
90
134
|
@data = addresses.first
|
91
135
|
else
|
92
|
-
mem_cache_options = options.dup
|
93
|
-
|
94
|
-
@
|
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]))
|
95
141
|
end
|
96
142
|
end
|
97
143
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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+.
|
102
180
|
def increment(name, amount = 1, options = nil)
|
103
181
|
options = merged_options(options)
|
104
182
|
instrument(:increment, name, amount: amount) do
|
105
183
|
rescue_error_with nil do
|
106
|
-
@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) }
|
107
185
|
end
|
108
186
|
end
|
109
187
|
end
|
110
188
|
|
111
|
-
# Decrement a cached value
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# 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+.
|
115
204
|
def decrement(name, amount = 1, options = nil)
|
116
205
|
options = merged_options(options)
|
117
206
|
instrument(:decrement, name, amount: amount) do
|
118
207
|
rescue_error_with nil do
|
119
|
-
@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) }
|
120
209
|
end
|
121
210
|
end
|
122
211
|
end
|
@@ -133,37 +222,70 @@ module ActiveSupport
|
|
133
222
|
end
|
134
223
|
|
135
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
|
+
|
136
239
|
# Read an entry from the cache.
|
137
240
|
def read_entry(key, **options)
|
138
|
-
|
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
|
139
248
|
end
|
140
249
|
|
141
250
|
# Write an entry to the cache.
|
142
251
|
def write_entry(key, entry, **options)
|
143
|
-
|
144
|
-
|
252
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
253
|
+
end
|
254
|
+
|
255
|
+
def write_serialized_entry(key, payload, **options)
|
256
|
+
method = options[:unless_exist] ? :add : :set
|
145
257
|
expires_in = options[:expires_in].to_i
|
146
|
-
if expires_in > 0 && !options[:raw]
|
258
|
+
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
|
147
259
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
148
260
|
expires_in += 5.minutes
|
149
261
|
end
|
150
262
|
rescue_error_with false do
|
151
|
-
|
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) }
|
152
266
|
end
|
153
267
|
end
|
154
268
|
|
155
269
|
# Reads multiple entries from the cache implementation.
|
156
270
|
def read_multi_entries(names, **options)
|
157
|
-
keys_to_names =
|
271
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
272
|
+
|
273
|
+
raw_values = begin
|
274
|
+
@data.with { |c| c.get_multi(keys_to_names.keys) }
|
275
|
+
rescue Dalli::UnmarshalError
|
276
|
+
{}
|
277
|
+
end
|
158
278
|
|
159
|
-
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
160
279
|
values = {}
|
161
280
|
|
162
281
|
raw_values.each do |key, value|
|
163
|
-
entry = deserialize_entry(value)
|
282
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
164
283
|
|
165
|
-
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
166
|
-
|
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
|
167
289
|
end
|
168
290
|
end
|
169
291
|
|
@@ -175,27 +297,50 @@ module ActiveSupport
|
|
175
297
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
176
298
|
end
|
177
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
|
+
|
178
308
|
# Memcache keys are binaries. So we need to force their encoding to binary
|
179
309
|
# before applying the regular expression to ensure we are escaping all
|
180
310
|
# characters properly.
|
181
311
|
def normalize_key(key, options)
|
182
|
-
key = super
|
183
|
-
|
184
|
-
|
185
|
-
|
312
|
+
key = super
|
313
|
+
if key
|
314
|
+
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
315
|
+
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
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
|
186
324
|
key
|
187
325
|
end
|
188
326
|
|
189
|
-
def deserialize_entry(
|
190
|
-
if
|
191
|
-
|
327
|
+
def deserialize_entry(payload, raw: false, **)
|
328
|
+
if payload && raw
|
329
|
+
Entry.new(payload)
|
330
|
+
else
|
331
|
+
super(payload)
|
192
332
|
end
|
193
333
|
end
|
194
334
|
|
195
335
|
def rescue_error_with(fallback)
|
196
336
|
yield
|
197
|
-
rescue Dalli::DalliError =>
|
198
|
-
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
|
+
)
|
199
344
|
fallback
|
200
345
|
end
|
201
346
|
end
|