activesupport 6.0.3 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +222 -499
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_support/actionable_error.rb +1 -1
- data/lib/active_support/array_inquirer.rb +2 -2
- data/lib/active_support/backtrace_cleaner.rb +3 -3
- data/lib/active_support/benchmarkable.rb +3 -3
- data/lib/active_support/cache/file_store.rb +18 -11
- data/lib/active_support/cache/mem_cache_store.rb +140 -44
- data/lib/active_support/cache/memory_store.rb +56 -28
- data/lib/active_support/cache/null_store.rb +10 -2
- data/lib/active_support/cache/redis_cache_store.rb +65 -85
- data/lib/active_support/cache/strategy/local_cache.rb +46 -57
- data/lib/active_support/cache.rb +273 -82
- data/lib/active_support/callbacks.rb +226 -118
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +49 -5
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +9 -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 +9 -7
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array.rb +1 -0
- 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 +21 -40
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +4 -4
- data/lib/active_support/core_ext/date/conversions.rb +5 -4
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/date.rb +1 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
- 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/conversions.rb +5 -5
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/date_time.rb +1 -0
- data/lib/active_support/core_ext/digest/uuid.rb +39 -13
- data/lib/active_support/core_ext/enumerable.rb +139 -15
- data/lib/active_support/core_ext/file/atomic.rb +1 -1
- data/lib/active_support/core_ext/hash/conversions.rb +2 -2
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
- data/lib/active_support/core_ext/hash/except.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +2 -2
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- 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 +25 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +40 -36
- data/lib/active_support/core_ext/module/introspection.rb +1 -25
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +79 -72
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
- data/lib/active_support/core_ext/numeric.rb +1 -0
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +11 -0
- data/lib/active_support/core_ext/object/json.rb +42 -26
- data/lib/active_support/core_ext/object/to_query.rb +2 -2
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with_options.rb +20 -1
- data/lib/active_support/core_ext/pathname/existence.rb +21 -0
- data/lib/active_support/core_ext/pathname.rb +3 -0
- data/lib/active_support/core_ext/range/compare_range.rb +6 -25
- data/lib/active_support/core_ext/range/conversions.rb +8 -8
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/range/each.rb +1 -1
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/regexp.rb +8 -1
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +1 -0
- data/lib/active_support/core_ext/string/filters.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +39 -5
- data/lib/active_support/core_ext/string/inquiry.rb +1 -0
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +69 -45
- 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/time/calculations.rb +26 -6
- data/lib/active_support/core_ext/time/conversions.rb +6 -3
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +4 -19
- data/lib/active_support/core_ext/time.rb +1 -0
- data/lib/active_support/core_ext/uri.rb +3 -23
- 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 +39 -16
- 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 -764
- data/lib/active_support/deprecation/behaviors.rb +19 -3
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +0 -1
- data/lib/active_support/deprecation/method_wrappers.rb +6 -5
- data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
- data/lib/active_support/deprecation/reporting.rb +50 -7
- data/lib/active_support/deprecation.rb +6 -1
- data/lib/active_support/descendants_tracker.rb +177 -64
- 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 -10
- data/lib/active_support/duration.rb +134 -55
- data/lib/active_support/encrypted_configuration.rb +11 -1
- data/lib/active_support/encrypted_file.rb +20 -3
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/error_reporter.rb +117 -0
- data/lib/active_support/evented_file_update_checker.rb +70 -134
- 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 +30 -4
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/fork_tracker.rb +71 -0
- data/lib/active_support/gem_version.rb +2 -2
- data/lib/active_support/hash_with_indifferent_access.rb +51 -25
- data/lib/active_support/html_safe_translation.rb +43 -0
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +14 -19
- data/lib/active_support/inflector/inflections.rb +24 -9
- data/lib/active_support/inflector/methods.rb +29 -49
- data/lib/active_support/inflector/transliterate.rb +4 -4
- data/lib/active_support/isolated_execution_state.rb +56 -0
- data/lib/active_support/json/decoding.rb +4 -4
- data/lib/active_support/json/encoding.rb +8 -4
- data/lib/active_support/key_generator.rb +19 -2
- data/lib/active_support/locale/en.yml +8 -4
- data/lib/active_support/log_subscriber.rb +21 -3
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +34 -21
- data/lib/active_support/message_encryptor.rb +12 -10
- data/lib/active_support/message_verifier.rb +50 -18
- data/lib/active_support/messages/metadata.rb +11 -3
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +6 -5
- data/lib/active_support/multibyte/chars.rb +13 -52
- data/lib/active_support/multibyte/unicode.rb +1 -87
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +110 -69
- data/lib/active_support/notifications/instrumenter.rb +37 -29
- data/lib/active_support/notifications.rb +47 -26
- data/lib/active_support/number_helper/number_converter.rb +2 -4
- data/lib/active_support/number_helper/number_to_currency_converter.rb +10 -9
- 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 +2 -2
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -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 +29 -16
- data/lib/active_support/option_merger.rb +9 -16
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +8 -2
- data/lib/active_support/parameter_filter.rb +21 -11
- data/lib/active_support/per_thread_registry.rb +6 -1
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +77 -5
- data/lib/active_support/rescuable.rb +6 -6
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +51 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +2 -2
- data/lib/active_support/subscriber.rb +19 -25
- data/lib/active_support/tagged_logging.rb +31 -6
- data/lib/active_support/test_case.rb +9 -21
- data/lib/active_support/testing/assertions.rb +49 -12
- data/lib/active_support/testing/deprecation.rb +52 -1
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +5 -5
- 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 +76 -0
- data/lib/active_support/testing/stream.rb +3 -5
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +53 -5
- data/lib/active_support/time_with_zone.rb +120 -55
- data/lib/active_support/values/time_zone.rb +49 -18
- data/lib/active_support/xml_mini/jdom.rb +1 -1
- 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 +4 -4
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +9 -2
- data/lib/active_support/xml_mini.rb +5 -4
- data/lib/active_support.rb +29 -1
- metadata +43 -42
- 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/dependencies/zeitwerk_integration.rb +0 -117
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -15,7 +15,7 @@ The latest version of Active Support can be installed with RubyGems:
|
|
15
15
|
|
16
16
|
Source code can be downloaded as part of the Rails project on GitHub:
|
17
17
|
|
18
|
-
* https://github.com/rails/rails/tree/
|
18
|
+
* https://github.com/rails/rails/tree/main/activesupport
|
19
19
|
|
20
20
|
|
21
21
|
== License
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveSupport
|
4
|
-
# Actionable errors
|
4
|
+
# Actionable errors lets you define actions to resolve an error.
|
5
5
|
#
|
6
6
|
# To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
|
7
7
|
# module and invoke the +action+ class macro to define the action. An action
|
@@ -34,11 +34,11 @@ module ActiveSupport
|
|
34
34
|
|
35
35
|
private
|
36
36
|
def respond_to_missing?(name, include_private = false)
|
37
|
-
(
|
37
|
+
name.end_with?("?") || super
|
38
38
|
end
|
39
39
|
|
40
40
|
def method_missing(name, *args)
|
41
|
-
if name
|
41
|
+
if name.end_with?("?")
|
42
42
|
any?(name[0..-2])
|
43
43
|
else
|
44
44
|
super
|
@@ -16,7 +16,7 @@ module ActiveSupport
|
|
16
16
|
#
|
17
17
|
# bc = ActiveSupport::BacktraceCleaner.new
|
18
18
|
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
|
19
|
-
# bc.add_silencer { |line|
|
19
|
+
# bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
|
20
20
|
# bc.clean(exception.backtrace) # perform the cleanup
|
21
21
|
#
|
22
22
|
# To reconfigure an existing BacktraceCleaner (like the default one in Rails)
|
@@ -65,7 +65,7 @@ module ActiveSupport
|
|
65
65
|
# for a given line, it will be excluded from the clean backtrace.
|
66
66
|
#
|
67
67
|
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
|
68
|
-
# backtrace_cleaner.add_silencer { |line|
|
68
|
+
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
|
69
69
|
def add_silencer(&block)
|
70
70
|
@silencers << block
|
71
71
|
end
|
@@ -91,7 +91,7 @@ module ActiveSupport
|
|
91
91
|
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
|
92
92
|
return if gems_paths.empty?
|
93
93
|
|
94
|
-
gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
|
94
|
+
gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
|
95
95
|
gems_result = '\3 (\4) \5'
|
96
96
|
add_filter { |line| line.sub(gems_regexp, gems_result) }
|
97
97
|
end
|
@@ -34,14 +34,14 @@ module ActiveSupport
|
|
34
34
|
# <% benchmark 'Process data files', level: :info, silence: true do %>
|
35
35
|
# <%= expensive_and_chatty_files_operation %>
|
36
36
|
# <% end %>
|
37
|
-
def benchmark(message = "Benchmarking", options = {})
|
37
|
+
def benchmark(message = "Benchmarking", options = {}, &block)
|
38
38
|
if logger
|
39
39
|
options.assert_valid_keys(:level, :silence)
|
40
40
|
options[:level] ||= :info
|
41
41
|
|
42
42
|
result = nil
|
43
|
-
ms = Benchmark.ms { result = options[:silence] ? logger.silence
|
44
|
-
logger.
|
43
|
+
ms = Benchmark.ms { result = options[:silence] ? logger.silence(&block) : yield }
|
44
|
+
logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
|
45
45
|
result
|
46
46
|
else
|
47
47
|
yield
|
@@ -1,6 +1,5 @@
|
|
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"
|
@@ -12,15 +11,14 @@ module ActiveSupport
|
|
12
11
|
# FileStore implements the Strategy::LocalCache strategy which implements
|
13
12
|
# an in-memory cache inside of a block.
|
14
13
|
class FileStore < Store
|
15
|
-
prepend Strategy::LocalCache
|
16
14
|
attr_reader :cache_path
|
17
15
|
|
18
16
|
DIR_FORMATTER = "%03X"
|
19
|
-
FILENAME_MAX_SIZE =
|
17
|
+
FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write)
|
20
18
|
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
21
19
|
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
|
22
20
|
|
23
|
-
def initialize(cache_path, options
|
21
|
+
def initialize(cache_path, **options)
|
24
22
|
super(options)
|
25
23
|
@cache_path = cache_path.to_s
|
26
24
|
end
|
@@ -73,18 +71,27 @@ module ActiveSupport
|
|
73
71
|
|
74
72
|
private
|
75
73
|
def read_entry(key, **options)
|
76
|
-
if
|
77
|
-
|
74
|
+
if payload = read_serialized_entry(key, **options)
|
75
|
+
entry = deserialize_entry(payload)
|
76
|
+
entry if entry.is_a?(Cache::Entry)
|
78
77
|
end
|
79
|
-
|
80
|
-
|
78
|
+
end
|
79
|
+
|
80
|
+
def read_serialized_entry(key, **)
|
81
|
+
File.binread(key) if File.exist?(key)
|
82
|
+
rescue => error
|
83
|
+
logger.error("FileStoreError (#{error}): #{error.message}") if logger
|
81
84
|
nil
|
82
85
|
end
|
83
86
|
|
84
87
|
def write_entry(key, entry, **options)
|
88
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_serialized_entry(key, payload, **options)
|
85
92
|
return false if options[:unless_exist] && File.exist?(key)
|
86
93
|
ensure_cache_path(File.dirname(key))
|
87
|
-
File.atomic_write(key, cache_path) { |f|
|
94
|
+
File.atomic_write(key, cache_path) { |f| f.write(payload) }
|
88
95
|
true
|
89
96
|
end
|
90
97
|
|
@@ -94,9 +101,9 @@ module ActiveSupport
|
|
94
101
|
File.delete(key)
|
95
102
|
delete_empty_directories(File.dirname(key))
|
96
103
|
true
|
97
|
-
rescue
|
104
|
+
rescue
|
98
105
|
# Just in case the error was caused by another process deleting the file first.
|
99
|
-
raise
|
106
|
+
raise if File.exist?(key)
|
100
107
|
false
|
101
108
|
end
|
102
109
|
end
|
@@ -7,7 +7,8 @@ rescue LoadError => e
|
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
-
require "
|
10
|
+
require "delegate"
|
11
|
+
require "active_support/core_ext/enumerable"
|
11
12
|
require "active_support/core_ext/array/extract_options"
|
12
13
|
|
13
14
|
module ActiveSupport
|
@@ -25,49 +26,68 @@ module ActiveSupport
|
|
25
26
|
# MemCacheStore implements the Strategy::LocalCache strategy which implements
|
26
27
|
# an in-memory cache inside of a block.
|
27
28
|
class MemCacheStore < Store
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
# Advertise cache versioning support.
|
30
|
+
def self.supports_cache_versioning?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
prepend Strategy::LocalCache
|
35
|
+
|
36
|
+
module DupLocalCache
|
37
|
+
class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
|
38
|
+
def write_entry(_key, entry)
|
39
|
+
if entry.is_a?(Entry)
|
40
|
+
entry.dup_value!
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_entry(key)
|
46
|
+
entry = super do
|
47
|
+
new_entry = yield
|
48
|
+
if entry.is_a?(Entry)
|
49
|
+
new_entry.dup_value!
|
50
|
+
end
|
51
|
+
new_entry
|
52
|
+
end
|
53
|
+
entry = entry.dup
|
54
|
+
|
55
|
+
if entry.is_a?(Entry)
|
56
|
+
entry.dup_value!
|
35
57
|
end
|
58
|
+
|
36
59
|
entry
|
37
60
|
end
|
61
|
+
end
|
38
62
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
63
|
+
private
|
64
|
+
def local_cache
|
65
|
+
if ActiveSupport::Cache.format_version == 6.1
|
66
|
+
if local_cache = super
|
67
|
+
DupLocalStore.new(local_cache)
|
68
|
+
end
|
44
69
|
else
|
45
70
|
super
|
46
71
|
end
|
47
72
|
end
|
48
73
|
end
|
49
|
-
|
50
|
-
# Advertise cache versioning support.
|
51
|
-
def self.supports_cache_versioning?
|
52
|
-
true
|
53
|
-
end
|
54
|
-
|
55
|
-
prepend Strategy::LocalCache
|
56
|
-
prepend LocalCacheWithRaw
|
74
|
+
prepend DupLocalCache
|
57
75
|
|
58
76
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
59
77
|
|
60
78
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
61
|
-
#
|
79
|
+
# If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
|
80
|
+
# - ENV["MEMCACHE_SERVERS"] (if defined)
|
81
|
+
# - "127.0.0.1:11211" (otherwise)
|
62
82
|
#
|
63
83
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
|
64
|
-
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["
|
84
|
+
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
|
65
85
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
|
66
86
|
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
|
67
87
|
def self.build_mem_cache(*addresses) # :nodoc:
|
68
88
|
addresses = addresses.flatten
|
69
89
|
options = addresses.extract_options!
|
70
|
-
addresses =
|
90
|
+
addresses = nil if addresses.compact.empty?
|
71
91
|
pool_options = retrieve_pool_options(options)
|
72
92
|
|
73
93
|
if pool_options.empty?
|
@@ -84,11 +104,14 @@ module ActiveSupport
|
|
84
104
|
#
|
85
105
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
86
106
|
#
|
87
|
-
# If no addresses are
|
88
|
-
#
|
107
|
+
# If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise,
|
108
|
+
# MemCacheStore will connect to localhost:11211 (the default memcached port).
|
89
109
|
def initialize(*addresses)
|
90
110
|
addresses = addresses.flatten
|
91
111
|
options = addresses.extract_options!
|
112
|
+
if options.key?(:cache_nils)
|
113
|
+
options[:skip_nil] = !options.delete(:cache_nils)
|
114
|
+
end
|
92
115
|
super(options)
|
93
116
|
|
94
117
|
unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
|
@@ -98,7 +121,9 @@ module ActiveSupport
|
|
98
121
|
@data = addresses.first
|
99
122
|
else
|
100
123
|
mem_cache_options = options.dup
|
101
|
-
|
124
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
125
|
+
mem_cache_options[:compress] = false
|
126
|
+
(UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) }
|
102
127
|
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
|
103
128
|
end
|
104
129
|
end
|
@@ -141,34 +166,93 @@ module ActiveSupport
|
|
141
166
|
end
|
142
167
|
|
143
168
|
private
|
169
|
+
module Coders # :nodoc:
|
170
|
+
class << self
|
171
|
+
def [](version)
|
172
|
+
case version
|
173
|
+
when 6.1
|
174
|
+
Rails61Coder
|
175
|
+
when 7.0
|
176
|
+
Rails70Coder
|
177
|
+
else
|
178
|
+
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module Loader
|
184
|
+
def load(payload)
|
185
|
+
if payload.is_a?(Entry)
|
186
|
+
payload
|
187
|
+
else
|
188
|
+
Cache::Coders::Loader.load(payload)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
module Rails61Coder
|
194
|
+
include Loader
|
195
|
+
extend self
|
196
|
+
|
197
|
+
def dump(entry)
|
198
|
+
entry
|
199
|
+
end
|
200
|
+
|
201
|
+
def dump_compressed(entry, threshold)
|
202
|
+
entry.compressed(threshold)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
module Rails70Coder
|
207
|
+
include Cache::Coders::Rails70Coder
|
208
|
+
include Loader
|
209
|
+
extend self
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def default_coder
|
214
|
+
Coders[Cache.format_version]
|
215
|
+
end
|
216
|
+
|
144
217
|
# Read an entry from the cache.
|
145
218
|
def read_entry(key, **options)
|
146
|
-
|
219
|
+
deserialize_entry(read_serialized_entry(key, **options), **options)
|
220
|
+
end
|
221
|
+
|
222
|
+
def read_serialized_entry(key, **options)
|
223
|
+
rescue_error_with(nil) do
|
224
|
+
@data.with { |c| c.get(key, options) }
|
225
|
+
end
|
147
226
|
end
|
148
227
|
|
149
228
|
# Write an entry to the cache.
|
150
229
|
def write_entry(key, entry, **options)
|
151
|
-
|
152
|
-
|
230
|
+
write_serialized_entry(key, serialize_entry(entry, **options), **options)
|
231
|
+
end
|
232
|
+
|
233
|
+
def write_serialized_entry(key, payload, **options)
|
234
|
+
method = options[:unless_exist] ? :add : :set
|
153
235
|
expires_in = options[:expires_in].to_i
|
154
|
-
if expires_in > 0 && !options[:raw]
|
236
|
+
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
|
155
237
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
156
238
|
expires_in += 5.minutes
|
157
239
|
end
|
158
240
|
rescue_error_with false do
|
159
|
-
|
241
|
+
# Don't pass compress option to Dalli since we are already dealing with compression.
|
242
|
+
options.delete(:compress)
|
243
|
+
@data.with { |c| c.send(method, key, payload, expires_in, **options) }
|
160
244
|
end
|
161
245
|
end
|
162
246
|
|
163
247
|
# Reads multiple entries from the cache implementation.
|
164
248
|
def read_multi_entries(names, **options)
|
165
|
-
keys_to_names =
|
249
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
166
250
|
|
167
251
|
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
168
252
|
values = {}
|
169
253
|
|
170
254
|
raw_values.each do |key, value|
|
171
|
-
entry = deserialize_entry(value)
|
255
|
+
entry = deserialize_entry(value, raw: options[:raw])
|
172
256
|
|
173
257
|
unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
|
174
258
|
values[keys_to_names[key]] = entry.value
|
@@ -183,28 +267,40 @@ module ActiveSupport
|
|
183
267
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
184
268
|
end
|
185
269
|
|
270
|
+
def serialize_entry(entry, raw: false, **options)
|
271
|
+
if raw
|
272
|
+
entry.value.to_s
|
273
|
+
else
|
274
|
+
super(entry, raw: raw, **options)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
186
278
|
# Memcache keys are binaries. So we need to force their encoding to binary
|
187
279
|
# before applying the regular expression to ensure we are escaping all
|
188
280
|
# characters properly.
|
189
281
|
def normalize_key(key, options)
|
190
|
-
key = super
|
191
|
-
|
192
|
-
|
193
|
-
|
282
|
+
key = super
|
283
|
+
if key
|
284
|
+
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
285
|
+
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
286
|
+
key = "#{key[0, 212]}:hash:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
|
287
|
+
end
|
194
288
|
key
|
195
289
|
end
|
196
290
|
|
197
|
-
def deserialize_entry(
|
198
|
-
if
|
199
|
-
|
200
|
-
|
291
|
+
def deserialize_entry(payload, raw: false, **)
|
292
|
+
if payload && raw
|
293
|
+
Entry.new(payload)
|
294
|
+
else
|
295
|
+
super(payload)
|
201
296
|
end
|
202
297
|
end
|
203
298
|
|
204
299
|
def rescue_error_with(fallback)
|
205
300
|
yield
|
206
|
-
rescue Dalli::DalliError =>
|
207
|
-
|
301
|
+
rescue Dalli::DalliError => error
|
302
|
+
ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
|
303
|
+
logger.error("DalliError (#{error}): #{error.message}") if logger
|
208
304
|
fallback
|
209
305
|
end
|
210
306
|
end
|
@@ -16,13 +16,41 @@ module ActiveSupport
|
|
16
16
|
# a cleanup will occur which tries to prune the cache down to three quarters
|
17
17
|
# of the maximum size by removing the least recently used entries.
|
18
18
|
#
|
19
|
+
# Unlike other Cache store implementations, MemoryStore does not compress
|
20
|
+
# values by default. MemoryStore does not benefit from compression as much
|
21
|
+
# as other Store implementations, as it does not send data over a network.
|
22
|
+
# However, when compression is enabled, it still pays the full cost of
|
23
|
+
# compression in terms of cpu use.
|
24
|
+
#
|
19
25
|
# MemoryStore is thread-safe.
|
20
26
|
class MemoryStore < Store
|
27
|
+
module DupCoder # :nodoc:
|
28
|
+
extend self
|
29
|
+
|
30
|
+
def dump(entry)
|
31
|
+
entry.dup_value! unless entry.compressed?
|
32
|
+
entry
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump_compressed(entry, threshold)
|
36
|
+
entry = entry.compressed(threshold)
|
37
|
+
entry.dup_value! unless entry.compressed?
|
38
|
+
entry
|
39
|
+
end
|
40
|
+
|
41
|
+
def load(entry)
|
42
|
+
entry = entry.dup
|
43
|
+
entry.dup_value!
|
44
|
+
entry
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
21
48
|
def initialize(options = nil)
|
22
49
|
options ||= {}
|
50
|
+
# Disable compression by default.
|
51
|
+
options[:compress] ||= false
|
23
52
|
super(options)
|
24
53
|
@data = {}
|
25
|
-
@key_access = {}
|
26
54
|
@max_size = options[:size] || 32.megabytes
|
27
55
|
@max_prune_time = options[:max_prune_time] || 2
|
28
56
|
@cache_size = 0
|
@@ -39,7 +67,6 @@ module ActiveSupport
|
|
39
67
|
def clear(options = nil)
|
40
68
|
synchronize do
|
41
69
|
@data.clear
|
42
|
-
@key_access.clear
|
43
70
|
@cache_size = 0
|
44
71
|
end
|
45
72
|
end
|
@@ -62,13 +89,13 @@ module ActiveSupport
|
|
62
89
|
return if pruning?
|
63
90
|
@pruning = true
|
64
91
|
begin
|
65
|
-
start_time =
|
92
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
66
93
|
cleanup
|
67
94
|
instrument(:prune, target_size, from: @cache_size) do
|
68
|
-
keys = synchronize { @
|
95
|
+
keys = synchronize { @data.keys }
|
69
96
|
keys.each do |key|
|
70
97
|
delete_entry(key, **options)
|
71
|
-
return if @cache_size <= target_size || (max_time &&
|
98
|
+
return if @cache_size <= target_size || (max_time && Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time > max_time)
|
72
99
|
end
|
73
100
|
end
|
74
101
|
ensure
|
@@ -104,7 +131,7 @@ module ActiveSupport
|
|
104
131
|
end
|
105
132
|
|
106
133
|
def inspect # :nodoc:
|
107
|
-
"
|
134
|
+
"#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
|
108
135
|
end
|
109
136
|
|
110
137
|
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
|
@@ -116,36 +143,38 @@ module ActiveSupport
|
|
116
143
|
private
|
117
144
|
PER_ENTRY_OVERHEAD = 240
|
118
145
|
|
119
|
-
def
|
120
|
-
|
146
|
+
def default_coder
|
147
|
+
DupCoder
|
148
|
+
end
|
149
|
+
|
150
|
+
def cached_size(key, payload)
|
151
|
+
key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
|
121
152
|
end
|
122
153
|
|
123
154
|
def read_entry(key, **options)
|
124
|
-
entry =
|
155
|
+
entry = nil
|
125
156
|
synchronize do
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
else
|
131
|
-
@key_access.delete(key)
|
157
|
+
payload = @data.delete(key)
|
158
|
+
if payload
|
159
|
+
@data[key] = payload
|
160
|
+
entry = deserialize_entry(payload)
|
132
161
|
end
|
133
162
|
end
|
134
163
|
entry
|
135
164
|
end
|
136
165
|
|
137
166
|
def write_entry(key, entry, **options)
|
138
|
-
entry
|
167
|
+
payload = serialize_entry(entry, **options)
|
139
168
|
synchronize do
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
169
|
+
return false if options[:unless_exist] && @data.key?(key)
|
170
|
+
|
171
|
+
old_payload = @data[key]
|
172
|
+
if old_payload
|
173
|
+
@cache_size -= (old_payload.bytesize - payload.bytesize)
|
144
174
|
else
|
145
|
-
@cache_size += cached_size(key,
|
175
|
+
@cache_size += cached_size(key, payload)
|
146
176
|
end
|
147
|
-
@
|
148
|
-
@data[key] = entry
|
177
|
+
@data[key] = payload
|
149
178
|
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
|
150
179
|
true
|
151
180
|
end
|
@@ -153,16 +182,15 @@ module ActiveSupport
|
|
153
182
|
|
154
183
|
def delete_entry(key, **options)
|
155
184
|
synchronize do
|
156
|
-
@
|
157
|
-
|
158
|
-
|
159
|
-
!!entry
|
185
|
+
payload = @data.delete(key)
|
186
|
+
@cache_size -= cached_size(key, payload) if payload
|
187
|
+
!!payload
|
160
188
|
end
|
161
189
|
end
|
162
190
|
|
163
191
|
def modify_value(name, amount, options)
|
192
|
+
options = merged_options(options)
|
164
193
|
synchronize do
|
165
|
-
options = merged_options(options)
|
166
194
|
if num = read(name, options)
|
167
195
|
num = num.to_i + amount
|
168
196
|
write(name, num, options)
|
@@ -33,10 +33,18 @@ module ActiveSupport
|
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
|
-
def read_entry(key, **
|
36
|
+
def read_entry(key, **s)
|
37
|
+
deserialize_entry(read_serialized_entry(key))
|
37
38
|
end
|
38
39
|
|
39
|
-
def
|
40
|
+
def read_serialized_entry(_key, **)
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_entry(key, entry, **)
|
44
|
+
write_serialized_entry(key, serialize_entry(entry))
|
45
|
+
end
|
46
|
+
|
47
|
+
def write_serialized_entry(_key, _payload, **)
|
40
48
|
true
|
41
49
|
end
|
42
50
|
|