activesupport 5.2.8.1 → 6.1.6.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 +426 -424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +29 -3
- data/lib/active_support/benchmarkable.rb +1 -1
- data/lib/active_support/cache/file_store.rb +34 -34
- data/lib/active_support/cache/mem_cache_store.rb +39 -24
- data/lib/active_support/cache/memory_store.rb +59 -33
- data/lib/active_support/cache/null_store.rb +8 -3
- data/lib/active_support/cache/redis_cache_store.rb +72 -45
- data/lib/active_support/cache/strategy/local_cache.rb +41 -26
- data/lib/active_support/cache.rb +148 -78
- data/lib/active_support/callbacks.rb +81 -64
- data/lib/active_support/concern.rb +70 -3
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/configurable.rb +10 -14
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/array/conversions.rb +5 -5
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array.rb +1 -1
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/class/attribute.rb +32 -47
- data/lib/active_support/core_ext/class/subclasses.rb +17 -38
- data/lib/active_support/core_ext/date/calculations.rb +6 -5
- data/lib/active_support/core_ext/date/conversions.rb +2 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +1 -0
- data/lib/active_support/core_ext/enumerable.rb +171 -75
- data/lib/active_support/core_ext/hash/conversions.rb +3 -3
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +2 -2
- data/lib/active_support/core_ext/hash/keys.rb +1 -30
- data/lib/active_support/core_ext/hash/slice.rb +6 -27
- data/lib/active_support/core_ext/hash.rb +1 -2
- data/lib/active_support/core_ext/integer/multiple.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/marshal.rb +2 -0
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
- data/lib/active_support/core_ext/module/concerning.rb +8 -2
- data/lib/active_support/core_ext/module/delegation.rb +76 -33
- data/lib/active_support/core_ext/module/introspection.rb +16 -15
- data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
- data/lib/active_support/core_ext/module.rb +0 -1
- data/lib/active_support/core_ext/name_error.rb +29 -2
- data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/blank.rb +1 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
- data/lib/active_support/core_ext/object/duplicable.rb +7 -114
- data/lib/active_support/core_ext/object/json.rb +14 -2
- data/lib/active_support/core_ext/object/try.rb +17 -7
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/range/compare_range.rb +34 -13
- data/lib/active_support/core_ext/range/conversions.rb +31 -29
- data/lib/active_support/core_ext/range/each.rb +0 -1
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
- data/lib/active_support/core_ext/regexp.rb +8 -5
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +5 -16
- data/lib/active_support/core_ext/string/conversions.rb +1 -0
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +45 -6
- data/lib/active_support/core_ext/string/inquiry.rb +1 -0
- data/lib/active_support/core_ext/string/multibyte.rb +6 -5
- data/lib/active_support/core_ext/string/output_safety.rb +70 -13
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/time/calculations.rb +53 -3
- data/lib/active_support/core_ext/time/conversions.rb +2 -0
- data/lib/active_support/core_ext/uri.rb +6 -1
- data/lib/active_support/core_ext.rb +1 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +16 -2
- data/lib/active_support/dependencies/zeitwerk_integration.rb +120 -0
- data/lib/active_support/dependencies.rb +109 -34
- data/lib/active_support/deprecation/behaviors.rb +16 -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 +18 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
- data/lib/active_support/deprecation/reporting.rb +50 -7
- data/lib/active_support/deprecation.rb +6 -1
- data/lib/active_support/descendants_tracker.rb +59 -9
- data/lib/active_support/digest.rb +2 -0
- data/lib/active_support/duration/iso8601_parser.rb +2 -4
- data/lib/active_support/duration/iso8601_serializer.rb +18 -14
- data/lib/active_support/duration.rb +82 -33
- data/lib/active_support/encrypted_configuration.rb +0 -4
- data/lib/active_support/encrypted_file.rb +22 -4
- data/lib/active_support/environment_inquirer.rb +20 -0
- data/lib/active_support/evented_file_update_checker.rb +82 -117
- data/lib/active_support/execution_wrapper.rb +2 -1
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/fork_tracker.rb +64 -0
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/hash_with_indifferent_access.rb +70 -42
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +15 -8
- data/lib/active_support/inflector/inflections.rb +2 -7
- data/lib/active_support/inflector/methods.rb +49 -58
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/json/decoding.rb +25 -26
- data/lib/active_support/json/encoding.rb +11 -3
- data/lib/active_support/key_generator.rb +1 -33
- data/lib/active_support/lazy_load_hooks.rb +5 -2
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/locale/en.yml +7 -3
- data/lib/active_support/log_subscriber.rb +39 -9
- data/lib/active_support/logger.rb +2 -17
- data/lib/active_support/logger_silence.rb +11 -19
- data/lib/active_support/logger_thread_safe_level.rb +50 -6
- data/lib/active_support/message_encryptor.rb +8 -13
- data/lib/active_support/message_verifier.rb +10 -10
- data/lib/active_support/messages/metadata.rb +11 -2
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotator.rb +10 -9
- data/lib/active_support/multibyte/chars.rb +10 -68
- data/lib/active_support/multibyte/unicode.rb +15 -327
- data/lib/active_support/notifications/fanout.rb +116 -16
- data/lib/active_support/notifications/instrumenter.rb +71 -9
- data/lib/active_support/notifications.rb +72 -8
- data/lib/active_support/number_helper/number_converter.rb +5 -6
- data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
- data/lib/active_support/number_helper/rounding_helper.rb +12 -28
- data/lib/active_support/number_helper.rb +38 -12
- data/lib/active_support/option_merger.rb +22 -3
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +13 -3
- data/lib/active_support/parameter_filter.rb +133 -0
- data/lib/active_support/per_thread_registry.rb +2 -1
- data/lib/active_support/rails.rb +1 -10
- data/lib/active_support/railtie.rb +23 -1
- data/lib/active_support/reloader.rb +4 -5
- data/lib/active_support/rescuable.rb +4 -4
- 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 +4 -3
- data/lib/active_support/subscriber.rb +72 -28
- data/lib/active_support/tagged_logging.rb +42 -8
- data/lib/active_support/test_case.rb +91 -0
- data/lib/active_support/testing/assertions.rb +30 -9
- data/lib/active_support/testing/deprecation.rb +0 -1
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +28 -1
- data/lib/active_support/testing/parallelization/server.rb +78 -0
- data/lib/active_support/testing/parallelization/worker.rb +100 -0
- data/lib/active_support/testing/parallelization.rb +51 -0
- data/lib/active_support/testing/stream.rb +1 -2
- data/lib/active_support/testing/time_helpers.rb +47 -12
- data/lib/active_support/time_with_zone.rb +81 -47
- data/lib/active_support/values/time_zone.rb +34 -17
- data/lib/active_support/xml_mini/jdom.rb +2 -3
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
- data/lib/active_support/xml_mini/rexml.rb +10 -3
- data/lib/active_support/xml_mini.rb +2 -10
- data/lib/active_support.rb +14 -1
- metadata +54 -27
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
- data/lib/active_support/core_ext/hash/compact.rb +0 -29
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/core_ext/module/reachable.rb +0 -11
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
- data/lib/active_support/core_ext/range/include_range.rb +0 -3
- data/lib/active_support/values/unicode_tables.dat +0 -0
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -5,6 +5,7 @@ extensions that were found useful for the Rails framework. These additions
|
|
5
5
|
reside in this package so they can be loaded as needed in Ruby projects
|
6
6
|
outside of Rails.
|
7
7
|
|
8
|
+
You can read more about the extensions in the {Active Support Core Extensions}[https://edgeguides.rubyonrails.org/active_support_core_extensions.html] guide.
|
8
9
|
|
9
10
|
== Download and installation
|
10
11
|
|
@@ -14,7 +15,7 @@ The latest version of Active Support can be installed with RubyGems:
|
|
14
15
|
|
15
16
|
Source code can be downloaded as part of the Rails project on GitHub:
|
16
17
|
|
17
|
-
* https://github.com/rails/rails/tree/
|
18
|
+
* https://github.com/rails/rails/tree/main/activesupport
|
18
19
|
|
19
20
|
|
20
21
|
== License
|
@@ -28,7 +29,7 @@ Active Support is released under the MIT license:
|
|
28
29
|
|
29
30
|
API documentation is at:
|
30
31
|
|
31
|
-
*
|
32
|
+
* https://api.rubyonrails.org
|
32
33
|
|
33
34
|
Bug reports for the Ruby on Rails project can be filed here:
|
34
35
|
|
@@ -36,4 +37,4 @@ Bug reports for the Ruby on Rails project can be filed here:
|
|
36
37
|
|
37
38
|
Feature requests should be discussed on the rails-core mailing list here:
|
38
39
|
|
39
|
-
* https://
|
40
|
+
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
# Actionable errors let's you define actions to resolve an error.
|
5
|
+
#
|
6
|
+
# To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
|
7
|
+
# module and invoke the +action+ class macro to define the action. An action
|
8
|
+
# needs a name and a block to execute.
|
9
|
+
module ActionableError
|
10
|
+
extend Concern
|
11
|
+
|
12
|
+
class NonActionable < StandardError; end
|
13
|
+
|
14
|
+
included do
|
15
|
+
class_attribute :_actions, default: {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.actions(error) # :nodoc:
|
19
|
+
case error
|
20
|
+
when ActionableError, -> it { Class === it && it < ActionableError }
|
21
|
+
error._actions
|
22
|
+
else
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.dispatch(error, name) # :nodoc:
|
28
|
+
actions(error).fetch(name).call
|
29
|
+
rescue KeyError
|
30
|
+
raise NonActionable, "Cannot find action \"#{name}\""
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
# Defines an action that can resolve the error.
|
35
|
+
#
|
36
|
+
# class PendingMigrationError < MigrationError
|
37
|
+
# include ActiveSupport::ActionableError
|
38
|
+
#
|
39
|
+
# action "Run pending migrations" do
|
40
|
+
# ActiveRecord::Tasks::DatabaseTasks.migrate
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
def action(name, &block)
|
44
|
+
_actions[name] = block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
4
|
+
|
3
5
|
module ActiveSupport
|
4
6
|
# Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
|
5
7
|
# its string-like contents:
|
@@ -34,11 +36,11 @@ module ActiveSupport
|
|
34
36
|
|
35
37
|
private
|
36
38
|
def respond_to_missing?(name, include_private = false)
|
37
|
-
(
|
39
|
+
name.end_with?("?") || super
|
38
40
|
end
|
39
41
|
|
40
42
|
def method_missing(name, *args)
|
41
|
-
if name
|
43
|
+
if name.end_with?("?")
|
42
44
|
any?(name[0..-2])
|
43
45
|
else
|
44
46
|
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)
|
@@ -31,6 +31,9 @@ module ActiveSupport
|
|
31
31
|
class BacktraceCleaner
|
32
32
|
def initialize
|
33
33
|
@filters, @silencers = [], []
|
34
|
+
add_gem_filter
|
35
|
+
add_gem_silencer
|
36
|
+
add_stdlib_silencer
|
34
37
|
end
|
35
38
|
|
36
39
|
# Returns the backtrace after all filters and silencers have been run
|
@@ -62,7 +65,7 @@ module ActiveSupport
|
|
62
65
|
# for a given line, it will be excluded from the clean backtrace.
|
63
66
|
#
|
64
67
|
# # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
|
65
|
-
# backtrace_cleaner.add_silencer { |line|
|
68
|
+
# backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
|
66
69
|
def add_silencer(&block)
|
67
70
|
@silencers << block
|
68
71
|
end
|
@@ -82,6 +85,25 @@ module ActiveSupport
|
|
82
85
|
end
|
83
86
|
|
84
87
|
private
|
88
|
+
FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
|
89
|
+
|
90
|
+
def add_gem_filter
|
91
|
+
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
|
92
|
+
return if gems_paths.empty?
|
93
|
+
|
94
|
+
gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
|
95
|
+
gems_result = '\3 (\4) \5'
|
96
|
+
add_filter { |line| line.sub(gems_regexp, gems_result) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_gem_silencer
|
100
|
+
add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_stdlib_silencer
|
104
|
+
add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
|
105
|
+
end
|
106
|
+
|
85
107
|
def filter_backtrace(backtrace)
|
86
108
|
@filters.each do |f|
|
87
109
|
backtrace = backtrace.map { |line| f.call(line) }
|
@@ -99,7 +121,11 @@ module ActiveSupport
|
|
99
121
|
end
|
100
122
|
|
101
123
|
def noise(backtrace)
|
102
|
-
backtrace
|
124
|
+
backtrace.select do |line|
|
125
|
+
@silencers.any? do |s|
|
126
|
+
s.call(line)
|
127
|
+
end
|
128
|
+
end
|
103
129
|
end
|
104
130
|
end
|
105
131
|
end
|
@@ -41,7 +41,7 @@ module ActiveSupport
|
|
41
41
|
|
42
42
|
result = nil
|
43
43
|
ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
|
44
|
-
logger.
|
44
|
+
logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
|
45
45
|
result
|
46
46
|
else
|
47
47
|
yield
|
@@ -16,31 +16,35 @@ module ActiveSupport
|
|
16
16
|
attr_reader :cache_path
|
17
17
|
|
18
18
|
DIR_FORMATTER = "%03X"
|
19
|
-
FILENAME_MAX_SIZE =
|
19
|
+
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
20
|
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
|
21
|
-
EXCLUDED_DIRS = [".", ".."].freeze
|
22
21
|
GITKEEP_FILES = [".gitkeep", ".keep"].freeze
|
23
22
|
|
24
|
-
def initialize(cache_path, options
|
23
|
+
def initialize(cache_path, **options)
|
25
24
|
super(options)
|
26
25
|
@cache_path = cache_path.to_s
|
27
26
|
end
|
28
27
|
|
28
|
+
# Advertise cache versioning support.
|
29
|
+
def self.supports_cache_versioning?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
29
33
|
# Deletes all items from the cache. In this case it deletes all the entries in the specified
|
30
34
|
# file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
|
31
35
|
# config file when using +FileStore+ because everything in that directory will be deleted.
|
32
36
|
def clear(options = nil)
|
33
|
-
root_dirs =
|
37
|
+
root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
|
34
38
|
FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
|
35
|
-
rescue Errno::ENOENT
|
39
|
+
rescue Errno::ENOENT, Errno::ENOTEMPTY
|
36
40
|
end
|
37
41
|
|
38
42
|
# Preemptively iterates through all stored keys and removes the ones which have expired.
|
39
43
|
def cleanup(options = nil)
|
40
44
|
options = merged_options(options)
|
41
45
|
search_dir(cache_path) do |fname|
|
42
|
-
entry = read_entry(fname, options)
|
43
|
-
delete_entry(fname, options) if entry && entry.expired?
|
46
|
+
entry = read_entry(fname, **options)
|
47
|
+
delete_entry(fname, **options) if entry && entry.expired?
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
@@ -62,30 +66,30 @@ module ActiveSupport
|
|
62
66
|
matcher = key_matcher(matcher, options)
|
63
67
|
search_dir(cache_path) do |path|
|
64
68
|
key = file_path_key(path)
|
65
|
-
delete_entry(path, options) if key.match(matcher)
|
69
|
+
delete_entry(path, **options) if key.match(matcher)
|
66
70
|
end
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
70
74
|
private
|
71
|
-
|
72
|
-
def read_entry(key, options)
|
75
|
+
def read_entry(key, **options)
|
73
76
|
if File.exist?(key)
|
74
|
-
File.open(key) { |f|
|
77
|
+
entry = File.open(key) { |f| deserialize_entry(f.read) }
|
78
|
+
entry if entry.is_a?(Cache::Entry)
|
75
79
|
end
|
76
80
|
rescue => e
|
77
81
|
logger.error("FileStoreError (#{e}): #{e.message}") if logger
|
78
82
|
nil
|
79
83
|
end
|
80
84
|
|
81
|
-
def write_entry(key, entry, options)
|
85
|
+
def write_entry(key, entry, **options)
|
82
86
|
return false if options[:unless_exist] && File.exist?(key)
|
83
87
|
ensure_cache_path(File.dirname(key))
|
84
|
-
File.atomic_write(key, cache_path) { |f|
|
88
|
+
File.atomic_write(key, cache_path) { |f| f.write(serialize_entry(entry)) }
|
85
89
|
true
|
86
90
|
end
|
87
91
|
|
88
|
-
def delete_entry(key, options)
|
92
|
+
def delete_entry(key, **options)
|
89
93
|
if File.exist?(key)
|
90
94
|
begin
|
91
95
|
File.delete(key)
|
@@ -103,12 +107,10 @@ module ActiveSupport
|
|
103
107
|
def lock_file(file_name, &block)
|
104
108
|
if File.exist?(file_name)
|
105
109
|
File.open(file_name, "r+") do |f|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
f.flock File::LOCK_UN
|
111
|
-
end
|
110
|
+
f.flock File::LOCK_EX
|
111
|
+
yield
|
112
|
+
ensure
|
113
|
+
f.flock File::LOCK_UN
|
112
114
|
end
|
113
115
|
else
|
114
116
|
yield
|
@@ -127,15 +129,19 @@ module ActiveSupport
|
|
127
129
|
hash = Zlib.adler32(fname)
|
128
130
|
hash, dir_1 = hash.divmod(0x1000)
|
129
131
|
dir_2 = hash.modulo(0x1000)
|
130
|
-
fname_paths = []
|
131
132
|
|
132
133
|
# Make sure file name doesn't exceed file system limits.
|
133
|
-
|
134
|
-
fname_paths
|
135
|
-
|
136
|
-
|
134
|
+
if fname.length < FILENAME_MAX_SIZE
|
135
|
+
fname_paths = fname
|
136
|
+
else
|
137
|
+
fname_paths = []
|
138
|
+
begin
|
139
|
+
fname_paths << fname[0, FILENAME_MAX_SIZE]
|
140
|
+
fname = fname[FILENAME_MAX_SIZE..-1]
|
141
|
+
end until fname.blank?
|
142
|
+
end
|
137
143
|
|
138
|
-
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2,
|
144
|
+
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
|
139
145
|
end
|
140
146
|
|
141
147
|
# Translate a file path into a key.
|
@@ -147,7 +153,7 @@ module ActiveSupport
|
|
147
153
|
# Delete empty directories in the cache.
|
148
154
|
def delete_empty_directories(dir)
|
149
155
|
return if File.realpath(dir) == File.realpath(cache_path)
|
150
|
-
if
|
156
|
+
if Dir.children(dir).empty?
|
151
157
|
Dir.delete(dir) rescue nil
|
152
158
|
delete_empty_directories(File.dirname(dir))
|
153
159
|
end
|
@@ -160,8 +166,7 @@ module ActiveSupport
|
|
160
166
|
|
161
167
|
def search_dir(dir, &callback)
|
162
168
|
return if !File.exist?(dir)
|
163
|
-
Dir.
|
164
|
-
next if EXCLUDED_DIRS.include?(d)
|
169
|
+
Dir.each_child(dir) do |d|
|
165
170
|
name = File.join(dir, d)
|
166
171
|
if File.directory?(name)
|
167
172
|
search_dir(name, &callback)
|
@@ -186,11 +191,6 @@ module ActiveSupport
|
|
186
191
|
end
|
187
192
|
end
|
188
193
|
end
|
189
|
-
|
190
|
-
# Exclude entries from source directory
|
191
|
-
def exclude_from(source, excludes)
|
192
|
-
Dir.entries(source).reject { |f| excludes.include?(f) }
|
193
|
-
end
|
194
194
|
end
|
195
195
|
end
|
196
196
|
end
|
@@ -7,6 +7,7 @@ rescue LoadError => e
|
|
7
7
|
raise e
|
8
8
|
end
|
9
9
|
|
10
|
+
require "active_support/core_ext/enumerable"
|
10
11
|
require "active_support/core_ext/marshal"
|
11
12
|
require "active_support/core_ext/array/extract_options"
|
12
13
|
|
@@ -25,36 +26,45 @@ 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
|
29
|
+
DEFAULT_CODER = NullCoder # Dalli automatically Marshal values
|
30
|
+
|
28
31
|
# Provide support for raw values in the local cache strategy.
|
29
32
|
module LocalCacheWithRaw # :nodoc:
|
30
33
|
private
|
31
|
-
def write_entry(key, entry, options)
|
34
|
+
def write_entry(key, entry, **options)
|
32
35
|
if options[:raw] && local_cache
|
33
36
|
raw_entry = Entry.new(entry.value.to_s)
|
34
37
|
raw_entry.expires_at = entry.expires_at
|
35
|
-
super(key, raw_entry, options)
|
38
|
+
super(key, raw_entry, **options)
|
36
39
|
else
|
37
40
|
super
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
41
44
|
|
45
|
+
# Advertise cache versioning support.
|
46
|
+
def self.supports_cache_versioning?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
42
50
|
prepend Strategy::LocalCache
|
43
51
|
prepend LocalCacheWithRaw
|
44
52
|
|
45
53
|
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
|
46
54
|
|
47
55
|
# Creates a new Dalli::Client instance with specified addresses and options.
|
48
|
-
#
|
56
|
+
# If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
|
57
|
+
# - ENV["MEMCACHE_SERVERS"] (if defined)
|
58
|
+
# - "127.0.0.1:11211" (otherwise)
|
49
59
|
#
|
50
60
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache
|
51
|
-
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["
|
61
|
+
# # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
|
52
62
|
# ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
|
53
63
|
# # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
|
54
64
|
def self.build_mem_cache(*addresses) # :nodoc:
|
55
65
|
addresses = addresses.flatten
|
56
66
|
options = addresses.extract_options!
|
57
|
-
addresses =
|
67
|
+
addresses = nil if addresses.compact.empty?
|
58
68
|
pool_options = retrieve_pool_options(options)
|
59
69
|
|
60
70
|
if pool_options.empty?
|
@@ -71,8 +81,8 @@ module ActiveSupport
|
|
71
81
|
#
|
72
82
|
# ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
|
73
83
|
#
|
74
|
-
# If no addresses are
|
75
|
-
#
|
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).
|
76
86
|
def initialize(*addresses)
|
77
87
|
addresses = addresses.flatten
|
78
88
|
options = addresses.extract_options!
|
@@ -129,27 +139,28 @@ module ActiveSupport
|
|
129
139
|
|
130
140
|
private
|
131
141
|
# Read an entry from the cache.
|
132
|
-
def read_entry(key, options)
|
142
|
+
def read_entry(key, **options)
|
133
143
|
rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
|
134
144
|
end
|
135
145
|
|
136
146
|
# Write an entry to the cache.
|
137
|
-
def write_entry(key, entry, options)
|
138
|
-
method = options
|
139
|
-
value = options[:raw] ? entry.value.to_s : entry
|
147
|
+
def write_entry(key, entry, **options)
|
148
|
+
method = options[:unless_exist] ? :add : :set
|
149
|
+
value = options[:raw] ? entry.value.to_s : serialize_entry(entry)
|
140
150
|
expires_in = options[:expires_in].to_i
|
141
|
-
if expires_in > 0 && !options[:raw]
|
151
|
+
if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
|
142
152
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
143
153
|
expires_in += 5.minutes
|
144
154
|
end
|
145
155
|
rescue_error_with false do
|
146
|
-
|
156
|
+
# The value "compress: false" prevents duplicate compression within Dalli.
|
157
|
+
@data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) }
|
147
158
|
end
|
148
159
|
end
|
149
160
|
|
150
161
|
# Reads multiple entries from the cache implementation.
|
151
|
-
def read_multi_entries(names, options)
|
152
|
-
keys_to_names =
|
162
|
+
def read_multi_entries(names, **options)
|
163
|
+
keys_to_names = names.index_by { |name| normalize_key(name, options) }
|
153
164
|
|
154
165
|
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
|
155
166
|
values = {}
|
@@ -166,7 +177,7 @@ module ActiveSupport
|
|
166
177
|
end
|
167
178
|
|
168
179
|
# Delete an entry from the cache.
|
169
|
-
def delete_entry(key, options)
|
180
|
+
def delete_entry(key, **options)
|
170
181
|
rescue_error_with(false) { @data.with { |c| c.delete(key) } }
|
171
182
|
end
|
172
183
|
|
@@ -174,17 +185,21 @@ module ActiveSupport
|
|
174
185
|
# before applying the regular expression to ensure we are escaping all
|
175
186
|
# characters properly.
|
176
187
|
def normalize_key(key, options)
|
177
|
-
key = super
|
178
|
-
|
179
|
-
|
180
|
-
|
188
|
+
key = super
|
189
|
+
|
190
|
+
if key
|
191
|
+
key = key.dup.force_encoding(Encoding::ASCII_8BIT)
|
192
|
+
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
|
193
|
+
key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
|
194
|
+
end
|
195
|
+
|
181
196
|
key
|
182
197
|
end
|
183
198
|
|
184
|
-
def deserialize_entry(
|
185
|
-
|
186
|
-
|
187
|
-
|
199
|
+
def deserialize_entry(payload)
|
200
|
+
entry = super
|
201
|
+
entry = Entry.new(entry, compress: false) unless entry.nil? || entry.is_a?(Entry)
|
202
|
+
entry
|
188
203
|
end
|
189
204
|
|
190
205
|
def rescue_error_with(fallback)
|
@@ -16,13 +16,37 @@ 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
|
+
class << self
|
29
|
+
def load(entry)
|
30
|
+
entry = entry.dup
|
31
|
+
entry.dup_value!
|
32
|
+
entry
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump(entry)
|
36
|
+
entry.dup_value!
|
37
|
+
entry
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
DEFAULT_CODER = DupCoder
|
43
|
+
|
21
44
|
def initialize(options = nil)
|
22
45
|
options ||= {}
|
46
|
+
# Disable compression by default.
|
47
|
+
options[:compress] ||= false
|
23
48
|
super(options)
|
24
49
|
@data = {}
|
25
|
-
@key_access = {}
|
26
50
|
@max_size = options[:size] || 32.megabytes
|
27
51
|
@max_prune_time = options[:max_prune_time] || 2
|
28
52
|
@cache_size = 0
|
@@ -30,11 +54,15 @@ module ActiveSupport
|
|
30
54
|
@pruning = false
|
31
55
|
end
|
32
56
|
|
57
|
+
# Advertise cache versioning support.
|
58
|
+
def self.supports_cache_versioning?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
33
62
|
# Delete all data stored in a given cache store.
|
34
63
|
def clear(options = nil)
|
35
64
|
synchronize do
|
36
65
|
@data.clear
|
37
|
-
@key_access.clear
|
38
66
|
@cache_size = 0
|
39
67
|
end
|
40
68
|
end
|
@@ -46,7 +74,7 @@ module ActiveSupport
|
|
46
74
|
keys = synchronize { @data.keys }
|
47
75
|
keys.each do |key|
|
48
76
|
entry = @data[key]
|
49
|
-
delete_entry(key, options) if entry && entry.expired?
|
77
|
+
delete_entry(key, **options) if entry && entry.expired?
|
50
78
|
end
|
51
79
|
end
|
52
80
|
end
|
@@ -57,13 +85,13 @@ module ActiveSupport
|
|
57
85
|
return if pruning?
|
58
86
|
@pruning = true
|
59
87
|
begin
|
60
|
-
start_time =
|
88
|
+
start_time = Concurrent.monotonic_time
|
61
89
|
cleanup
|
62
90
|
instrument(:prune, target_size, from: @cache_size) do
|
63
|
-
keys = synchronize { @
|
91
|
+
keys = synchronize { @data.keys }
|
64
92
|
keys.each do |key|
|
65
|
-
delete_entry(key, options)
|
66
|
-
return if @cache_size <= target_size || (max_time &&
|
93
|
+
delete_entry(key, **options)
|
94
|
+
return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
|
67
95
|
end
|
68
96
|
end
|
69
97
|
ensure
|
@@ -93,13 +121,13 @@ module ActiveSupport
|
|
93
121
|
matcher = key_matcher(matcher, options)
|
94
122
|
keys = synchronize { @data.keys }
|
95
123
|
keys.each do |key|
|
96
|
-
delete_entry(key, options) if key.match(matcher)
|
124
|
+
delete_entry(key, **options) if key.match(matcher)
|
97
125
|
end
|
98
126
|
end
|
99
127
|
end
|
100
128
|
|
101
129
|
def inspect # :nodoc:
|
102
|
-
"
|
130
|
+
"#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
|
103
131
|
end
|
104
132
|
|
105
133
|
# Synchronize calls to the cache. This should be called wherever the underlying cache implementation
|
@@ -109,54 +137,52 @@ module ActiveSupport
|
|
109
137
|
end
|
110
138
|
|
111
139
|
private
|
112
|
-
|
113
140
|
PER_ENTRY_OVERHEAD = 240
|
114
141
|
|
115
|
-
def cached_size(key,
|
116
|
-
key.to_s.bytesize +
|
142
|
+
def cached_size(key, payload)
|
143
|
+
key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
|
117
144
|
end
|
118
145
|
|
119
|
-
def read_entry(key, options)
|
120
|
-
entry =
|
146
|
+
def read_entry(key, **options)
|
147
|
+
entry = nil
|
121
148
|
synchronize do
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
149
|
+
payload = @data.delete(key)
|
150
|
+
if payload
|
151
|
+
@data[key] = payload
|
152
|
+
entry = deserialize_entry(payload)
|
126
153
|
end
|
127
154
|
end
|
128
155
|
entry
|
129
156
|
end
|
130
157
|
|
131
|
-
def write_entry(key, entry, options)
|
132
|
-
entry
|
158
|
+
def write_entry(key, entry, **options)
|
159
|
+
payload = serialize_entry(entry)
|
133
160
|
synchronize do
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
161
|
+
return false if options[:unless_exist] && @data.key?(key)
|
162
|
+
|
163
|
+
old_payload = @data[key]
|
164
|
+
if old_payload
|
165
|
+
@cache_size -= (old_payload.bytesize - payload.bytesize)
|
138
166
|
else
|
139
|
-
@cache_size += cached_size(key,
|
167
|
+
@cache_size += cached_size(key, payload)
|
140
168
|
end
|
141
|
-
@
|
142
|
-
@data[key] = entry
|
169
|
+
@data[key] = payload
|
143
170
|
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
|
144
171
|
true
|
145
172
|
end
|
146
173
|
end
|
147
174
|
|
148
|
-
def delete_entry(key, options)
|
175
|
+
def delete_entry(key, **options)
|
149
176
|
synchronize do
|
150
|
-
@
|
151
|
-
|
152
|
-
|
153
|
-
!!entry
|
177
|
+
payload = @data.delete(key)
|
178
|
+
@cache_size -= cached_size(key, payload) if payload
|
179
|
+
!!payload
|
154
180
|
end
|
155
181
|
end
|
156
182
|
|
157
183
|
def modify_value(name, amount, options)
|
184
|
+
options = merged_options(options)
|
158
185
|
synchronize do
|
159
|
-
options = merged_options(options)
|
160
186
|
if num = read(name, options)
|
161
187
|
num = num.to_i + amount
|
162
188
|
write(name, num, options)
|