activesupport 6.1.0 → 7.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1075 -325
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +32 -7
- data/lib/active_support/benchmarkable.rb +3 -2
- data/lib/active_support/broadcast_logger.rb +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +201 -62
- data/lib/active_support/cache/memory_store.rb +86 -24
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +186 -193
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +63 -71
- data/lib/active_support/cache.rb +487 -249
- data/lib/active_support/callbacks.rb +227 -105
- data/lib/active_support/code_generator.rb +70 -0
- data/lib/active_support/concern.rb +9 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +18 -5
- data/lib/active_support/configuration_file.rb +7 -2
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +15 -13
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/subclasses.rb +37 -26
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +24 -9
- data/lib/active_support/core_ext/date/conversions.rb +16 -15
- data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +85 -83
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +1 -2
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +4 -4
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +8 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +49 -22
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +81 -43
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/name_error.rb +2 -8
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -77
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +31 -11
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +49 -27
- data/lib/active_support/core_ext/object/to_query.rb +2 -4
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +0 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/conversions.rb +2 -2
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +17 -10
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/string/output_safety.rb +85 -165
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +30 -8
- data/lib/active_support/core_ext/time/conversions.rb +15 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes.rb +47 -20
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -788
- data/lib/active_support/deprecation/behaviors.rb +66 -40
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +9 -26
- data/lib/active_support/deprecation/proxy_wrappers.rb +38 -23
- data/lib/active_support/deprecation/reporting.rb +43 -26
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -72
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +9 -3
- data/lib/active_support/duration.rb +83 -52
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +29 -13
- data/lib/active_support/environment_inquirer.rb +23 -3
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -0
- data/lib/active_support/evented_file_update_checker.rb +20 -7
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +44 -22
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +28 -11
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +44 -19
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +21 -14
- data/lib/active_support/inflector/inflections.rb +25 -7
- data/lib/active_support/inflector/methods.rb +50 -64
- data/lib/active_support/inflector/transliterate.rb +4 -2
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +27 -45
- data/lib/active_support/key_generator.rb +31 -6
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +4 -2
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +97 -35
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_thread_safe_level.rb +11 -34
- data/lib/active_support/message_encryptor.rb +206 -56
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +235 -84
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +112 -46
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +12 -11
- data/lib/active_support/multibyte/unicode.rb +9 -49
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +304 -114
- data/lib/active_support/notifications/instrumenter.rb +117 -35
- data/lib/active_support/notifications.rb +25 -25
- data/lib/active_support/number_helper/number_converter.rb +14 -7
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -4
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +10 -6
- data/lib/active_support/number_helper/rounding_helper.rb +2 -6
- data/lib/active_support/number_helper.rb +379 -319
- data/lib/active_support/option_merger.rb +10 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +15 -1
- data/lib/active_support/parameter_filter.rb +105 -81
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +83 -21
- data/lib/active_support/reloader.rb +13 -5
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +18 -11
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +3 -3
- data/lib/active_support/subscriber.rb +11 -40
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +65 -25
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +61 -15
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- data/lib/active_support/testing/parallelization/server.rb +4 -0
- data/lib/active_support/testing/parallelization/worker.rb +3 -0
- data/lib/active_support/testing/parallelization.rb +4 -0
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +49 -16
- data/lib/active_support/time_with_zone.rb +39 -28
- data/lib/active_support/values/time_zone.rb +50 -18
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +28 -1
- metadata +150 -18
- data/lib/active_support/core_ext/marshal.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -28
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -29
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
@@ -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| 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.
|
@@ -64,64 +96,116 @@ module ActiveSupport
|
|
64
96
|
def self.build_mem_cache(*addresses) # :nodoc:
|
65
97
|
addresses = addresses.flatten
|
66
98
|
options = addresses.extract_options!
|
67
|
-
addresses = nil if addresses.empty?
|
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,27 +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
|
-
key = super
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
192
324
|
key
|
193
325
|
end
|
194
326
|
|
195
|
-
def deserialize_entry(payload)
|
196
|
-
|
197
|
-
|
198
|
-
|
327
|
+
def deserialize_entry(payload, raw: false, **)
|
328
|
+
if payload && raw
|
329
|
+
Entry.new(payload)
|
330
|
+
else
|
331
|
+
super(payload)
|
332
|
+
end
|
199
333
|
end
|
200
334
|
|
201
335
|
def rescue_error_with(fallback)
|
202
336
|
yield
|
203
|
-
rescue Dalli::DalliError =>
|
204
|
-
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
|
+
)
|
205
344
|
fallback
|
206
345
|
end
|
207
346
|
end
|