activesupport 5.1.7 → 7.0.4.1
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 +259 -585
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -5
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/all.rb +2 -0
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +33 -5
- data/lib/active_support/benchmarkable.rb +5 -3
- data/lib/active_support/builder.rb +2 -0
- data/lib/active_support/cache/file_store.rb +50 -43
- data/lib/active_support/cache/mem_cache_store.rb +194 -67
- data/lib/active_support/cache/memory_store.rb +70 -34
- data/lib/active_support/cache/null_store.rb +18 -3
- data/lib/active_support/cache/redis_cache_store.rb +474 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -50
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
- data/lib/active_support/cache.rb +556 -220
- data/lib/active_support/callbacks.rb +264 -159
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +81 -8
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
- data/lib/active_support/concurrency/share_lock.rb +4 -3
- data/lib/active_support/configurable.rb +17 -16
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +18 -8
- data/lib/active_support/core_ext/array/conversions.rb +20 -17
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/extract_options.rb +2 -0
- data/lib/active_support/core_ext/array/grouping.rb +8 -6
- data/lib/active_support/core_ext/array/inquiry.rb +4 -2
- data/lib/active_support/core_ext/array/wrap.rb +2 -0
- data/lib/active_support/core_ext/array.rb +4 -1
- data/lib/active_support/core_ext/benchmark.rb +4 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +3 -1
- data/lib/active_support/core_ext/big_decimal.rb +2 -0
- data/lib/active_support/core_ext/class/attribute.rb +50 -47
- data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
- data/lib/active_support/core_ext/class/subclasses.rb +10 -24
- data/lib/active_support/core_ext/class.rb +2 -0
- data/lib/active_support/core_ext/date/acts_like.rb +2 -0
- data/lib/active_support/core_ext/date/blank.rb +3 -1
- data/lib/active_support/core_ext/date/calculations.rb +17 -14
- data/lib/active_support/core_ext/date/conversions.rb +24 -22
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/date/zones.rb +2 -0
- data/lib/active_support/core_ext/date.rb +3 -0
- data/lib/active_support/core_ext/date_and_time/calculations.rb +65 -41
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -1
- data/lib/active_support/core_ext/date_and_time/zones.rb +2 -1
- data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
- data/lib/active_support/core_ext/date_time/blank.rb +3 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +3 -1
- data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +15 -14
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/date_time.rb +3 -0
- data/lib/active_support/core_ext/digest/uuid.rb +42 -14
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +244 -72
- data/lib/active_support/core_ext/file/atomic.rb +6 -2
- data/lib/active_support/core_ext/file.rb +2 -0
- data/lib/active_support/core_ext/hash/conversions.rb +7 -6
- data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +4 -2
- data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
- data/lib/active_support/core_ext/hash/keys.rb +4 -31
- data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
- data/lib/active_support/core_ext/hash/slice.rb +8 -29
- data/lib/active_support/core_ext/hash.rb +3 -2
- data/lib/active_support/core_ext/integer/inflections.rb +2 -0
- data/lib/active_support/core_ext/integer/multiple.rb +3 -1
- data/lib/active_support/core_ext/integer/time.rb +7 -14
- data/lib/active_support/core_ext/integer.rb +2 -0
- data/lib/active_support/core_ext/kernel/concern.rb +2 -0
- data/lib/active_support/core_ext/kernel/reporting.rb +6 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +3 -1
- data/lib/active_support/core_ext/kernel.rb +2 -1
- data/lib/active_support/core_ext/load_error.rb +3 -8
- data/lib/active_support/core_ext/module/aliasing.rb +2 -0
- data/lib/active_support/core_ext/module/anonymous.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +4 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +46 -56
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +36 -27
- data/lib/active_support/core_ext/module/concerning.rb +15 -10
- data/lib/active_support/core_ext/module/delegation.rb +97 -58
- data/lib/active_support/core_ext/module/deprecation.rb +2 -0
- data/lib/active_support/core_ext/module/introspection.rb +18 -15
- data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
- data/lib/active_support/core_ext/module/remove_method.rb +5 -23
- data/lib/active_support/core_ext/module.rb +3 -1
- data/lib/active_support/core_ext/name_error.rb +30 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +134 -129
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
- data/lib/active_support/core_ext/numeric/time.rb +7 -15
- data/lib/active_support/core_ext/numeric.rb +3 -1
- data/lib/active_support/core_ext/object/acts_like.rb +41 -6
- data/lib/active_support/core_ext/object/blank.rb +15 -5
- data/lib/active_support/core_ext/object/conversions.rb +2 -0
- data/lib/active_support/core_ext/object/deep_dup.rb +3 -1
- data/lib/active_support/core_ext/object/duplicable.rb +16 -110
- data/lib/active_support/core_ext/object/inclusion.rb +2 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
- data/lib/active_support/core_ext/object/json.rb +51 -26
- data/lib/active_support/core_ext/object/to_param.rb +2 -0
- data/lib/active_support/core_ext/object/to_query.rb +4 -2
- data/lib/active_support/core_ext/object/try.rb +26 -14
- data/lib/active_support/core_ext/object/with_options.rb +24 -3
- data/lib/active_support/core_ext/object.rb +2 -0
- 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 +57 -0
- data/lib/active_support/core_ext/range/conversions.rb +35 -25
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
- data/lib/active_support/core_ext/range/each.rb +6 -3
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +7 -0
- data/lib/active_support/core_ext/range/overlaps.rb +3 -1
- data/lib/active_support/core_ext/range.rb +4 -1
- data/lib/active_support/core_ext/regexp.rb +10 -5
- data/lib/active_support/core_ext/securerandom.rb +25 -3
- data/lib/active_support/core_ext/string/access.rb +7 -16
- data/lib/active_support/core_ext/string/behavior.rb +2 -0
- data/lib/active_support/core_ext/string/conversions.rb +5 -2
- data/lib/active_support/core_ext/string/exclude.rb +2 -0
- data/lib/active_support/core_ext/string/filters.rb +44 -1
- data/lib/active_support/core_ext/string/indent.rb +2 -0
- data/lib/active_support/core_ext/string/inflections.rb +69 -16
- data/lib/active_support/core_ext/string/inquiry.rb +4 -1
- data/lib/active_support/core_ext/string/multibyte.rb +9 -4
- data/lib/active_support/core_ext/string/output_safety.rb +135 -27
- data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
- data/lib/active_support/core_ext/string/strip.rb +5 -1
- data/lib/active_support/core_ext/string/zones.rb +2 -0
- data/lib/active_support/core_ext/string.rb +2 -0
- 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/acts_like.rb +2 -0
- data/lib/active_support/core_ext/time/calculations.rb +81 -24
- data/lib/active_support/core_ext/time/compatibility.rb +4 -2
- data/lib/active_support/core_ext/time/conversions.rb +17 -12
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
- data/lib/active_support/core_ext/time/zones.rb +12 -25
- data/lib/active_support/core_ext/time.rb +3 -0
- data/lib/active_support/core_ext/uri.rb +4 -23
- data/lib/active_support/core_ext.rb +4 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +226 -0
- data/lib/active_support/dependencies/autoload.rb +2 -0
- data/lib/active_support/dependencies/interlock.rb +12 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +59 -715
- data/lib/active_support/deprecation/behaviors.rb +48 -13
- data/lib/active_support/deprecation/constant_accessor.rb +4 -2
- data/lib/active_support/deprecation/disallowed.rb +56 -0
- data/lib/active_support/deprecation/instance_delegator.rb +2 -1
- data/lib/active_support/deprecation/method_wrappers.rb +29 -21
- data/lib/active_support/deprecation/proxy_wrappers.rb +34 -8
- data/lib/active_support/deprecation/reporting.rb +54 -9
- data/lib/active_support/deprecation.rb +10 -3
- data/lib/active_support/descendants_tracker.rb +192 -34
- data/lib/active_support/digest.rb +22 -0
- data/lib/active_support/duration/iso8601_parser.rb +9 -9
- data/lib/active_support/duration/iso8601_serializer.rb +29 -15
- data/lib/active_support/duration.rb +158 -72
- data/lib/active_support/encrypted_configuration.rb +56 -0
- data/lib/active_support/encrypted_file.rb +129 -0
- 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 +87 -122
- 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 +46 -21
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/executor.rb +2 -0
- data/lib/active_support/file_update_checker.rb +2 -1
- data/lib/active_support/fork_tracker.rb +71 -0
- data/lib/active_support/gem_version.rb +7 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +126 -42
- data/lib/active_support/html_safe_translation.rb +43 -0
- data/lib/active_support/i18n.rb +5 -1
- data/lib/active_support/i18n_railtie.rb +19 -14
- data/lib/active_support/inflections.rb +2 -0
- data/lib/active_support/inflector/inflections.rb +41 -14
- data/lib/active_support/inflector/methods.rb +73 -87
- data/lib/active_support/inflector/transliterate.rb +56 -18
- data/lib/active_support/inflector.rb +2 -0
- data/lib/active_support/isolated_execution_state.rb +72 -0
- data/lib/active_support/json/decoding.rb +27 -26
- data/lib/active_support/json/encoding.rb +16 -6
- data/lib/active_support/json.rb +2 -0
- data/lib/active_support/key_generator.rb +25 -38
- data/lib/active_support/lazy_load_hooks.rb +35 -6
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +8 -4
- data/lib/active_support/log_subscriber/test_helper.rb +4 -2
- data/lib/active_support/log_subscriber.rb +54 -13
- data/lib/active_support/logger.rb +4 -17
- data/lib/active_support/logger_silence.rb +13 -20
- data/lib/active_support/logger_thread_safe_level.rb +48 -10
- data/lib/active_support/message_encryptor.rb +111 -37
- data/lib/active_support/message_verifier.rb +124 -21
- data/lib/active_support/messages/metadata.rb +80 -0
- data/lib/active_support/messages/rotation_configuration.rb +23 -0
- data/lib/active_support/messages/rotator.rb +57 -0
- data/lib/active_support/multibyte/chars.rb +19 -76
- data/lib/active_support/multibyte/unicode.rb +9 -331
- data/lib/active_support/multibyte.rb +3 -1
- data/lib/active_support/notifications/fanout.rb +165 -37
- data/lib/active_support/notifications/instrumenter.rb +92 -11
- data/lib/active_support/notifications.rb +96 -30
- data/lib/active_support/number_helper/number_converter.rb +8 -9
- data/lib/active_support/number_helper/number_to_currency_converter.rb +14 -12
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +6 -3
- data/lib/active_support/number_helper/number_to_human_converter.rb +6 -3
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +7 -4
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +6 -3
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +14 -27
- data/lib/active_support/number_helper/rounding_helper.rb +16 -34
- data/lib/active_support/number_helper.rb +38 -12
- data/lib/active_support/option_merger.rb +19 -6
- data/lib/active_support/ordered_hash.rb +4 -2
- data/lib/active_support/ordered_options.rb +18 -6
- data/lib/active_support/parameter_filter.rb +138 -0
- data/lib/active_support/per_thread_registry.rb +8 -1
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +3 -10
- data/lib/active_support/railtie.rb +112 -11
- data/lib/active_support/reloader.rb +12 -11
- data/lib/active_support/rescuable.rb +19 -18
- 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 +26 -15
- data/lib/active_support/string_inquirer.rb +4 -3
- data/lib/active_support/subscriber.rb +81 -42
- data/lib/active_support/tagged_logging.rb +45 -9
- data/lib/active_support/test_case.rb +86 -2
- data/lib/active_support/testing/assertions.rb +89 -21
- data/lib/active_support/testing/autorun.rb +2 -0
- data/lib/active_support/testing/constant_lookup.rb +2 -0
- data/lib/active_support/testing/declarative.rb +2 -0
- data/lib/active_support/testing/deprecation.rb +54 -2
- data/lib/active_support/testing/file_fixtures.rb +4 -0
- data/lib/active_support/testing/isolation.rb +6 -4
- data/lib/active_support/testing/method_call_assertions.rb +34 -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 +55 -0
- data/lib/active_support/testing/parallelize_executor.rb +76 -0
- data/lib/active_support/testing/setup_and_teardown.rb +12 -7
- data/lib/active_support/testing/stream.rb +6 -7
- data/lib/active_support/testing/tagged_logging.rb +3 -1
- data/lib/active_support/testing/time_helpers.rb +91 -15
- data/lib/active_support/time.rb +2 -0
- data/lib/active_support/time_with_zone.rb +168 -56
- data/lib/active_support/values/time_zone.rb +85 -37
- data/lib/active_support/version.rb +3 -1
- data/lib/active_support/xml_mini/jdom.rb +6 -5
- data/lib/active_support/xml_mini/libxml.rb +9 -7
- data/lib/active_support/xml_mini/libxmlsax.rb +7 -5
- data/lib/active_support/xml_mini/nokogiri.rb +8 -6
- data/lib/active_support/xml_mini/nokogirisax.rb +6 -4
- data/lib/active_support/xml_mini/rexml.rb +13 -4
- data/lib/active_support/xml_mini.rb +10 -15
- data/lib/active_support.rb +30 -9
- metadata +76 -35
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
- data/lib/active_support/core_ext/hash/compact.rb +0 -27
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -30
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
- data/lib/active_support/core_ext/marshal.rb +0 -22
- data/lib/active_support/core_ext/module/reachable.rb +0 -8
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
- data/lib/active_support/core_ext/range/include_range.rb +0 -23
- data/lib/active_support/values/unicode_tables.dat +0 -0
data/lib/active_support/cache.rb
CHANGED
@@ -1,29 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "zlib"
|
2
4
|
require "active_support/core_ext/array/extract_options"
|
3
5
|
require "active_support/core_ext/array/wrap"
|
6
|
+
require "active_support/core_ext/enumerable"
|
4
7
|
require "active_support/core_ext/module/attribute_accessors"
|
5
8
|
require "active_support/core_ext/numeric/bytes"
|
6
9
|
require "active_support/core_ext/numeric/time"
|
7
10
|
require "active_support/core_ext/object/to_param"
|
11
|
+
require "active_support/core_ext/object/try"
|
8
12
|
require "active_support/core_ext/string/inflections"
|
9
13
|
|
10
14
|
module ActiveSupport
|
11
15
|
# See ActiveSupport::Cache::Store for documentation.
|
12
16
|
module Cache
|
13
|
-
autoload :FileStore,
|
14
|
-
autoload :MemoryStore,
|
15
|
-
autoload :MemCacheStore,
|
16
|
-
autoload :NullStore,
|
17
|
+
autoload :FileStore, "active_support/cache/file_store"
|
18
|
+
autoload :MemoryStore, "active_support/cache/memory_store"
|
19
|
+
autoload :MemCacheStore, "active_support/cache/mem_cache_store"
|
20
|
+
autoload :NullStore, "active_support/cache/null_store"
|
21
|
+
autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
|
17
22
|
|
18
23
|
# These options mean something to all cache implementations. Individual cache
|
19
24
|
# implementations may support additional options.
|
20
|
-
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
|
25
|
+
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :expire_in, :expired_in, :race_condition_ttl, :coder, :skip_nil]
|
26
|
+
|
27
|
+
DEFAULT_COMPRESS_LIMIT = 1.kilobyte
|
28
|
+
|
29
|
+
# Mapping of canonical option names to aliases that a store will recognize.
|
30
|
+
OPTION_ALIASES = {
|
31
|
+
expires_in: [:expire_in, :expired_in]
|
32
|
+
}.freeze
|
21
33
|
|
22
34
|
module Strategy
|
23
35
|
autoload :LocalCache, "active_support/cache/strategy/local_cache"
|
24
36
|
end
|
25
37
|
|
38
|
+
@format_version = 6.1
|
39
|
+
|
26
40
|
class << self
|
41
|
+
attr_accessor :format_version
|
42
|
+
|
27
43
|
# Creates a new Store object according to the given options.
|
28
44
|
#
|
29
45
|
# If no arguments are passed to this method, then a new
|
@@ -49,12 +65,19 @@ module ActiveSupport
|
|
49
65
|
#
|
50
66
|
# ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
|
51
67
|
# # => returns MyOwnCacheStore.new
|
52
|
-
def lookup_store(*
|
53
|
-
store, *parameters = *Array.wrap(store_option).flatten
|
54
|
-
|
68
|
+
def lookup_store(store = nil, *parameters)
|
55
69
|
case store
|
56
70
|
when Symbol
|
57
|
-
|
71
|
+
options = parameters.extract_options!
|
72
|
+
# clean this up once Ruby 2.7 support is dropped
|
73
|
+
# see https://github.com/rails/rails/pull/41522#discussion_r581186602
|
74
|
+
if options.empty?
|
75
|
+
retrieve_store_class(store).new(*parameters)
|
76
|
+
else
|
77
|
+
retrieve_store_class(store).new(*parameters, **options)
|
78
|
+
end
|
79
|
+
when Array
|
80
|
+
lookup_store(*store)
|
58
81
|
when nil
|
59
82
|
ActiveSupport::Cache::MemoryStore.new
|
60
83
|
else
|
@@ -75,7 +98,7 @@ module ActiveSupport
|
|
75
98
|
#
|
76
99
|
# The +key+ argument can also respond to +cache_key+ or +to_param+.
|
77
100
|
def expand_cache_key(key, namespace = nil)
|
78
|
-
expanded_cache_key = namespace ? "#{namespace}/" : ""
|
101
|
+
expanded_cache_key = namespace ? +"#{namespace}/" : +""
|
79
102
|
|
80
103
|
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
|
81
104
|
expanded_cache_key << "#{prefix}/"
|
@@ -88,16 +111,19 @@ module ActiveSupport
|
|
88
111
|
private
|
89
112
|
def retrieve_cache_key(key)
|
90
113
|
case
|
91
|
-
when key.respond_to?(:
|
92
|
-
when key.
|
93
|
-
when key.
|
94
|
-
|
114
|
+
when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
|
115
|
+
when key.respond_to?(:cache_key) then key.cache_key
|
116
|
+
when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
|
117
|
+
when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
|
118
|
+
else key.to_param
|
95
119
|
end.to_s
|
96
120
|
end
|
97
121
|
|
98
122
|
# Obtains the specified cache store class, given the name of the +store+.
|
99
123
|
# Raises an error when the store class cannot be found.
|
100
124
|
def retrieve_store_class(store)
|
125
|
+
# require_relative cannot be used here because the class might be
|
126
|
+
# provided by another gem, like redis-activesupport for example.
|
101
127
|
require "active_support/cache/#{store}"
|
102
128
|
rescue LoadError => e
|
103
129
|
raise "Could not find cache store adapter for #{store} (#{e})"
|
@@ -113,9 +139,10 @@ module ActiveSupport
|
|
113
139
|
# popular cache store for large production websites.
|
114
140
|
#
|
115
141
|
# Some implementations may not support all methods beyond the basic cache
|
116
|
-
# methods of
|
142
|
+
# methods of #fetch, #write, #read, #exist?, and #delete.
|
117
143
|
#
|
118
|
-
# ActiveSupport::Cache::Store can store any
|
144
|
+
# ActiveSupport::Cache::Store can store any Ruby object that is supported by
|
145
|
+
# its +coder+'s +dump+ and +load+ methods.
|
119
146
|
#
|
120
147
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
121
148
|
#
|
@@ -123,6 +150,8 @@ module ActiveSupport
|
|
123
150
|
# cache.write('city', "Duckburgh")
|
124
151
|
# cache.read('city') # => "Duckburgh"
|
125
152
|
#
|
153
|
+
# cache.write('not serializable', Proc.new {}) # => TypeError
|
154
|
+
#
|
126
155
|
# Keys are always translated into Strings and are case sensitive. When an
|
127
156
|
# object is specified as a key and has a +cache_key+ method defined, this
|
128
157
|
# method will be called to define the key. Otherwise, the +to_param+
|
@@ -143,23 +172,49 @@ module ActiveSupport
|
|
143
172
|
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
|
144
173
|
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
145
174
|
#
|
146
|
-
# Caches can also store values in a compressed format to save space and
|
147
|
-
# reduce time spent sending data. Since there is overhead, values must be
|
148
|
-
# large enough to warrant compression. To turn on compression either pass
|
149
|
-
# <tt>compress: true</tt> in the initializer or as an option to +fetch+
|
150
|
-
# or +write+. To specify the threshold at which to compress values, set the
|
151
|
-
# <tt>:compress_threshold</tt> option. The default threshold is 16K.
|
152
175
|
class Store
|
153
176
|
cattr_accessor :logger, instance_writer: true
|
154
177
|
|
155
178
|
attr_reader :silence, :options
|
156
179
|
alias :silence? :silence
|
157
180
|
|
158
|
-
|
159
|
-
|
160
|
-
|
181
|
+
class << self
|
182
|
+
private
|
183
|
+
def retrieve_pool_options(options)
|
184
|
+
{}.tap do |pool_options|
|
185
|
+
pool_options[:size] = options.delete(:pool_size) if options[:pool_size]
|
186
|
+
pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def ensure_connection_pool_added!
|
191
|
+
require "connection_pool"
|
192
|
+
rescue LoadError => e
|
193
|
+
$stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
|
194
|
+
raise e
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Creates a new cache.
|
199
|
+
#
|
200
|
+
# ==== Options
|
201
|
+
#
|
202
|
+
# * +:namespace+ - Sets the namespace for the cache. This option is
|
203
|
+
# especially useful if your application shares a cache with other
|
204
|
+
# applications.
|
205
|
+
# * +:coder+ - Replaces the default cache entry serialization mechanism
|
206
|
+
# with a custom one. The +coder+ must respond to +dump+ and +load+.
|
207
|
+
# Using a custom coder disables automatic compression.
|
208
|
+
#
|
209
|
+
# Any other specified options are treated as default options for the
|
210
|
+
# relevant cache operations, such as #read, #write, and #fetch.
|
161
211
|
def initialize(options = nil)
|
162
|
-
@options = options ? options
|
212
|
+
@options = options ? normalize_options(options) : {}
|
213
|
+
@options[:compress] = true unless @options.key?(:compress)
|
214
|
+
@options[:compress_threshold] = DEFAULT_COMPRESS_LIMIT unless @options.key?(:compress_threshold)
|
215
|
+
|
216
|
+
@coder = @options.delete(:coder) { default_coder } || NullCoder
|
217
|
+
@coder_supports_compression = @coder.respond_to?(:dump_compressed)
|
163
218
|
end
|
164
219
|
|
165
220
|
# Silences the logger.
|
@@ -194,99 +249,85 @@ module ActiveSupport
|
|
194
249
|
# end
|
195
250
|
# cache.fetch('city') # => "Duckburgh"
|
196
251
|
#
|
197
|
-
#
|
198
|
-
# Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
|
199
|
-
# the cache value as missing even if it's present. Passing a block is
|
200
|
-
# required when +force+ is true so this always results in a cache write.
|
252
|
+
# ==== Options
|
201
253
|
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
# The +:force+ option is useful when you're calling some other method to
|
207
|
-
# ask whether you should force a cache write. Otherwise, it's clearer to
|
208
|
-
# just call <tt>Cache#write</tt>.
|
209
|
-
#
|
210
|
-
# Setting <tt>:compress</tt> will store a large cache entry set by the call
|
211
|
-
# in a compressed format.
|
212
|
-
#
|
213
|
-
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
|
214
|
-
# All caches support auto-expiring content after a specified number of
|
215
|
-
# seconds. This value can be specified as an option to the constructor
|
216
|
-
# (in which case all entries will be affected), or it can be supplied to
|
217
|
-
# the +fetch+ or +write+ method to effect just one entry.
|
218
|
-
#
|
219
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
220
|
-
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
221
|
-
#
|
222
|
-
# Setting <tt>:race_condition_ttl</tt> is very useful in situations where
|
223
|
-
# a cache entry is used very frequently and is under heavy load. If a
|
224
|
-
# cache expires and due to heavy load several different processes will try
|
225
|
-
# to read data natively and then they all will try to write to cache. To
|
226
|
-
# avoid that case the first process to find an expired cache entry will
|
227
|
-
# bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
|
228
|
-
# Yes, this process is extending the time for a stale value by another few
|
229
|
-
# seconds. Because of extended life of the previous cache, other processes
|
230
|
-
# will continue to use slightly stale data for a just a bit longer. In the
|
231
|
-
# meantime that first process will go ahead and will write into cache the
|
232
|
-
# new value. After that all the processes will start getting the new value.
|
233
|
-
# The key is to keep <tt>:race_condition_ttl</tt> small.
|
234
|
-
#
|
235
|
-
# If the process regenerating the entry errors out, the entry will be
|
236
|
-
# regenerated after the specified number of seconds. Also note that the
|
237
|
-
# life of stale cache is extended only if it expired recently. Otherwise
|
238
|
-
# a new value is generated and <tt>:race_condition_ttl</tt> does not play
|
239
|
-
# any role.
|
240
|
-
#
|
241
|
-
# # Set all values to expire after one minute.
|
242
|
-
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
243
|
-
#
|
244
|
-
# cache.write('foo', 'original value')
|
245
|
-
# val_1 = nil
|
246
|
-
# val_2 = nil
|
247
|
-
# sleep 60
|
248
|
-
#
|
249
|
-
# Thread.new do
|
250
|
-
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
251
|
-
# sleep 1
|
252
|
-
# 'new value 1'
|
253
|
-
# end
|
254
|
-
# end
|
254
|
+
# Internally, +fetch+ calls #read_entry, and calls #write_entry on a cache
|
255
|
+
# miss. Thus, +fetch+ supports the same options as #read and #write.
|
256
|
+
# Additionally, +fetch+ supports the following options:
|
255
257
|
#
|
256
|
-
#
|
257
|
-
#
|
258
|
-
#
|
259
|
-
# end
|
260
|
-
# end
|
258
|
+
# * <tt>force: true</tt> - Forces a cache "miss," meaning we treat the
|
259
|
+
# cache value as missing even if it's present. Passing a block is
|
260
|
+
# required when +force+ is true so this always results in a cache write.
|
261
261
|
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
# val_1 # => "new value 1"
|
266
|
-
# val_2 # => "original value"
|
262
|
+
# cache.write('today', 'Monday')
|
263
|
+
# cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
|
264
|
+
# cache.fetch('today', force: true) # => ArgumentError
|
267
265
|
#
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
266
|
+
# The +:force+ option is useful when you're calling some other method to
|
267
|
+
# ask whether you should force a cache write. Otherwise, it's clearer to
|
268
|
+
# just call +write+.
|
271
269
|
#
|
272
|
-
#
|
273
|
-
# option, which tells the memcached server to store all values as strings.
|
274
|
-
# We can use this option with #fetch too:
|
270
|
+
# * <tt>skip_nil: true</tt> - Prevents caching a nil result:
|
275
271
|
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
|
272
|
+
# cache.fetch('foo') { nil }
|
273
|
+
# cache.fetch('bar', skip_nil: true) { nil }
|
274
|
+
# cache.exist?('foo') # => true
|
275
|
+
# cache.exist?('bar') # => false
|
276
|
+
#
|
277
|
+
# * +:race_condition_ttl+ - Specifies the number of seconds during which
|
278
|
+
# an expired value can be reused while a new value is being generated.
|
279
|
+
# This can be used to prevent race conditions when cache entries expire,
|
280
|
+
# by preventing multiple processes from simultaneously regenerating the
|
281
|
+
# same entry (also known as the dog pile effect).
|
282
|
+
#
|
283
|
+
# When a process encounters a cache entry that has expired less than
|
284
|
+
# +:race_condition_ttl+ seconds ago, it will bump the expiration time by
|
285
|
+
# +:race_condition_ttl+ seconds before generating a new value. During
|
286
|
+
# this extended time window, while the process generates a new value,
|
287
|
+
# other processes will continue to use the old value. After the first
|
288
|
+
# process writes the new value, other processes will then use it.
|
289
|
+
#
|
290
|
+
# If the first process errors out while generating a new value, another
|
291
|
+
# process can try to generate a new value after the extended time window
|
292
|
+
# has elapsed.
|
293
|
+
#
|
294
|
+
# # Set all values to expire after one minute.
|
295
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
|
296
|
+
#
|
297
|
+
# cache.write('foo', 'original value')
|
298
|
+
# val_1 = nil
|
299
|
+
# val_2 = nil
|
300
|
+
# sleep 60
|
301
|
+
#
|
302
|
+
# Thread.new do
|
303
|
+
# val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
304
|
+
# sleep 1
|
305
|
+
# 'new value 1'
|
306
|
+
# end
|
307
|
+
# end
|
308
|
+
#
|
309
|
+
# Thread.new do
|
310
|
+
# val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do
|
311
|
+
# 'new value 2'
|
312
|
+
# end
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# cache.fetch('foo') # => "original value"
|
316
|
+
# sleep 10 # First thread extended the life of cache by another 10 seconds
|
317
|
+
# cache.fetch('foo') # => "new value 1"
|
318
|
+
# val_1 # => "new value 1"
|
319
|
+
# val_2 # => "original value"
|
320
|
+
#
|
321
|
+
def fetch(name, options = nil, &block)
|
282
322
|
if block_given?
|
283
323
|
options = merged_options(options)
|
284
324
|
key = normalize_key(name, options)
|
285
325
|
|
286
326
|
entry = nil
|
287
327
|
instrument(:read, name, options) do |payload|
|
288
|
-
cached_entry = read_entry(key, options) unless options[:force]
|
328
|
+
cached_entry = read_entry(key, **options, event: payload) unless options[:force]
|
289
329
|
entry = handle_expired_entry(cached_entry, key, options)
|
330
|
+
entry = nil if entry && entry.mismatched?(normalize_version(name, options))
|
290
331
|
payload[:super_operation] = :fetch if payload
|
291
332
|
payload[:hit] = !!entry if payload
|
292
333
|
end
|
@@ -294,7 +335,7 @@ module ActiveSupport
|
|
294
335
|
if entry
|
295
336
|
get_entry_value(entry, name, options)
|
296
337
|
else
|
297
|
-
save_block_result_to_cache(name, options)
|
338
|
+
save_block_result_to_cache(name, options, &block)
|
298
339
|
end
|
299
340
|
elsif options && options[:force]
|
300
341
|
raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
|
@@ -303,19 +344,35 @@ module ActiveSupport
|
|
303
344
|
end
|
304
345
|
end
|
305
346
|
|
306
|
-
#
|
347
|
+
# Reads data from the cache, using the given key. If there is data in
|
307
348
|
# the cache with the given key, then that data is returned. Otherwise,
|
308
349
|
# +nil+ is returned.
|
309
350
|
#
|
310
|
-
#
|
351
|
+
# Note, if data was written with the <tt>:expires_in</tt> or
|
352
|
+
# <tt>:version</tt> options, both of these conditions are applied before
|
353
|
+
# the data is returned.
|
354
|
+
#
|
355
|
+
# ==== Options
|
356
|
+
#
|
357
|
+
# * +:version+ - Specifies a version for the cache entry. If the cached
|
358
|
+
# version does not match the requested version, the read will be treated
|
359
|
+
# as a cache miss. This feature is used to support recyclable cache keys.
|
360
|
+
#
|
361
|
+
# Other options will be handled by the specific cache store implementation.
|
311
362
|
def read(name, options = nil)
|
312
363
|
options = merged_options(options)
|
313
|
-
key
|
364
|
+
key = normalize_key(name, options)
|
365
|
+
version = normalize_version(name, options)
|
366
|
+
|
314
367
|
instrument(:read, name, options) do |payload|
|
315
|
-
entry = read_entry(key, options)
|
368
|
+
entry = read_entry(key, **options, event: payload)
|
369
|
+
|
316
370
|
if entry
|
317
371
|
if entry.expired?
|
318
|
-
delete_entry(key, options)
|
372
|
+
delete_entry(key, **options)
|
373
|
+
payload[:hit] = false if payload
|
374
|
+
nil
|
375
|
+
elsif entry.mismatched?(version)
|
319
376
|
payload[:hit] = false if payload
|
320
377
|
nil
|
321
378
|
else
|
@@ -339,19 +396,24 @@ module ActiveSupport
|
|
339
396
|
options = names.extract_options!
|
340
397
|
options = merged_options(options)
|
341
398
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
entry = read_entry(key, options)
|
346
|
-
if entry
|
347
|
-
if entry.expired?
|
348
|
-
delete_entry(key, options)
|
349
|
-
else
|
350
|
-
results[name] = entry.value
|
351
|
-
end
|
399
|
+
instrument :read_multi, names, options do |payload|
|
400
|
+
read_multi_entries(names, **options, event: payload).tap do |results|
|
401
|
+
payload[:hits] = results.keys
|
352
402
|
end
|
353
403
|
end
|
354
|
-
|
404
|
+
end
|
405
|
+
|
406
|
+
# Cache Storage API to write multiple values at once.
|
407
|
+
def write_multi(hash, options = nil)
|
408
|
+
options = merged_options(options)
|
409
|
+
|
410
|
+
instrument :write_multi, hash, options do |payload|
|
411
|
+
entries = hash.each_with_object({}) do |(name, value), memo|
|
412
|
+
memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
413
|
+
end
|
414
|
+
|
415
|
+
write_multi_entries entries, **options
|
416
|
+
end
|
355
417
|
end
|
356
418
|
|
357
419
|
# Fetches data from the cache, using the given keys. If there is data in
|
@@ -362,8 +424,6 @@ module ActiveSupport
|
|
362
424
|
# to the cache. If you do not want to write the cache when the cache is
|
363
425
|
# not found, use #read_multi.
|
364
426
|
#
|
365
|
-
# Options are passed to the underlying cache implementation.
|
366
|
-
#
|
367
427
|
# Returns a hash with the data for each of the names. For example:
|
368
428
|
#
|
369
429
|
# cache.write("bim", "bam")
|
@@ -373,31 +433,78 @@ module ActiveSupport
|
|
373
433
|
# # => { "bim" => "bam",
|
374
434
|
# # "unknown_key" => "Fallback value for key: unknown_key" }
|
375
435
|
#
|
436
|
+
# Options are passed to the underlying cache implementation. For example:
|
437
|
+
#
|
438
|
+
# cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
|
439
|
+
# "buzz"
|
440
|
+
# end
|
441
|
+
# # => {"fizz"=>"buzz"}
|
442
|
+
# cache.read("fizz")
|
443
|
+
# # => "buzz"
|
444
|
+
# sleep(6)
|
445
|
+
# cache.read("fizz")
|
446
|
+
# # => nil
|
376
447
|
def fetch_multi(*names)
|
377
448
|
raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
|
378
449
|
|
379
450
|
options = names.extract_options!
|
380
451
|
options = merged_options(options)
|
381
|
-
results = read_multi(*names, options)
|
382
452
|
|
383
|
-
names
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
453
|
+
instrument :read_multi, names, options do |payload|
|
454
|
+
reads = read_multi_entries(names, **options)
|
455
|
+
writes = {}
|
456
|
+
ordered = names.index_with do |name|
|
457
|
+
reads.fetch(name) { writes[name] = yield(name) }
|
388
458
|
end
|
459
|
+
|
460
|
+
payload[:hits] = reads.keys
|
461
|
+
payload[:super_operation] = :fetch_multi
|
462
|
+
|
463
|
+
write_multi(writes, options)
|
464
|
+
|
465
|
+
ordered
|
389
466
|
end
|
390
467
|
end
|
391
468
|
|
392
|
-
# Writes the value to the cache
|
469
|
+
# Writes the value to the cache with the key. The value must be supported
|
470
|
+
# by the +coder+'s +dump+ and +load+ methods.
|
393
471
|
#
|
394
|
-
#
|
472
|
+
# By default, cache entries larger than 1kB are compressed. Compression
|
473
|
+
# allows more data to be stored in the same memory footprint, leading to
|
474
|
+
# fewer cache evictions and higher hit rates.
|
475
|
+
#
|
476
|
+
# ==== Options
|
477
|
+
#
|
478
|
+
# * <tt>compress: false</tt> - Disables compression of the cache entry.
|
479
|
+
#
|
480
|
+
# * +:compress_threshold+ - The compression threshold, specified in bytes.
|
481
|
+
# \Cache entries larger than this threshold will be compressed. Defaults
|
482
|
+
# to +1.kilobyte+.
|
483
|
+
#
|
484
|
+
# * +:expires_in+ - Sets a relative expiration time for the cache entry,
|
485
|
+
# specified in seconds. +:expire_in+ and +:expired_in+ are aliases for
|
486
|
+
# +:expires_in+.
|
487
|
+
#
|
488
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
|
489
|
+
# cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
|
490
|
+
#
|
491
|
+
# * +:expires_at+ - Sets an absolute expiration time for the cache entry.
|
492
|
+
#
|
493
|
+
# cache = ActiveSupport::Cache::MemoryStore.new
|
494
|
+
# cache.write(key, value, expires_at: Time.now.at_end_of_hour)
|
495
|
+
#
|
496
|
+
# * +:version+ - Specifies a version for the cache entry. When reading
|
497
|
+
# from the cache, if the cached version does not match the requested
|
498
|
+
# version, the read will be treated as a cache miss. This feature is
|
499
|
+
# used to support recyclable cache keys.
|
500
|
+
#
|
501
|
+
# Other options will be handled by the specific cache store implementation.
|
395
502
|
def write(name, value, options = nil)
|
396
503
|
options = merged_options(options)
|
397
504
|
|
398
505
|
instrument(:write, name, options) do
|
399
|
-
entry = Entry.new(value, options)
|
400
|
-
write_entry(normalize_key(name, options), entry, options)
|
506
|
+
entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
|
507
|
+
write_entry(normalize_key(name, options), entry, **options)
|
401
508
|
end
|
402
509
|
end
|
403
510
|
|
@@ -408,7 +515,19 @@ module ActiveSupport
|
|
408
515
|
options = merged_options(options)
|
409
516
|
|
410
517
|
instrument(:delete, name) do
|
411
|
-
delete_entry(normalize_key(name, options), options)
|
518
|
+
delete_entry(normalize_key(name, options), **options)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# Deletes multiple entries in the cache.
|
523
|
+
#
|
524
|
+
# Options are passed to the underlying cache implementation.
|
525
|
+
def delete_multi(names, options = nil)
|
526
|
+
options = merged_options(options)
|
527
|
+
names.map! { |key| normalize_key(key, options) }
|
528
|
+
|
529
|
+
instrument :delete_multi, names do
|
530
|
+
delete_multi_entries(names, **options)
|
412
531
|
end
|
413
532
|
end
|
414
533
|
|
@@ -418,17 +537,21 @@ module ActiveSupport
|
|
418
537
|
def exist?(name, options = nil)
|
419
538
|
options = merged_options(options)
|
420
539
|
|
421
|
-
instrument(:exist?, name) do
|
422
|
-
entry = read_entry(normalize_key(name, options), options)
|
423
|
-
(entry && !entry.expired?) || false
|
540
|
+
instrument(:exist?, name) do |payload|
|
541
|
+
entry = read_entry(normalize_key(name, options), **options, event: payload)
|
542
|
+
(entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
|
424
543
|
end
|
425
544
|
end
|
426
545
|
|
546
|
+
def new_entry(value, options = nil) # :nodoc:
|
547
|
+
Entry.new(value, **merged_options(options))
|
548
|
+
end
|
549
|
+
|
427
550
|
# Deletes all entries with keys matching the pattern.
|
428
551
|
#
|
429
552
|
# Options are passed to the underlying cache implementation.
|
430
553
|
#
|
431
|
-
#
|
554
|
+
# Some implementations may not support this method.
|
432
555
|
def delete_matched(matcher, options = nil)
|
433
556
|
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
|
434
557
|
end
|
@@ -437,7 +560,7 @@ module ActiveSupport
|
|
437
560
|
#
|
438
561
|
# Options are passed to the underlying cache implementation.
|
439
562
|
#
|
440
|
-
#
|
563
|
+
# Some implementations may not support this method.
|
441
564
|
def increment(name, amount = 1, options = nil)
|
442
565
|
raise NotImplementedError.new("#{self.class.name} does not support increment")
|
443
566
|
end
|
@@ -446,16 +569,16 @@ module ActiveSupport
|
|
446
569
|
#
|
447
570
|
# Options are passed to the underlying cache implementation.
|
448
571
|
#
|
449
|
-
#
|
572
|
+
# Some implementations may not support this method.
|
450
573
|
def decrement(name, amount = 1, options = nil)
|
451
574
|
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
452
575
|
end
|
453
576
|
|
454
|
-
#
|
577
|
+
# Cleans up the cache by removing expired entries.
|
455
578
|
#
|
456
579
|
# Options are passed to the underlying cache implementation.
|
457
580
|
#
|
458
|
-
#
|
581
|
+
# Some implementations may not support this method.
|
459
582
|
def cleanup(options = nil)
|
460
583
|
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
|
461
584
|
end
|
@@ -465,12 +588,16 @@ module ActiveSupport
|
|
465
588
|
#
|
466
589
|
# The options hash is passed to the underlying cache implementation.
|
467
590
|
#
|
468
|
-
#
|
591
|
+
# Some implementations may not support this method.
|
469
592
|
def clear(options = nil)
|
470
593
|
raise NotImplementedError.new("#{self.class.name} does not support clear")
|
471
594
|
end
|
472
595
|
|
473
596
|
private
|
597
|
+
def default_coder
|
598
|
+
Coders[Cache.format_version]
|
599
|
+
end
|
600
|
+
|
474
601
|
# Adds the namespace defined in the options to a pattern designed to
|
475
602
|
# match keys. Implementations that support delete_matched should call
|
476
603
|
# this method to translate a pattern that matches names into one that
|
@@ -492,28 +619,125 @@ module ActiveSupport
|
|
492
619
|
|
493
620
|
# Reads an entry from the cache implementation. Subclasses must implement
|
494
621
|
# this method.
|
495
|
-
def read_entry(key, options)
|
622
|
+
def read_entry(key, **options)
|
496
623
|
raise NotImplementedError.new
|
497
624
|
end
|
498
625
|
|
499
626
|
# Writes an entry to the cache implementation. Subclasses must implement
|
500
627
|
# this method.
|
501
|
-
def write_entry(key, entry, options)
|
628
|
+
def write_entry(key, entry, **options)
|
502
629
|
raise NotImplementedError.new
|
503
630
|
end
|
504
631
|
|
632
|
+
def serialize_entry(entry, **options)
|
633
|
+
options = merged_options(options)
|
634
|
+
if @coder_supports_compression && options[:compress]
|
635
|
+
@coder.dump_compressed(entry, options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT)
|
636
|
+
else
|
637
|
+
@coder.dump(entry)
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
def deserialize_entry(payload)
|
642
|
+
payload.nil? ? nil : @coder.load(payload)
|
643
|
+
end
|
644
|
+
|
645
|
+
# Reads multiple entries from the cache implementation. Subclasses MAY
|
646
|
+
# implement this method.
|
647
|
+
def read_multi_entries(names, **options)
|
648
|
+
names.each_with_object({}) do |name, results|
|
649
|
+
key = normalize_key(name, options)
|
650
|
+
entry = read_entry(key, **options)
|
651
|
+
|
652
|
+
next unless entry
|
653
|
+
|
654
|
+
version = normalize_version(name, options)
|
655
|
+
|
656
|
+
if entry.expired?
|
657
|
+
delete_entry(key, **options)
|
658
|
+
elsif !entry.mismatched?(version)
|
659
|
+
results[name] = entry.value
|
660
|
+
end
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
# Writes multiple entries to the cache implementation. Subclasses MAY
|
665
|
+
# implement this method.
|
666
|
+
def write_multi_entries(hash, **options)
|
667
|
+
hash.each do |key, entry|
|
668
|
+
write_entry key, entry, **options
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
505
672
|
# Deletes an entry from the cache implementation. Subclasses must
|
506
673
|
# implement this method.
|
507
|
-
def delete_entry(key, options)
|
674
|
+
def delete_entry(key, **options)
|
508
675
|
raise NotImplementedError.new
|
509
676
|
end
|
510
677
|
|
678
|
+
# Deletes multiples entries in the cache implementation. Subclasses MAY
|
679
|
+
# implement this method.
|
680
|
+
def delete_multi_entries(entries, **options)
|
681
|
+
entries.count { |key| delete_entry(key, **options) }
|
682
|
+
end
|
683
|
+
|
511
684
|
# Merges the default options with ones specific to a method call.
|
512
685
|
def merged_options(call_options)
|
513
686
|
if call_options
|
514
|
-
|
687
|
+
call_options = normalize_options(call_options)
|
688
|
+
if options.empty?
|
689
|
+
call_options
|
690
|
+
else
|
691
|
+
options.merge(call_options)
|
692
|
+
end
|
693
|
+
else
|
694
|
+
options
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
# Normalize aliased options to their canonical form
|
699
|
+
def normalize_options(options)
|
700
|
+
options = options.dup
|
701
|
+
OPTION_ALIASES.each do |canonical_name, aliases|
|
702
|
+
alias_key = aliases.detect { |key| options.key?(key) }
|
703
|
+
options[canonical_name] ||= options[alias_key] if alias_key
|
704
|
+
options.except!(*aliases)
|
705
|
+
end
|
706
|
+
|
707
|
+
options
|
708
|
+
end
|
709
|
+
|
710
|
+
# Expands and namespaces the cache key. May be overridden by
|
711
|
+
# cache stores to do additional normalization.
|
712
|
+
def normalize_key(key, options = nil)
|
713
|
+
namespace_key expanded_key(key), options
|
714
|
+
end
|
715
|
+
|
716
|
+
# Prefix the key with a namespace string:
|
717
|
+
#
|
718
|
+
# namespace_key 'foo', namespace: 'cache'
|
719
|
+
# # => 'cache:foo'
|
720
|
+
#
|
721
|
+
# With a namespace block:
|
722
|
+
#
|
723
|
+
# namespace_key 'foo', namespace: -> { 'cache' }
|
724
|
+
# # => 'cache:foo'
|
725
|
+
def namespace_key(key, options = nil)
|
726
|
+
options = merged_options(options)
|
727
|
+
namespace = options[:namespace]
|
728
|
+
|
729
|
+
if namespace.respond_to?(:call)
|
730
|
+
namespace = namespace.call
|
731
|
+
end
|
732
|
+
|
733
|
+
if key && key.encoding != Encoding::UTF_8
|
734
|
+
key = key.dup.force_encoding(Encoding::UTF_8)
|
735
|
+
end
|
736
|
+
|
737
|
+
if namespace
|
738
|
+
"#{namespace}:#{key}"
|
515
739
|
else
|
516
|
-
|
740
|
+
key
|
517
741
|
end
|
518
742
|
end
|
519
743
|
|
@@ -526,50 +750,49 @@ module ActiveSupport
|
|
526
750
|
case key
|
527
751
|
when Array
|
528
752
|
if key.size > 1
|
529
|
-
key
|
753
|
+
key.collect { |element| expanded_key(element) }
|
530
754
|
else
|
531
|
-
key
|
755
|
+
expanded_key(key.first)
|
532
756
|
end
|
533
757
|
when Hash
|
534
|
-
key
|
535
|
-
|
758
|
+
key.collect { |k, v| "#{k}=#{v}" }.sort!
|
759
|
+
else
|
760
|
+
key
|
761
|
+
end.to_param
|
762
|
+
end
|
536
763
|
|
537
|
-
|
764
|
+
def normalize_version(key, options = nil)
|
765
|
+
(options && options[:version].try(:to_param)) || expanded_version(key)
|
538
766
|
end
|
539
767
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
key
|
544
|
-
|
545
|
-
|
546
|
-
key = "#{prefix}:#{key}" if prefix
|
547
|
-
key
|
768
|
+
def expanded_version(key)
|
769
|
+
case
|
770
|
+
when key.respond_to?(:cache_version) then key.cache_version.to_param
|
771
|
+
when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
|
772
|
+
when key.respond_to?(:to_a) then expanded_version(key.to_a)
|
773
|
+
end
|
548
774
|
end
|
549
775
|
|
550
776
|
def instrument(operation, key, options = nil)
|
551
|
-
|
777
|
+
if logger && logger.debug? && !silence?
|
778
|
+
logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
|
779
|
+
end
|
552
780
|
|
553
|
-
payload = { key: key }
|
781
|
+
payload = { key: key, store: self.class.name }
|
554
782
|
payload.merge!(options) if options.is_a?(Hash)
|
555
783
|
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
|
556
784
|
end
|
557
785
|
|
558
|
-
def log
|
559
|
-
return unless logger && logger.debug? && !silence?
|
560
|
-
logger.debug(yield)
|
561
|
-
end
|
562
|
-
|
563
786
|
def handle_expired_entry(entry, key, options)
|
564
787
|
if entry && entry.expired?
|
565
788
|
race_ttl = options[:race_condition_ttl].to_i
|
566
789
|
if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl)
|
567
790
|
# When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache
|
568
791
|
# for a brief period while the entry is being recalculated.
|
569
|
-
entry.expires_at = Time.now + race_ttl
|
792
|
+
entry.expires_at = Time.now.to_f + race_ttl
|
570
793
|
write_entry(key, entry, expires_in: race_ttl * 2)
|
571
794
|
else
|
572
|
-
delete_entry(key, options)
|
795
|
+
delete_entry(key, **options)
|
573
796
|
end
|
574
797
|
entry = nil
|
575
798
|
end
|
@@ -577,7 +800,7 @@ module ActiveSupport
|
|
577
800
|
end
|
578
801
|
|
579
802
|
def get_entry_value(entry, name, options)
|
580
|
-
instrument(:fetch_hit, name, options) {}
|
803
|
+
instrument(:fetch_hit, name, options) { }
|
581
804
|
entry.value
|
582
805
|
end
|
583
806
|
|
@@ -586,39 +809,137 @@ module ActiveSupport
|
|
586
809
|
yield(name)
|
587
810
|
end
|
588
811
|
|
589
|
-
write(name, result, options)
|
812
|
+
write(name, result, options) unless result.nil? && options[:skip_nil]
|
590
813
|
result
|
591
814
|
end
|
592
815
|
end
|
593
816
|
|
594
|
-
|
595
|
-
|
596
|
-
|
817
|
+
module NullCoder # :nodoc:
|
818
|
+
extend self
|
819
|
+
|
820
|
+
def dump(entry)
|
821
|
+
entry
|
822
|
+
end
|
823
|
+
|
824
|
+
def dump_compressed(entry, threshold)
|
825
|
+
entry.compressed(threshold)
|
826
|
+
end
|
827
|
+
|
828
|
+
def load(payload)
|
829
|
+
payload
|
830
|
+
end
|
831
|
+
end
|
832
|
+
|
833
|
+
module Coders # :nodoc:
|
834
|
+
MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
|
835
|
+
MARK_70_UNCOMPRESSED = "\x00".b.freeze
|
836
|
+
MARK_70_COMPRESSED = "\x01".b.freeze
|
837
|
+
|
838
|
+
class << self
|
839
|
+
def [](version)
|
840
|
+
case version
|
841
|
+
when 6.1
|
842
|
+
Rails61Coder
|
843
|
+
when 7.0
|
844
|
+
Rails70Coder
|
845
|
+
else
|
846
|
+
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
module Loader
|
852
|
+
extend self
|
853
|
+
|
854
|
+
def load(payload)
|
855
|
+
if !payload.is_a?(String)
|
856
|
+
ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
|
857
|
+
|
858
|
+
return nil
|
859
|
+
elsif payload.start_with?(MARK_70_UNCOMPRESSED)
|
860
|
+
members = Marshal.load(payload.byteslice(1..-1))
|
861
|
+
elsif payload.start_with?(MARK_70_COMPRESSED)
|
862
|
+
members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
|
863
|
+
elsif payload.start_with?(MARK_61)
|
864
|
+
return Marshal.load(payload)
|
865
|
+
else
|
866
|
+
ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
|
867
|
+
|
868
|
+
return nil
|
869
|
+
end
|
870
|
+
Entry.unpack(members)
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
module Rails61Coder
|
875
|
+
include Loader
|
876
|
+
extend self
|
877
|
+
|
878
|
+
def dump(entry)
|
879
|
+
Marshal.dump(entry)
|
880
|
+
end
|
881
|
+
|
882
|
+
def dump_compressed(entry, threshold)
|
883
|
+
Marshal.dump(entry.compressed(threshold))
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
module Rails70Coder
|
888
|
+
include Loader
|
889
|
+
extend self
|
890
|
+
|
891
|
+
def dump(entry)
|
892
|
+
MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
|
893
|
+
end
|
894
|
+
|
895
|
+
def dump_compressed(entry, threshold)
|
896
|
+
payload = Marshal.dump(entry.pack)
|
897
|
+
if payload.bytesize >= threshold
|
898
|
+
compressed_payload = Zlib::Deflate.deflate(payload)
|
899
|
+
if compressed_payload.bytesize < payload.bytesize
|
900
|
+
return MARK_70_COMPRESSED + compressed_payload
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
MARK_70_UNCOMPRESSED + payload
|
905
|
+
end
|
906
|
+
end
|
907
|
+
end
|
908
|
+
|
909
|
+
# This class is used to represent cache entries. Cache entries have a value, an optional
|
910
|
+
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
|
911
|
+
# on the cache. The version is used to support the :version option on the cache for rejecting
|
912
|
+
# mismatches.
|
597
913
|
#
|
598
914
|
# Since cache entries in most instances will be serialized, the internals of this class are highly optimized
|
599
915
|
# using short instance variable names that are lazily defined.
|
600
916
|
class Entry # :nodoc:
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
# +:compress+, +:compress_threshold+, and +:expires_in+.
|
605
|
-
def initialize(value, options = {})
|
606
|
-
if should_compress?(value, options)
|
607
|
-
@value = compress(value)
|
608
|
-
@compressed = true
|
609
|
-
else
|
610
|
-
@value = value
|
917
|
+
class << self
|
918
|
+
def unpack(members)
|
919
|
+
new(members[0], expires_at: members[1], version: members[2])
|
611
920
|
end
|
921
|
+
end
|
612
922
|
|
613
|
-
|
614
|
-
|
615
|
-
|
923
|
+
attr_reader :version
|
924
|
+
|
925
|
+
# Creates a new cache entry for the specified value. Options supported are
|
926
|
+
# +:compressed+, +:version+, +:expires_at+ and +:expires_in+.
|
927
|
+
def initialize(value, compressed: false, version: nil, expires_in: nil, expires_at: nil, **)
|
928
|
+
@value = value
|
929
|
+
@version = version
|
930
|
+
@created_at = 0.0
|
931
|
+
@expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
|
932
|
+
@compressed = true if compressed
|
616
933
|
end
|
617
934
|
|
618
935
|
def value
|
619
936
|
compressed? ? uncompress(@value) : @value
|
620
937
|
end
|
621
938
|
|
939
|
+
def mismatched?(version)
|
940
|
+
@version && version && @version != version
|
941
|
+
end
|
942
|
+
|
622
943
|
# Checks if the entry is expired. The +expires_in+ parameter can override
|
623
944
|
# the value set when the entry was created.
|
624
945
|
def expired?
|
@@ -638,20 +959,48 @@ module ActiveSupport
|
|
638
959
|
end
|
639
960
|
|
640
961
|
# Returns the size of the cached value. This could be less than
|
641
|
-
# <tt>value.
|
642
|
-
def
|
643
|
-
|
644
|
-
|
962
|
+
# <tt>value.bytesize</tt> if the data is compressed.
|
963
|
+
def bytesize
|
964
|
+
case value
|
965
|
+
when NilClass
|
966
|
+
0
|
967
|
+
when String
|
968
|
+
@value.bytesize
|
645
969
|
else
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
970
|
+
@s ||= Marshal.dump(@value).bytesize
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
def compressed? # :nodoc:
|
975
|
+
defined?(@compressed)
|
976
|
+
end
|
977
|
+
|
978
|
+
def compressed(compress_threshold)
|
979
|
+
return self if compressed?
|
980
|
+
|
981
|
+
case @value
|
982
|
+
when nil, true, false, Numeric
|
983
|
+
uncompressed_size = 0
|
984
|
+
when String
|
985
|
+
uncompressed_size = @value.bytesize
|
986
|
+
else
|
987
|
+
serialized = Marshal.dump(@value)
|
988
|
+
uncompressed_size = serialized.bytesize
|
989
|
+
end
|
990
|
+
|
991
|
+
if uncompressed_size >= compress_threshold
|
992
|
+
serialized ||= Marshal.dump(@value)
|
993
|
+
compressed = Zlib::Deflate.deflate(serialized)
|
994
|
+
|
995
|
+
if compressed.bytesize < uncompressed_size
|
996
|
+
return Entry.new(compressed, compressed: true, expires_at: expires_at, version: version)
|
653
997
|
end
|
654
998
|
end
|
999
|
+
self
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def local?
|
1003
|
+
false
|
655
1004
|
end
|
656
1005
|
|
657
1006
|
# Duplicates the value in a class. This is used by cache implementations that don't natively
|
@@ -666,26 +1015,13 @@ module ActiveSupport
|
|
666
1015
|
end
|
667
1016
|
end
|
668
1017
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
return true if serialized_value_size >= compress_threshold
|
676
|
-
end
|
677
|
-
|
678
|
-
false
|
679
|
-
end
|
680
|
-
|
681
|
-
def compressed?
|
682
|
-
defined?(@compressed) ? @compressed : false
|
683
|
-
end
|
684
|
-
|
685
|
-
def compress(value)
|
686
|
-
Zlib::Deflate.deflate(Marshal.dump(value))
|
687
|
-
end
|
1018
|
+
def pack
|
1019
|
+
members = [value, expires_at, version]
|
1020
|
+
members.pop while !members.empty? && members.last.nil?
|
1021
|
+
members
|
1022
|
+
end
|
688
1023
|
|
1024
|
+
private
|
689
1025
|
def uncompress(value)
|
690
1026
|
Marshal.load(Zlib::Inflate.inflate(value))
|
691
1027
|
end
|